railway-provider-hafas-0.1.2/.cargo_vcs_info.json0000644000000001640000000000100154150ustar { "git": { "sha1": "edc896b592d45f69faf9d95818a8316d475ef8a5" }, "path_in_vcs": "railway-provider-hafas" }railway-provider-hafas-0.1.2/Cargo.toml0000644000000067660000000000100134310ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "railway-provider-hafas" version = "0.1.2" authors = [ "Julian Schmidhuber ", "Yureka ", ] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Implementation of a HAFAS client in Rust" readme = "README.md" keywords = [ "railway-backend", "train", "public-transport", ] license = "AGPL-3.0-or-later OR EUPL-1.2" repository = "https://gitlab.com/schmiddi-on-mobile/railway-backend" [lib] name = "railway_provider_hafas" path = "src/lib.rs" [dependencies.async-trait] version = "0.1" [dependencies.chrono] version = "0.4" features = ["serde"] [dependencies.chrono-tz] version = "0.8" [dependencies.geojson] version = "0.24" optional = true [dependencies.hex] version = "0.4.3" [dependencies.log] version = "0.4" [dependencies.md-5] version = "0.10" [dependencies.polyline] version = "0.10" optional = true [dependencies.rcore] version = "0.1" package = "railway-core" [dependencies.serde] version = "1.0" features = ["derive"] [dependencies.serde_json] version = "1.0" [dependencies.thiserror] version = "1.0" [dependencies.tokio] version = "1.37" features = [ "macros", "rt-multi-thread", ] optional = true [dependencies.url] version = "2.5.0" [dev-dependencies.env_logger] version = "0.11.3" [dev-dependencies.tokio] version = "1.37" features = [ "rt-multi-thread", "macros", ] [features] all-profiles = [ "db-profile", "vbb-profile", "oebb-profile", "nahsh-profile", "vvt-profile", "pkp-profile", "irish-rail-profile", "mobiliteit-lu-profile", "dart-profile", "rmv-profile", "cmta-profile", "sbahn-muenchen-profile", "saarvv-profile", "cfl-profile", "nvv-profile", "vsn-profile", "vgi-profile", "vbn-profile", "vrn-profile", "rsag-profile", "vmt-profile", "vos-profile", "avv-profile", "rejseplanen-profile", "ooevv-profile", "salzburg-profile", "verbundlinie-profile", "svv-profile", "vor-profile", "vkg-profile", "vvv-profile", "bls-profile", "kvb-profile", "bart-profile", "ivb-profile", "resrobot-profile", ] avv-profile = [] bart-profile = [] bls-profile = [] cfl-profile = [] cmta-profile = [] dart-profile = [] db-profile = [] default = [] insa-profile = [] irish-rail-profile = [] ivb-profile = [] kvb-profile = [] mobil-nrw-profile = [] mobiliteit-lu-profile = [] nahsh-profile = [] nvv-profile = [] oebb-profile = [] ooevv-profile = [] pkp-profile = [] polylines = [ "polyline", "geojson", "rcore/polylines", ] rejseplanen-profile = [] resrobot-profile = [] rmv-profile = [] rsag-profile = [] rt-multi-thread = ["rcore/rt-multi-thread"] saarvv-profile = [] salzburg-profile = [] sbahn-muenchen-profile = [] svv-profile = [] vbb-profile = [] vbn-profile = [] verbundlinie-profile = [] vgi-profile = [] vkg-profile = [] vmt-profile = [] vor-profile = [] vos-profile = [] vrn-profile = [] vsn-profile = [] vvt-profile = [] vvv-profile = [] railway-provider-hafas-0.1.2/Cargo.toml.orig000064400000000000000000000052341046102023000170770ustar 00000000000000[package] name = "railway-provider-hafas" version = "0.1.2" authors = ["Julian Schmidhuber ", "Yureka "] edition = "2021" description = "Implementation of a HAFAS client in Rust" repository = "https://gitlab.com/schmiddi-on-mobile/railway-backend" license = "AGPL-3.0-or-later OR EUPL-1.2" keywords = ["railway-backend", "train", "public-transport"] [dependencies] rcore = { package = "railway-core", path = "../railway-core", version = "0.1" } md-5 = "0.10" serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" thiserror = "1.0" async-trait = "0.1" chrono = { version = "0.4", features = [ "serde" ] } chrono-tz = "0.8" geojson = { version = "0.24", optional = true } polyline = { version = "0.10", optional = true } log = "0.4" hex = "0.4.3" url = "2.5.0" tokio = { version = "1.37", optional = true, features = [ "macros", "rt-multi-thread" ] } [features] default = [ ] rt-multi-thread = [ "rcore/rt-multi-thread" ] polylines = [ "polyline", "geojson", "rcore/polylines" ] db-profile = [] # sncf-profile = [] vbb-profile = [] oebb-profile = [] nahsh-profile = [] vvt-profile = [] pkp-profile = [] irish-rail-profile = [] mobiliteit-lu-profile = [] bart-profile = [] dart-profile = [] rmv-profile = [] insa-profile = [] ivb-profile = [] kvb-profile = [] cmta-profile = [] sbahn-muenchen-profile = [] saarvv-profile = [] cfl-profile = [] nvv-profile = [] mobil-nrw-profile = [] vsn-profile = [] vgi-profile = [] vbn-profile = [] vrn-profile = [] rsag-profile = [] vmt-profile = [] vos-profile = [] avv-profile = [] rejseplanen-profile = [] ooevv-profile = [] salzburg-profile = [] verbundlinie-profile = [] svv-profile = [] vor-profile = [] vkg-profile = [] vvv-profile = [] # tpg-profile = [] bls-profile = [] resrobot-profile = [] all-profiles = [ "db-profile", "vbb-profile", "oebb-profile", "nahsh-profile", "vvt-profile", "pkp-profile", "irish-rail-profile", "mobiliteit-lu-profile", "dart-profile", "rmv-profile", "cmta-profile", "sbahn-muenchen-profile", "saarvv-profile", "cfl-profile", "nvv-profile", "vsn-profile", "vgi-profile", "vbn-profile", "vrn-profile", "rsag-profile", "vmt-profile", "vos-profile", "avv-profile", "rejseplanen-profile", "ooevv-profile", "salzburg-profile", "verbundlinie-profile", "svv-profile", "vor-profile", "vkg-profile", "vvv-profile", "bls-profile", "kvb-profile", "bart-profile", "ivb-profile", "resrobot-profile", ] [dev-dependencies] tokio = { version = "1.37", features = [ "rt-multi-thread", "macros" ] } env_logger = "0.11.3" rcore = { package = "railway-core", path = "../railway-core", features = [ "reqwest-requester" ] } railway-provider-hafas-0.1.2/README.md000064400000000000000000000011621046102023000154630ustar 00000000000000# Railway Hafas Provider Implementation of a HAFAS client in Rust. This crate is part of [railway-backend](https://gitlab.com/schmiddi-on-mobile/railway-backend). You can find a high-level documentation of railway-backend [here](https://gitlab.com/schmiddi-on-mobile/railway-backend/-/tree/main/docs?ref_type=heads). This was originally forked from [hafas-rs](https://cyberchaos.dev/yuka/hafas-rs/), but later heavily refactored to create a generic backend for Railway. It takes a lot of inspiration from the JavaScript [hafas-client](https://github.com/public-transport/hafas-client) library, which is licensed under ISC. railway-provider-hafas-0.1.2/src/api/mod.rs000064400000000000000000000164231046102023000166770ustar 00000000000000use async_trait::async_trait; use chrono::Utc; use rcore::{ Journey, JourneysOptions, JourneysResponse, LocationsOptions, LocationsResponse, LoyaltyCard, Place, Provider, RefreshJourneyOptions, RefreshJourneyResponse, Requester, TransferOptions, }; use serde_json::json; use crate::{ client::HafasClient, format::ToHafas, parse::{journeys_response::HafasJourneysResponse, locations_response::HafasLocationsResponse}, }; #[cfg_attr(feature = "rt-multi-thread", async_trait)] #[cfg_attr(not(feature = "rt-multi-thread"), async_trait(?Send))] impl Provider for HafasClient { type Error = crate::Error; async fn locations( &self, opts: LocationsOptions, ) -> Result> { let data: HafasLocationsResponse = self .request(json!({ "svcReqL": [ { "cfg": { "polyEnc": "GPA" }, "meth": "LocMatch", "req": { "input": { "loc": { "type": "ALL", "name": format!("{}?", opts.query), }, "maxLoc": opts.results, "field": "S" } } } ], "lang": opts.language.as_deref().unwrap_or_else(|| self.profile.language()), })) .await?; Ok(self .profile .parse_locations_response(data) .map_err(|e| rcore::Error::Provider(e.into()))?) } async fn journeys( &self, from: Place, to: Place, opts: JourneysOptions, ) -> Result> { let timezone = self.profile.timezone(); let (when, is_departure) = match (opts.departure, opts.arrival) { (Some(_), Some(_)) => Err(rcore::Error::Provider(Self::Error::InvalidInput( "departure and arrival are mutually exclusive".to_string(), )))?, (Some(departure), None) => (departure.with_timezone(&timezone), true), (None, Some(arrival)) => (arrival.with_timezone(&timezone), false), (None, None) => (Utc::now().with_timezone(&timezone), true), }; let tariff_class = opts.tariff_class; let mut req = json!({ "svcReqL": [ { "cfg": { "polyEnc": "GPA" }, "meth": "TripSearch", "req": { "ctxScr": null, "getPasslist": opts.stopovers, "maxChg": match opts.transfers { TransferOptions::Unlimited => -1, TransferOptions::Limited(i) => i as i64, }, "minChgTime": opts.transfer_time.num_minutes(), "numF": opts.results, "depLocL": [ from.to_hafas() ], "viaLocL": json!(opts.via.into_iter().map(|x| json!({ "loc": x.to_hafas() })).collect::>()), "arrLocL": [ to.to_hafas() ], "jnyFltrL": [ { "type": "PROD", "mode": "INC", "value": self.profile.products_to_hafas(&opts.products), }, { "type": "META", "mode": "INC", "meta": opts.accessibility.to_hafas(), } ], "gisFltrL": [], "getTariff": opts.tickets, "ushrp": opts.start_with_walking, "getPT": true, "getIV": false, "outFrwd": is_departure, "outDate": when.format("%Y%m%d").to_string(), "outTime": when.format("%H%M%S").to_string(), "trfReq": { "jnyCl": tariff_class.to_hafas(), "tvlrProf": [ { "type": opts.passenger_age.map(|a| self.profile.age_to_hafas(a)).unwrap_or("E"), "redtnCard": opts.loyalty_card.map(LoyaltyCard::to_id), } ], "cType": "PK" } } } ], "lang": opts.language.as_deref().unwrap_or("en"), }); #[cfg(feature = "polylines")] { req["svcReqL"][0]["req"]["getPolyline"] = json!(opts.polylines); } #[cfg(not(feature = "polylines"))] { req["svcReqL"][0]["req"]["getPolyline"] = json!(false); } if let Some(r) = opts.later_than.or(opts.earlier_than) { req["svcReqL"][0]["req"]["ctxScr"] = json!(r); } if opts.bike_friendly { req["svcReqL"][0]["req"]["jnyFltrL"] .as_array_mut() .unwrap() .push(json!({"type": "BC", "mode": "INC"})) } let data = self.request(req).await?; Ok(self .profile .parse_journeys_response(data, tariff_class) .map_err(|e| rcore::Error::Provider(e.into()))?) } async fn refresh_journey( &self, journey: &Journey, opts: RefreshJourneyOptions, ) -> Result> { let refresh_token = &journey.id; let tariff_class = opts.tariff_class; let mut req = json!({ "svcReqL": [ { "cfg": {}, "meth": "Reconstruction", "req": { "getIST": true, "getPasslist": opts.stopovers, "getTariff": opts.tickets, } } ], "lang": opts.language.as_deref().unwrap_or("en"), }); if self.profile.refresh_journey_use_out_recon_l() { req["svcReqL"][0]["req"]["outReconL"] = json!([{ "ctx": refresh_token }]); } else { req["svcReqL"][0]["req"]["ctxRecon"] = json!(refresh_token); } #[cfg(feature = "polylines")] { req["svcReqL"][0]["req"]["getPolyline"] = json!(opts.polylines); } #[cfg(not(feature = "polylines"))] { req["svcReqL"][0]["req"]["getPolyline"] = json!(false); } let data: HafasJourneysResponse = self.request(req).await?; let mut journeys = self .profile .parse_journeys_response(data, tariff_class) .map_err(|e| rcore::Error::Provider(e.into()))?; Ok(journeys.journeys.remove(0)) } } railway-provider-hafas-0.1.2/src/client.rs000064400000000000000000000131371046102023000166240ustar 00000000000000use super::{Error, ParseError, Profile, Requester}; use log::debug; use md5::{Digest, Md5}; use rcore::RequesterBuilder; use serde::de::DeserializeOwned; use serde::Deserialize; use serde_json::Value; use std::collections::HashMap; use std::sync::Arc; #[derive(Clone)] pub struct HafasClient { pub(crate) profile: Arc>, requester: Arc, } impl HafasClient { pub fn new>( profile: P, mut requester: RB, ) -> Self { if let Some(certificate) = profile.custom_pem_bundle() { requester = requester.with_pem_bundle(certificate); } HafasClient { profile: Arc::new(Box::new(profile)), requester: Arc::new(requester.build()), } } } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct HafasResponseInner { err: Option, err_txt: Option, } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct HafasResponseOuter { err: Option, err_txt: Option, svc_res_l: Option>, } #[derive(Deserialize)] struct HafasResponseInnerOk { res: T, } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct HafasResponseOuterOk { svc_res_l: Vec>, } impl HafasClient { pub(crate) async fn request( &self, mut req_json: Value, ) -> Result> { self.profile.prepare_body(&mut req_json); debug!( "{}", serde_json::to_string_pretty(&req_json) .map_err(|e| rcore::Error::Provider(e.into()))? ); let req_str = serde_json::to_string(&req_json).map_err(|e| rcore::Error::Provider(e.into()))?; // TODO: Error? let mut url = url::Url::parse(self.profile.url()).expect("Failed to parse provider URL"); if let Some(salt) = self.profile.checksum_salt() { if self.profile.salt() { let mut hasher = Md5::new(); hasher.update(&req_str); hasher.update(salt); let checksum = hasher .finalize() .iter() .map(|b| format!("{:02x}", b)) .collect::>() .join(""); url.query_pairs_mut().append_pair("checksum", &checksum); } if self.profile.mic_mac() { let mut hasher_mic = Md5::new(); hasher_mic.update(&req_str); let mic = hasher_mic .finalize() .iter() .map(|b| format!("{:02x}", b)) .collect::>() .join(""); let mut hasher_mac = Md5::new(); hasher_mac.update(&mic); if let Ok(s) = hex::decode(salt) { hasher_mac.update(s); } else { hasher_mac.update(salt); } let mac = hasher_mac .finalize() .iter() .map(|b| format!("{:02x}", b)) .collect::>() .join(""); url.query_pairs_mut() .append_pair("mic", &mic) .append_pair("mac", &mac); } } let mut headers = HashMap::new(); headers.insert("Content-Type", "application/json"); headers.insert("Accept", "application/json"); self.profile.prepare_headers(&mut headers); let bytes = self .requester .post(&url, req_str.as_bytes(), headers) .await .map_err(rcore::Error::Request)?; debug!( "Response: {}", serde_json::to_string( &serde_json::from_slice::(&bytes) .map_err(|e| rcore::Error::Provider(e.into()))? ) .map_err(|e| rcore::Error::Provider(e.into()))? ); { let data = serde_json::from_slice(&bytes).map_err(|e| rcore::Error::Provider(e.into()))?; let HafasResponseOuter { err, err_txt, svc_res_l, } = data; if let Some(some_err) = err { if some_err != "OK" { return Err(rcore::Error::Provider(Error::Hafas { text: err_txt.unwrap_or_else(|| format!("Code {}", &some_err)), code: some_err, })); } } let HafasResponseInner { err, err_txt } = svc_res_l .map(|mut x| x.remove(0)) .ok_or_else(|| ParseError::from("missing svcResL")) .map_err(|e| rcore::Error::Provider(e.into()))?; if let Some(some_err) = err { if some_err != "OK" { // TODO: Parse to better errors. return Err(rcore::Error::Provider(Error::Hafas { text: err_txt.unwrap_or_else(|| format!("Code {}", &some_err)), code: some_err, })); } } } { let mut data: HafasResponseOuterOk = serde_json::from_slice(&bytes).map_err(|e| rcore::Error::Provider(e.into()))?; let HafasResponseInnerOk { res } = data.svc_res_l.remove(0); Ok(res) } } } railway-provider-hafas-0.1.2/src/error.rs000064400000000000000000000020721046102023000164730ustar 00000000000000use thiserror::Error as ThisError; #[derive(ThisError, Debug)] pub enum ParseError { #[error("{info}")] InvalidData { info: String }, #[error("source")] Chrono { #[from] source: chrono::ParseError, }, #[error("source")] Int { #[from] source: std::num::ParseIntError, }, } impl From for ParseError { fn from(info: String) -> ParseError { ParseError::InvalidData { info } } } impl From<&str> for ParseError { fn from(info: &str) -> ParseError { ParseError::InvalidData { info: info.to_string(), } } } #[derive(ThisError, Debug)] pub enum Error { #[error("{source}")] Json { #[from] source: serde_json::Error, }, #[error("{source}")] Parse { #[from] source: ParseError, }, #[error("{text}")] Hafas { code: String, text: String }, #[error("{0}")] InvalidInput(String), } pub type Result = std::result::Result; pub type ParseResult = std::result::Result; railway-provider-hafas-0.1.2/src/format.rs000064400000000000000000000050171046102023000166340ustar 00000000000000use super::{Accessibility, Location, Place, Station, TariffClass}; use serde_json::json; pub trait ToHafas { fn to_hafas(&self) -> T; } fn format_coord(coordinate: f32) -> u64 { (coordinate * 1000000.0) as u64 } fn format_identifier(components: Vec<(&str, &str)>) -> String { components .iter() .map(|(k, v)| format!("{}={}@", k, v)) .collect::>() .join("") } impl ToHafas for Place { fn to_hafas(&self) -> serde_json::Value { match self { Place::Station(stop) => { let Station { id, .. } = stop; json!({ "type": "S", "lid": format_identifier(vec![ ("A", "1"), ("L", id), ]) }) } Place::Location(location) => match location { Location::Address { address, latitude, longitude, } => json!({ "type": "A", "lid": format_identifier(vec![ ("A", "2"), ("O", address), ("X", &format_coord(*latitude).to_string()), ("Y", &format_coord(*longitude).to_string()), ]) }), Location::Point { id, latitude, longitude, .. } => { let x = format_coord(*latitude).to_string(); let y = format_coord(*longitude).to_string(); let mut lid = vec![("A", "4"), ("X", &x), ("Y", &y)]; if let Some(id) = id { lid.push(("L", id)); } json!({ "type": "P", "lid": format_identifier(lid) }) } }, } } } impl ToHafas for Accessibility { fn to_hafas(&self) -> String { match self { Accessibility::r#None => "notBarrierfree", Accessibility::Partial => "limitedBarrierfree", Accessibility::Complete => "completeBarrierfree", } .to_string() } } impl ToHafas for TariffClass { fn to_hafas(&self) -> u64 { match *self { TariffClass::First => 1, TariffClass::Second => 2, } } } railway-provider-hafas-0.1.2/src/lib.rs000064400000000000000000000004001046102023000161010ustar 00000000000000#![doc = include_str!("../README.md")] pub mod api; pub mod client; pub mod error; pub mod format; pub mod parse; pub mod profile; // TODO // mod serialize; pub use error::{Error, ParseError, ParseResult, Result}; pub use profile::Profile; use rcore::*; railway-provider-hafas-0.1.2/src/parse/arrival_or_departure.rs000064400000000000000000000034201046102023000226650ustar 00000000000000use crate::ParseResult; use crate::Profile; use chrono::DateTime; use chrono::FixedOffset; use chrono::NaiveDate; use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct HafasPlatform { txt: String, } pub struct HafasArrivalOrDeparture { pub t_z_offset: Option, pub time_s: Option, pub time_r: Option, pub platf_s: Option, pub platf_r: Option, pub pltf_s: Option, pub pltf_r: Option, pub cncl: Option, } pub struct ArrivalOrDeparture { pub platform: Option, pub planned_platform: Option, pub time: Option>, pub planned_time: Option>, pub delay: Option, pub cancelled: Option, } pub(crate) fn default_parse_arrival_or_departure( profile: &P, data: HafasArrivalOrDeparture, date: &NaiveDate, ) -> ParseResult { let HafasArrivalOrDeparture { t_z_offset, time_s, time_r, platf_s, platf_r, pltf_s, pltf_r, cncl, } = data; let planned_time = profile.parse_date(time_s, t_z_offset, date)?; let rt_time = profile.parse_date(time_r, t_z_offset, date)?; let platform_r = platf_r.or_else(|| pltf_r.map(|x| x.txt)); let platform_s = platf_s.or_else(|| pltf_s.map(|x| x.txt)); Ok(ArrivalOrDeparture { platform: platform_r.or_else(|| platform_s.clone()), planned_platform: platform_s, time: rt_time.or(planned_time), planned_time, delay: planned_time.zip(rt_time).map(|(planned_time, rt_time)| { let diff = rt_time - planned_time; diff.num_seconds() }), cancelled: cncl, }) } railway-provider-hafas-0.1.2/src/parse/common.rs000064400000000000000000000051451046102023000177500ustar 00000000000000use crate::parse::line::HafasLine; use crate::parse::load_factor::HafasLoadFactorEntry; use crate::parse::load_factor::LoadFactorEntry; use crate::parse::location::HafasPlace; use crate::parse::operator::HafasOperator; #[cfg(feature = "polylines")] use crate::parse::polyline::HafasPolyline; use crate::parse::remark::HafasRemark; use crate::Line; use crate::ParseResult; use crate::Place; use crate::Profile; use crate::Remark; use crate::TariffClass; use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasCommon { loc_l: Vec, prod_l: Vec, op_l: Option>, tcoc_l: Option>, rem_l: Option>, #[cfg(feature = "polylines")] poly_l: Option>, } #[derive(Debug)] pub struct CommonData { pub tariff_class: TariffClass, pub places: Vec>, pub lines: Vec>, pub load_factors: Vec, pub remarks: Vec>, #[cfg(feature = "polylines")] pub polylines: Vec>, } pub(crate) fn default_parse_common( profile: &P, data: HafasCommon, tariff_class: TariffClass, ) -> ParseResult { let HafasCommon { loc_l, prod_l, op_l, tcoc_l, rem_l, #[cfg(feature = "polylines")] poly_l, } = data; let operators: Vec<_> = op_l .map(|x| { x.into_iter() .map(|x| profile.parse_operator(x)) .collect::>() }) .transpose()? .unwrap_or_default(); Ok(CommonData { tariff_class, places: loc_l .into_iter() .map(|x| profile.parse_place(x).ok()) .collect(), lines: prod_l .into_iter() .map(|x| profile.parse_line(x, &operators).ok()) .collect(), load_factors: tcoc_l .unwrap_or_default() .into_iter() .filter_map(|x| profile.parse_load_factor_entry(x).transpose()) .collect::>()?, remarks: rem_l .unwrap_or_default() .into_iter() .map(|x| profile.parse_remark(x).ok()) .collect(), #[cfg(feature = "polylines")] polylines: poly_l .map(|x| { x.into_iter() .map(|x| profile.parse_polyline(x)) .collect::>() }) .transpose()? .unwrap_or_default(), }) } railway-provider-hafas-0.1.2/src/parse/date.rs000064400000000000000000000034711046102023000173750ustar 00000000000000use crate::ParseError; use crate::ParseResult; use crate::Profile; use chrono::DateTime; use chrono::Duration; use chrono::FixedOffset; use chrono::NaiveDate; use chrono::NaiveTime; use chrono::TimeZone; use chrono_tz::OffsetComponents; pub(crate) fn default_parse_date( profile: &P, time: Option, tz_offset: Option, date: &NaiveDate, ) -> ParseResult>> { let time = match time { Some(time) => time, None => return Ok(None), }; let (dayoffset, time) = match time.len() { 8 => { let iter = time.chars(); let dayoffset = iter.clone().take(2).collect::().parse()?; (dayoffset, iter.skip(2).collect::()) } 6 => (0, time), len => return Err(format!("invalid time length. expected 6 or 8, got {}", len).into()), }; let time = NaiveTime::parse_from_str(&time, "%H%M%S")?; let naive_dt = date.and_time(time) + Duration::days(dayoffset); let timezone = match tz_offset { Some(min) => FixedOffset::east_opt(min * 60), None => match profile .timezone() .offset_from_local_datetime(&naive_dt) // XXX: This may be wrong if the offset is ambiguous, but probably better than throwing an error. .earliest() { Some(off) => FixedOffset::east_opt( (off.base_utc_offset() + off.dst_offset()).num_seconds() as i32, ), None => return Err("timestamp is missing offset and it can not be filled in".into()), }, }; let dt = timezone .ok_or(ParseError::from("timezone offset too large"))? .from_local_datetime(&naive_dt) .unwrap(); // This will never panic for FixedOffset timezone Ok(Some(dt)) } railway-provider-hafas-0.1.2/src/parse/journey.rs000064400000000000000000000045351046102023000201550ustar 00000000000000use crate::parse::common::CommonData; use crate::parse::leg::HafasLeg; use crate::Journey; use crate::ParseResult; use crate::Price; use crate::Profile; use chrono::NaiveDate; use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct HafasJourneyFarePrice { amount: i64, } #[derive(Debug, Deserialize)] pub struct HafasJourneyFare { price: Option, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasJourneyFareSet { #[serde(default)] fare_l: Vec, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasJourneyTrfRes { #[serde(default)] fare_set_l: Vec, } #[derive(Debug, Deserialize)] pub struct HafasJourneyRecon { ctx: Option, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasJourney { date: String, ctx_recon: Option, recon: Option, sec_l: Vec, trf_res: Option, } pub(crate) fn default_parse_journey( profile: &P, data: HafasJourney, common: &CommonData, ) -> ParseResult { let HafasJourney { date, recon, ctx_recon, sec_l, trf_res, } = data; let date = NaiveDate::parse_from_str(&date, "%Y%m%d")?; let lowest_price = trf_res .map(|x| x.fare_set_l) .unwrap_or_default() .into_iter() .flat_map(|x| x.fare_l) .filter_map(|x| x.price) .map(|x| x.amount) .filter(|x| *x > 0) .min() .map(|x| Price { currency: profile.price_currency().to_string(), amount: x as f64 / 100.0, }); let legs: Vec<_> = sec_l .into_iter() .filter_map(|x| profile.parse_leg(x, common, &date).transpose()) .filter(|l| { !l.as_ref() .is_ok_and(|l| l.walking && l.planned_departure == l.planned_arrival) }) // Filter out 0-minute walks; those must have been introduced mid-2024 to Hafas, but we don't know why. .collect::>()?; Ok(Journey { id: recon .and_then(|x| x.ctx) .or(ctx_recon) .unwrap_or_else(|| legs.iter().map(|l| l.id() + "|").collect()), legs, price: lowest_price, }) } railway-provider-hafas-0.1.2/src/parse/journeys_response.rs000064400000000000000000000020651046102023000222520ustar 00000000000000use crate::parse::common::HafasCommon; use crate::parse::journey::HafasJourney; use crate::ParseResult; use crate::Profile; use crate::TariffClass; use rcore::JourneysResponse; use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasJourneysResponse { out_ctx_scr_b: Option, out_ctx_scr_f: Option, out_con_l: Vec, common: HafasCommon, } pub(crate) fn default_parse_journeys_response( profile: &P, data: HafasJourneysResponse, tariff_class: TariffClass, ) -> ParseResult { let HafasJourneysResponse { out_ctx_scr_b, out_ctx_scr_f, out_con_l, common, } = data; let common_data = profile.parse_common(common, tariff_class)?; Ok(JourneysResponse { earlier_ref: out_ctx_scr_b, later_ref: out_ctx_scr_f, journeys: out_con_l .into_iter() .map(|x| profile.parse_journey(x, &common_data)) .collect::>()?, }) } railway-provider-hafas-0.1.2/src/parse/leg.rs000064400000000000000000000246031046102023000172270ustar 00000000000000use crate::parse::arrival_or_departure::{HafasArrivalOrDeparture, HafasPlatform}; use crate::parse::common::CommonData; use crate::parse::stopover::HafasStopover; use crate::ParseResult; use crate::Profile; use crate::{Frequency, Leg}; use chrono::{Duration, NaiveDate}; #[cfg(feature = "polylines")] use geojson::FeatureCollection; use rcore::{IntermediateLocation, Stop}; use serde::{Deserialize, Serialize}; #[cfg(feature = "polylines")] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasLegJnyPolyG { poly_x_l: Vec, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasLegJnyLoad { tcoc_x: Vec, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasLegFreq { min_c: Option, max_c: Option, num_c: Option, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasLegJny { jid: Option, is_rchbl: Option, dir_txt: Option, prod_x: Option, stop_l: Option>, msg_l: Option>, #[cfg(feature = "polylines")] poly_g: Option, d_trn_cmp_s_x: Option, freq: Option, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasLegJnyMsg { rem_x: Option, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasLegArr { a_t_z_offset: Option, a_time_s: Option, a_time_r: Option, a_platf_s: Option, a_platf_r: Option, a_pltf_s: Option, a_pltf_r: Option, a_cncl: Option, loc_x: usize, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasLegDep { d_t_z_offset: Option, d_time_s: Option, d_time_r: Option, d_platf_s: Option, d_platf_r: Option, d_pltf_s: Option, d_pltf_r: Option, d_cncl: Option, loc_x: usize, } /// See #[derive(Debug, Deserialize, Serialize, PartialEq, Eq)] pub enum HafasLegType { #[serde(rename = "JNY")] Journey, #[serde(rename = "WALK")] Walk, #[serde(rename = "TRSF")] Transfer, // TODO: Bike, KISS (Car), Park, Taxi #[serde(rename = "DEVI")] Deviation, #[serde(rename = "CHKI")] CheckIn, #[serde(rename = "TETA")] TeleTaxi, } #[derive(Debug, Deserialize)] pub struct HafasLegGis { dist: Option, } #[derive(Debug, Deserialize)] pub struct HafasLeg { dep: HafasLegDep, arr: HafasLegArr, jny: Option, gis: Option, r#type: HafasLegType, hide: Option, } pub(crate) fn default_parse_leg( profile: &P, data: HafasLeg, common: &CommonData, date: &NaiveDate, ) -> ParseResult> { let HafasLeg { mut dep, mut arr, jny, gis, r#type, hide, } = data; if let Some(true) = hide { return Ok(None); } let origin = common .places .get(dep.loc_x) .cloned() .ok_or_else(|| format!("Invalid place index: {}", arr.loc_x))? .ok_or_else(|| format!("Parse error place index: {}", arr.loc_x))?; let destination = common .places .get(arr.loc_x) .cloned() .ok_or_else(|| format!("Invalid place index: {}", arr.loc_x))? .ok_or_else(|| format!("Parse error place index: {}", arr.loc_x))?; if r#type == HafasLegType::Walk && dep.d_t_z_offset != arr.a_t_z_offset { if dep.d_t_z_offset == Some(0) { dep.d_t_z_offset = arr.a_t_z_offset; } if arr.a_t_z_offset == Some(0) { arr.a_t_z_offset = dep.d_t_z_offset; } } let dep = profile.parse_arrival_or_departure( HafasArrivalOrDeparture { t_z_offset: dep.d_t_z_offset, time_s: dep.d_time_s, time_r: dep.d_time_r, platf_s: dep.d_platf_s, platf_r: dep.d_platf_r, pltf_s: dep.d_pltf_s, pltf_r: dep.d_pltf_r, cncl: dep.d_cncl, }, date, )?; let arr = profile.parse_arrival_or_departure( HafasArrivalOrDeparture { t_z_offset: arr.a_t_z_offset, time_s: arr.a_time_s, time_r: arr.a_time_r, platf_s: arr.a_platf_s, platf_r: arr.a_platf_r, pltf_s: arr.a_pltf_s, pltf_r: arr.a_pltf_r, cncl: arr.a_cncl, }, date, )?; let mut cancelled = None; if let Some(true) = dep.cancelled { cancelled = Some(true); } if let Some(true) = arr.cancelled { cancelled = Some(true); } let mut line = None; let mut reachable = None; let mut trip_id = None; let mut direction = None; let mut stopovers: Option> = None; let mut load_factor = None; let mut remarks = None; #[cfg(feature = "polylines")] let mut polyline = None; let mut walking = None; let mut transfer = None; let mut distance = None; let mut frequency = None; match r#type { HafasLegType::Journey | HafasLegType::TeleTaxi => { let HafasLegJny { prod_x, is_rchbl, jid, dir_txt, stop_l, msg_l, #[cfg(feature = "polylines")] poly_g, d_trn_cmp_s_x, freq, } = jny.ok_or("Missing jny field")?; line = prod_x .map(|x| -> ParseResult<_> { Ok(common .lines .get(x) .cloned() .ok_or_else(|| format!("Invalid line index: {}", x))? .ok_or_else(|| format!("Parse error line index: {}", x))?) }) .transpose()?; reachable = is_rchbl; trip_id = jid; direction = dir_txt; stopovers = stop_l .map(|x| { x.into_iter() .map(|x| profile.parse_stopover(x, common, date)) .collect::>() }) .transpose()?; remarks = msg_l .map(|x| { x.into_iter() .filter_map(|x| x.rem_x) .filter_map(|x| { common .remarks .get(x) .cloned() .ok_or_else(|| format!("Invalid remark index: {}", x).into()) .transpose() }) .collect::>() }) .transpose()?; frequency = freq.map(|freq| Frequency { minimum: freq.min_c.map(|i| Duration::minutes(i as i64)), maximum: freq.max_c.map(|i| Duration::minutes(i as i64)), iterations: freq.num_c, }); #[cfg(feature = "polylines")] { polyline = poly_g .map(|poly_g| -> ParseResult<_> { let mut features = vec![]; for x in poly_g.poly_x_l { let mut polyline = common .polylines .get(x) .ok_or_else(|| format!("Invalid polyline index: {}", x))? .clone(); features.append(&mut polyline); } Ok(FeatureCollection { features, bbox: None, foreign_members: None, }) }) .transpose()?; } load_factor = d_trn_cmp_s_x .map(|x: HafasLegJnyLoad| -> ParseResult<_> { let mut entries = vec![]; for i in x.tcoc_x { entries.push( common .load_factors .get(i) .ok_or_else(|| format!("Invalid load factor index: {}", i))? .clone(), ); } Ok(entries .into_iter() .find(|x| x.class == common.tariff_class) .map(|x| x.load)) }) .transpose()? .and_then(|x| x); } HafasLegType::Walk => { walking = Some(true); distance = gis.and_then(|x| x.dist); } HafasLegType::Transfer | HafasLegType::Deviation => { transfer = Some(true); } HafasLegType::CheckIn => {} } Ok(Some(Leg { origin, destination, departure: dep.time.map(|t| t.with_timezone(&profile.timezone())), planned_departure: dep .planned_time .map(|t| t.with_timezone(&profile.timezone())), arrival: arr.time.map(|t| t.with_timezone(&profile.timezone())), planned_arrival: arr .planned_time .map(|t| t.with_timezone(&profile.timezone())), arrival_platform: arr.platform, planned_arrival_platform: arr.planned_platform, departure_platform: dep.platform, planned_departure_platform: dep.planned_platform, frequency, cancelled: cancelled.unwrap_or_default(), line, reachable: reachable.unwrap_or(true), trip_id, direction, intermediate_locations: stopovers .into_iter() .flat_map(|s| s.into_iter()) .map(IntermediateLocation::Stop) .collect(), load_factor, remarks: remarks.unwrap_or_default(), #[cfg(feature = "polylines")] polyline, walking: walking.unwrap_or_default(), transfer: transfer.unwrap_or_default(), distance, })) } railway-provider-hafas-0.1.2/src/parse/line.rs000064400000000000000000000023331046102023000174030ustar 00000000000000use crate::Line; use crate::Operator; use crate::ParseResult; use crate::Profile; use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasLineProdCtx { num: Option, cat_out: Option, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasLine { line: Option, add_name: Option, name: Option, prod_ctx: Option, opr_x: Option, cls: Option, } pub(crate) fn default_parse_line( profile: &P, data: HafasLine, operators: &[Operator], ) -> ParseResult { let HafasLine { line, add_name, name, prod_ctx, opr_x, cls, } = data; let product = profile.parse_product(cls.ok_or("Missing cls field")?)?; Ok(Line { name: line.or(add_name).or(name), fahrt_nr: prod_ctx.as_ref().and_then(|x| x.num.clone()), operator: opr_x.and_then(|x| operators.get(x)).cloned(), mode: product.mode.clone(), product_name: prod_ctx .and_then(|x| x.cat_out) .map(|s| s.trim().to_owned()), product: product.clone(), }) } railway-provider-hafas-0.1.2/src/parse/load_factor.rs000064400000000000000000000026431046102023000207350ustar 00000000000000use crate::LoadFactor; use crate::ParseResult; use crate::Profile; use crate::TariffClass; use serde::Deserialize; #[derive(Debug, Clone, Deserialize)] #[serde(rename_all = "UPPERCASE")] pub enum HafasTariffClass { First, Second, } impl From for TariffClass { fn from(h: HafasTariffClass) -> TariffClass { match h { HafasTariffClass::First => TariffClass::First, HafasTariffClass::Second => TariffClass::Second, } } } pub type HafasLoadFactor = u8; #[derive(Debug, Clone, Deserialize)] pub struct HafasLoadFactorEntry { c: HafasTariffClass, r: Option, } #[derive(Debug, Clone)] pub struct LoadFactorEntry { pub class: TariffClass, pub load: LoadFactor, } pub fn default_parse_load_factor_entry( profile: &P, h: HafasLoadFactorEntry, ) -> ParseResult> { if let Some(r) = h.r { Ok(Some(LoadFactorEntry { class: h.c.into(), load: profile.parse_load_factor(r)?, })) } else { Ok(None) } } pub fn default_parse_load_factor(h: HafasLoadFactor) -> ParseResult { match h { 1 => Ok(LoadFactor::LowToMedium), 2 => Ok(LoadFactor::High), 3 => Ok(LoadFactor::VeryHigh), 4 => Ok(LoadFactor::ExceptionallyHigh), _ => Err(format!("Invalid load factor: {}", h).into()), } } railway-provider-hafas-0.1.2/src/parse/location.rs000064400000000000000000000036061046102023000202700ustar 00000000000000use crate::Profile; use crate::{Location, ParseResult, Place, Station}; use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct HafasCoords { x: i64, y: i64, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasPlace { r#type: Option, name: String, crd: HafasCoords, ext_id: Option, p_cls: Option, } pub(crate) fn default_parse_coords(coords: HafasCoords) -> (f32, f32) { (coords.x as f32 / 1000000.0, coords.y as f32 / 1000000.0) } pub(crate) fn default_parse_place( profile: &P, data: HafasPlace, ) -> ParseResult { let HafasPlace { r#type, name, crd, ext_id, p_cls, } = data; let coords = profile.parse_coords(crd); match r#type.as_deref() { Some("S") => { let id = ext_id.ok_or("Missing ext_id")?; Ok(Place::Station(Station { id: id.clone(), name: Some(name), products: p_cls .map(|p_cls| profile.parse_products(p_cls).into_iter().cloned().collect()) .unwrap_or_default(), location: Some(Location::Point { id: Some(id), name: None, latitude: coords.0, longitude: coords.1, poi: None, }), })) } Some("P") => Ok(Place::Location(Location::Point { id: ext_id, name: Some(name), latitude: coords.0, longitude: coords.1, poi: Some(true), })), Some("A") => Ok(Place::Location(Location::Address { address: name, latitude: coords.0, longitude: coords.1, })), other => Err(format!("Unknown location type: {:?}", other).into()), } } railway-provider-hafas-0.1.2/src/parse/locations_response.rs000064400000000000000000000012661046102023000223710ustar 00000000000000use crate::parse::location::HafasPlace; use crate::ParseResult; use crate::Profile; use rcore::LocationsResponse; use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct HafasLocationsResponse { r#match: HafasLocationsResponseMatch, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasLocationsResponseMatch { loc_l: Vec, } pub(crate) fn default_parse_locations_response( profile: &P, data: HafasLocationsResponse, ) -> ParseResult { Ok(data .r#match .loc_l .into_iter() .filter_map(|p| profile.parse_place(p).ok()) .collect::>()) } railway-provider-hafas-0.1.2/src/parse/mod.rs000064400000000000000000000004741046102023000172370ustar 00000000000000pub mod arrival_or_departure; pub mod common; pub mod date; pub mod journey; pub mod journeys_response; pub mod leg; pub mod line; pub mod load_factor; pub mod location; pub mod locations_response; pub mod operator; #[cfg(feature = "polylines")] pub mod polyline; pub mod products; pub mod remark; pub mod stopover; railway-provider-hafas-0.1.2/src/parse/operator.rs000064400000000000000000000005341046102023000203100ustar 00000000000000use crate::Operator; use crate::ParseResult; use serde::Deserialize; #[derive(Debug, Deserialize)] pub struct HafasOperator { name: String, } pub(crate) fn default_parse_operator(data: HafasOperator) -> ParseResult { let HafasOperator { name } = data; Ok(Operator { id: name.clone(), // FIXME name, }) } railway-provider-hafas-0.1.2/src/parse/polyline.rs000064400000000000000000000015071046102023000203110ustar 00000000000000use crate::ParseResult; use geojson::{Feature, Geometry, Value}; use serde::Deserialize; //#[derive(Debug, Deserialize)] //#[serde(rename_all = "camelCase")] //pub struct HafasPolylineLocRef { // pp_idx: usize, // loc_x: usize, //} #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasPolyline { crd_enc_y_x: String, //pp_loc_ref_l: Vec, } pub(crate) fn default_parse_polyline(data: HafasPolyline) -> ParseResult> { let HafasPolyline { crd_enc_y_x, /*, pp_loc_ref_l*/ } = data; let coords = polyline::decode_polyline(&crd_enc_y_x, 5)?; let features = coords .into_points() .into_iter() .map(|point| Feature::from(Geometry::new(Value::Point(vec![point.x(), point.y()])))) .collect(); Ok(features) } railway-provider-hafas-0.1.2/src/parse/products.rs000064400000000000000000000011741046102023000203210ustar 00000000000000use crate::ParseResult; use crate::Product; pub(crate) fn default_parse_products<'a>( p_cls: u16, products: &'a [&'a Product], ) -> Vec<&'a Product> { let mut result = vec![]; for (i, p) in products.iter().enumerate() { if (1 << i) & p_cls != 0 { result.push(*p); } } result } pub(crate) fn default_parse_product<'a>( p_cls: u16, products: &'a [&'a Product], ) -> ParseResult<&'a Product> { for (i, p) in products.iter().enumerate() { if (1 << i) == p_cls { return Ok(p); } } Err(format!("Unknown product bit: {:b}", p_cls).into()) } railway-provider-hafas-0.1.2/src/parse/remark.rs000064400000000000000000000034161046102023000177400ustar 00000000000000use crate::ParseResult; use crate::Remark; use crate::RemarkType; use rcore::RemarkAssociation; use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasRemark { r#type: Option, txt_s: Option, txt_n: Option, code: Option, jid: Option, } pub fn default_parse_remark RemarkAssociation>( rem: HafasRemark, association: F, ) -> ParseResult { let association = rem .code .as_ref() .map(|c| association(c)) .unwrap_or_default(); Ok(match rem.r#type.as_deref() { Some("M") | Some("P") => Remark { r#type: RemarkType::Status, code: rem.code.ok_or("Missing code")?, text: rem.txt_n.ok_or("Missing remark text")?, trip_id: None, summary: rem.txt_s, association, }, Some("L") => Remark { r#type: RemarkType::Status, code: "alternative-trip".to_string(), text: rem.txt_n.ok_or("Missing remark text")?, trip_id: rem.jid, summary: None, association, }, Some("A") | Some("I") | Some("H") => Remark { r#type: RemarkType::Hint, code: rem.code.ok_or("Missing code")?, text: rem.txt_n.ok_or("Missing remark text")?, trip_id: None, summary: None, association, }, _ => Remark { // TODO: parse more accurately r#type: RemarkType::Status, code: rem.code.ok_or("Missing code")?, text: rem.txt_n.ok_or("Missing remark text")?, trip_id: None, summary: None, association, }, }) } railway-provider-hafas-0.1.2/src/parse/stopover.rs000064400000000000000000000073261046102023000203440ustar 00000000000000use crate::parse::arrival_or_departure::{HafasArrivalOrDeparture, HafasPlatform}; use crate::parse::common::CommonData; use crate::ParseResult; use crate::Profile; use crate::Stop; use chrono::NaiveDate; use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasStopover { loc_x: usize, a_t_z_offset: Option, a_time_s: Option, a_time_r: Option, a_platf_s: Option, a_platf_r: Option, a_pltf_s: Option, a_pltf_r: Option, a_cncl: Option, d_t_z_offset: Option, d_time_s: Option, d_time_r: Option, d_platf_s: Option, d_platf_r: Option, d_pltf_s: Option, d_pltf_r: Option, d_cncl: Option, msg_l: Option>, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HafasStopoverMsg { rem_x: usize, } pub(crate) fn default_parse_stopover( profile: &P, data: HafasStopover, common: &CommonData, date: &NaiveDate, ) -> ParseResult { let HafasStopover { loc_x, a_t_z_offset, a_time_s, a_time_r, a_platf_s, a_platf_r, a_pltf_s, a_pltf_r, a_cncl, d_t_z_offset, d_time_s, d_time_r, d_platf_s, d_platf_r, d_pltf_s, d_pltf_r, d_cncl, msg_l, } = data; let stop = common .places .get(loc_x) .and_then(|x| x.clone()) .ok_or_else(|| format!("Invalid place index {}", loc_x))?; let dep = profile.parse_arrival_or_departure( HafasArrivalOrDeparture { t_z_offset: d_t_z_offset, time_s: d_time_s, time_r: d_time_r, platf_s: d_platf_s, platf_r: d_platf_r, pltf_s: d_pltf_s, pltf_r: d_pltf_r, cncl: d_cncl, }, date, )?; let arr = profile.parse_arrival_or_departure( HafasArrivalOrDeparture { t_z_offset: a_t_z_offset, time_s: a_time_s, time_r: a_time_r, platf_s: a_platf_s, platf_r: a_platf_r, pltf_s: a_pltf_s, pltf_r: a_pltf_r, cncl: a_cncl, }, date, )?; let mut cancelled = None; if let Some(true) = dep.cancelled { cancelled = Some(true); } if let Some(true) = arr.cancelled { cancelled = Some(true); } let remarks = msg_l .map(|x| { x.into_iter() .filter_map(|x| { common .remarks .get(x.rem_x) .cloned() .ok_or_else(|| format!("Invalid remark index: {}", x.rem_x).into()) .transpose() }) .collect::>() }) .transpose()?; Ok(Stop { place: stop, departure: dep.time.map(|t| t.with_timezone(&profile.timezone())), planned_departure: dep .planned_time .map(|t| t.with_timezone(&profile.timezone())), arrival: arr.time.map(|t| t.with_timezone(&profile.timezone())), planned_arrival: arr .planned_time .map(|t| t.with_timezone(&profile.timezone())), arrival_platform: arr.platform, planned_arrival_platform: arr.planned_platform, departure_platform: dep.platform, planned_departure_platform: dep.planned_platform, cancelled: cancelled.unwrap_or_default(), remarks: remarks.unwrap_or_default(), }) } railway-provider-hafas-0.1.2/src/profile/avv.rs000064400000000000000000000072421046102023000176020ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const REGIONALZUG: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Regionalzug"), short: Cow::Borrowed("Regionalzug"), }; pub const FERNZUG: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("Fernzug"), short: Cow::Borrowed("Fernzug"), }; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("ICE/Thalys"), short: Cow::Borrowed("ICE/Thalys"), }; pub const FERNBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Fernbus"), short: Cow::Borrowed("Fernbus"), }; pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("S-Bahn"), short: Cow::Borrowed("S"), }; pub const U: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U"), }; pub const STRASSENBAHN: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Straßenbahn"), short: Cow::Borrowed("Straßenbahn"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const BUS_V: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus, Verstärkerfahrt"), short: Cow::Borrowed("Bus V"), }; pub const BEDARFSVERKEHR: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Bedarfsverkehr"), short: Cow::Borrowed("Bedarfsverkehr"), }; pub const FAEHRE: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Fähre"), short: Cow::Borrowed("Fähre"), }; pub const PRODUCTS: &[&Product] = &[ ®IONALZUG, &FERNZUG, &ICE, &FERNBUS, &S, &U, &STRASSENBAHN, &BUS, &BUS_V, &BEDARFSVERKEHR, &FAEHRE, ]; } #[derive(Debug)] pub struct AvvProfile; impl Profile for AvvProfile { fn url(&self) -> &'static str { "https://auskunft.avv.de/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn checksum_salt(&self) -> Option<&'static str> { None } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"WEB","id":"AVV_AACHEN","v":"","name":"webapp"}); req_json["ver"] = json!("1.26"); req_json["auth"] = json!({"type":"AID","aid":"4vV1AcH3N511icH"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search( AvvProfile {}, "Bayrische Str", "Dortmund, Eving Bayrische Straße", ) .await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(AvvProfile {}, "1057", "1397").await } } railway-provider-hafas-0.1.2/src/profile/bart.rs000064400000000000000000000057411046102023000177400ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const CABLE_CAR: Product = Product { mode: Mode::Cablecar, name: Cow::Borrowed("Cable car"), short: Cow::Borrowed("Cable car"), }; pub const REGIONAL_TRAIN: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Regional trains (Caltrain, Capitol Corridor, ACE)"), short: Cow::Borrowed("regional trains"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const FERRY: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Ferry"), short: Cow::Borrowed("Ferry"), }; pub const BART: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("BART"), short: Cow::Borrowed("BART"), }; pub const TRAM: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Tram"), short: Cow::Borrowed("Tram"), }; pub const PRODUCTS: &[&Product] = &[ &Product::unknown(), &Product::unknown(), &CABLE_CAR, ®IONAL_TRAIN, &Product::unknown(), &BUS, &FERRY, &BART, &TRAM, ]; } #[derive(Debug)] pub struct BartProfile; impl Profile for BartProfile { fn url(&self) -> &'static str { "https://planner.bart.gov/bin/mgate.exe" } fn language(&self) -> &'static str { "en" } fn checksum_salt(&self) -> Option<&'static str> { None } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::America::Los_Angeles } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({ "type": "WEB", "id": "BART", "name": "webapp" }); req_json["ver"] = json!("1.40"); req_json["auth"] = json!({ "type": "AID", "aid": "kEwHkFUCIL500dym" }); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "USD" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search( BartProfile {}, "Rye, San Francisco", "Rye Bar, San Francisco", ) .await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(BartProfile {}, "100013296", "100013295").await } } railway-provider-hafas-0.1.2/src/profile/bls.rs000064400000000000000000000067141046102023000175710ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("ICE"), short: Cow::Borrowed("ICE"), }; pub const IC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("IC/EC"), short: Cow::Borrowed("IC/EC"), }; pub const IR: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("IR"), short: Cow::Borrowed("IR"), }; pub const NAHVERKEHR: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Nahverkehr"), short: Cow::Borrowed("Nahverkehr"), }; pub const SCHIFF: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Schiff"), short: Cow::Borrowed("Schiff"), }; pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("S-Bahn"), short: Cow::Borrowed("S"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const SEILBAHN: Product = Product { mode: Mode::Cablecar, name: Cow::Borrowed("Seilbahn"), short: Cow::Borrowed("Seilbahn"), }; pub const TRAM: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Tram"), short: Cow::Borrowed("Tram"), }; pub const AUTOVERLAD: Product = Product { // TODO: Correct? mode: Mode::RegionalTrain, name: Cow::Borrowed("Autoverlad"), short: Cow::Borrowed("Autoverlad"), }; pub const PRODUCTS: &[&Product] = &[ &ICE, &IC, &IR, &NAHVERKEHR, &SCHIFF, &S, &BUS, &SEILBAHN, &Product::unknown(), &Product::unknown(), &TRAM, &Product::unknown(), &AUTOVERLAD, ]; } #[derive(Debug)] pub struct BlsProfile; impl Profile for BlsProfile { fn url(&self) -> &'static str { "https://bls.hafas.de/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn checksum_salt(&self) -> Option<&'static str> { None } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"WEB","id":"HAFAS","v":"","name":"webapp"}); req_json["ver"] = json!("1.46"); req_json["auth"] = json!({"type":"AID","aid":"3jkAncud78HSoqclmN54812A"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(BlsProfile {}, "Bayr", "Bayon").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(BlsProfile {}, "8590093", "8578932").await } } railway-provider-hafas-0.1.2/src/profile/cfl.rs000064400000000000000000000050741046102023000175530ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const TGV: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("TGV, ICE, EuroCity"), short: Cow::Borrowed("TGV/ICE/EC"), }; pub const LOCAL: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("local trains"), short: Cow::Borrowed("local"), }; pub const TRAM: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("tram"), short: Cow::Borrowed("tram"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("bus"), short: Cow::Borrowed("bus"), }; pub const FUN: Product = Product { mode: Mode::Cablecar, name: Cow::Borrowed("Fun"), short: Cow::Borrowed("Fun"), }; pub const PRODUCTS: &[&Product] = &[ &TGV, &TGV, &Product::unknown(), &LOCAL, &LOCAL, &BUS, &Product::unknown(), &Product::unknown(), &TRAM, &FUN, ]; } #[derive(Debug)] pub struct CflProfile; impl Profile for CflProfile { fn url(&self) -> &'static str { "https://horaires.cfl.lu/bin/mgate.exe" } fn language(&self) -> &'static str { "fr" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Luxembourg } fn checksum_salt(&self) -> Option<&'static str> { None } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"IPH","id":"HAFAS","v":"4000000","name":"cflPROD-STORE"}); req_json["ver"] = json!("1.43"); req_json["auth"] = json!({"type":"AID","aid":"ALT2vl7LAFDFu2dz"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(CflProfile {}, "Pari", "PARIS NORD (France)").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(CflProfile {}, "9864348", "8800003").await } } railway-provider-hafas-0.1.2/src/profile/cmta.rs000064400000000000000000000045541046102023000177350ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const B: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("MetroBus"), short: Cow::Borrowed("B"), }; pub const R: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("MetroRapid"), short: Cow::Borrowed("R"), }; pub const M: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("MetroRail"), short: Cow::Borrowed("M"), }; pub const PRODUCTS: &[&Product] = &[ &Product::unknown(), &Product::unknown(), &Product::unknown(), &M, &Product::unknown(), &B, &Product::unknown(), &Product::unknown(), &Product::unknown(), &Product::unknown(), &Product::unknown(), &Product::unknown(), &R, ]; } #[derive(Debug)] pub struct CmtaProfile; impl Profile for CmtaProfile { fn url(&self) -> &'static str { "https://capmetro.hafas.cloud/bin/mgate.exe" } fn language(&self) -> &'static str { "en" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::America::Chicago } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"IPH","id":"CMTA","v":"2","name":"CapMetro"}); req_json["ver"] = json!("1.40"); req_json["auth"] = json!({"type":"AID","aid":"ioslaskdcndrjcmlsd"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "USD" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(CmtaProfile {}, "Plaza Salti", "Plaza Saltillo").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(CmtaProfile {}, "000002370", "000005919").await } } railway-provider-hafas-0.1.2/src/profile/custom-certificates/README.md000064400000000000000000000003641046102023000236720ustar 00000000000000# Custom Certificates Some providers require the usage of custom certificates to communicate with their servers. This folder contains those certificates. The source of those certificates is . railway-provider-hafas-0.1.2/src/profile/custom-certificates/ivb.crt.pem000064400000000000000000000126121046102023000244640ustar 00000000000000-----BEGIN CERTIFICATE----- MIIG3zCCBcegAwIBAgIQA4jrkiZxv246m64Ihrcs5zANBgkqhkiG9w0BAQsFADBZ MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMTMwMQYDVQQDEypE aWdpQ2VydCBHbG9iYWwgRzIgVExTIFJTQSBTSEEyNTYgMjAyMCBDQTEwHhcNMjMw NDIwMDAwMDAwWhcNMjQwNTIwMjM1OTU5WjB1MQswCQYDVQQGEwJBVDEOMAwGA1UE CBMFVGlyb2wxEjAQBgNVBAcTCUlubnNicnVjazEoMCYGA1UEChMfSW5uc2JydWNr ZXIgS29tbXVuYWxiZXRyaWViZSBBRzEYMBYGA1UEAxMPZmFocnBsYW4uaXZiLmF0 MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwYmSrXUIcYCIqpPLZiqx Kt3cNYtrGGXRx78tiuGUrUwvaIVAWADUkmCw4lObUU9uPVwBrz6lBfbLRTBLV4vm lji9QN6gefH1niBWSAZsNg7g+oJTscy+MyAriZ5AxEObInFsB1TDj/KoJSwmskMB Ys2A0GDQF0vxtvUhIUDbS1cM5c0PKxMn2ZMCrlia4JVp/UvH4fY4b4GqV9ARGg9b fC5+b+8sf11YgNMQtv/9ybUrKXbLUoW3IkgDBCd8pu8lSHK2J4Go/aJaqtQxlypr BOzNeMJiakFcQRy39XYewn1QuQIp+ffwdbA7bPJf6ivQ0AgmAUmX08ejAYnPyTgm tQIDAQABo4IDhTCCA4EwHwYDVR0jBBgwFoAUdIWAwGbH3zfez70pN6oDHb7tzRcw HQYDVR0OBBYEFDNdNH+vHib5MyGaMvsr7sHCQNfVMBoGA1UdEQQTMBGCD2ZhaHJw bGFuLml2Yi5hdDAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMIGfBgNVHR8EgZcwgZQwSKBGoESGQmh0dHA6Ly9jcmwzLmRpZ2lj ZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbEcyVExTUlNBU0hBMjU2MjAyMENBMS0xLmNy bDBIoEagRIZCaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFs RzJUTFNSU0FTSEEyNTYyMDIwQ0ExLTEuY3JsMD4GA1UdIAQ3MDUwMwYGZ4EMAQIC MCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzCBhwYI KwYBBQUHAQEEezB5MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j b20wUQYIKwYBBQUHMAKGRWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp Q2VydEdsb2JhbEcyVExTUlNBU0hBMjU2MjAyMENBMS0xLmNydDAJBgNVHRMEAjAA MIIBewYKKwYBBAHWeQIEAgSCAWsEggFnAWUAdQDuzdBk1dsazsVct520zROiModG fLzs3sNRSFlGcR+1mwAAAYefRP/9AAAEAwBGMEQCIFjeLHYIYiJPLIxFR15tBOR5 dDWoBqnN5yh4gWkFZWYrAiA4VD1tijI+LZA4vHRo+n3x06S7xrkFLj0CHAnYwTF2 FwB1AHPZnokbTJZ4oCB9R53mssYc0FFecRkqjGuAEHrBd3K1AAABh59FABUAAAQD AEYwRAIgWYNY8mjOvJRwtISFw0h5difwB+GanCeY7vs7s1eIbP0CIBz+1RMDncjK CBgaQ9A0r3VtIRSZGpZXYgtNLlwAm2QMAHUASLDja9qmRzQP5WoC+p0w6xxSActW 3SyB2bu/qznYhHMAAAGHn0T/2gAABAMARjBEAiBqWfo/fIUsxsbAxZ+nV3telcqV UdI0VzsVdQyDOqjFxAIgco7BJJsS98sTeQa9MgTUaV5ehsHm1pHTO91d5SWU6cMw DQYJKoZIhvcNAQELBQADggEBAFXJiVrfiED+qd1J8RUI6FG72VlOk8AEAW9aCxwI Ik+9whPOLy7v6zoPLdIoWLSZSRao/RE79/Kj7z4IsaYS03SlwalcjDeHkatgnql2 WUEtnj4KuDVADqLbKCRc38pVb2BZ7n9tQDk20VLdSK+tDbxCPWiFQTLPDGLe8ugZ pXzXHv53jhy+2Im+hgZ0QDlFz9quV5G/7MJLfnZUgRgVWdhcoU5vzzhHZUIr98Lr K9gD/8kdEa/eF2Px7THbV1uBb+TbGLBrOGhymsU9UNs+Zo48uE7mpkSCNFOptPVY kxhTLfkHhQr7DJyWO3VRSzCkZZNuo4CeU+tyD7wTNXfZb98= -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIE9DCCA9ygAwIBAgIQCF+UwC2Fe+jMFP9T7aI+KjANBgkqhkiG9w0BAQsFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH MjAeFw0yMDA5MjQwMDAwMDBaFw0zMDA5MjMyMzU5NTlaMFkxCzAJBgNVBAYTAlVT MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMzAxBgNVBAMTKkRpZ2lDZXJ0IEdsb2Jh bCBHMiBUTFMgUlNBIFNIQTI1NiAyMDIwIENBMTCCASIwDQYJKoZIhvcNAQEBBQAD ggEPADCCAQoCggEBAMz3EGJPprtjb+2QUlbFbSd7ehJWivH0+dbn4Y+9lavyYEEV cNsSAPonCrVXOFt9slGTcZUOakGUWzUb+nv6u8W+JDD+Vu/E832X4xT1FE3LpxDy FuqrIvAxIhFhaZAmunjZlx/jfWardUSVc8is/+9dCopZQ+GssjoP80j812s3wWPc 3kbW20X+fSP9kOhRBx5Ro1/tSUZUfyyIxfQTnJcVPAPooTncaQwywa8WV0yUR0J8 osicfebUTVSvQpmowQTCd5zWSOTOEeAqgJnwQ3DPP3Zr0UxJqyRewg2C/Uaoq2yT zGJSQnWS+Jr6Xl6ysGHlHx+5fwmY6D36g39HaaECAwEAAaOCAa4wggGqMB0GA1Ud DgQWBBR0hYDAZsffN97PvSk3qgMdvu3NFzAfBgNVHSMEGDAWgBROIlQgGJXm427m D/r6uRLtBhePOTAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwEG CCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8CAQAwdgYIKwYBBQUHAQEEajBoMCQG CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQAYIKwYBBQUHMAKG NGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RH Mi5jcnQwewYDVR0fBHQwcjA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQuY29t L0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDA3oDWgM4YxaHR0cDovL2NybDQuZGln aWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDAwBgNVHSAEKTAnMAcG BWeBDAEBMAgGBmeBDAECATAIBgZngQwBAgIwCAYGZ4EMAQIDMA0GCSqGSIb3DQEB CwUAA4IBAQB1i8A8W+//cFxrivUh76wx5kM9gK/XVakew44YbHnT96xC34+IxZ20 dfPJCP2K/lHz8p0gGgQ1zvi2QXmv/8yWXpTTmh1wLqIxi/ulzH9W3xc3l7/BjUOG q4xmfrnti/EPjLXUVa9ciZ7gpyptsqNjMhg7y961n4OzEQGsIA2QlxK3KZw1tdeR Du9Ab21cO72h2fviyy52QNI6uyy/FgvqvQNbTpg6Ku0FUAcVkzxzOZGUWkgOxtNK Aa9mObm9QjQc2wgD80D8EuiuPKuK1ftyeWSm4w5VsTuVP61gM2eKrLanXPDtWlIb 1GHhJRLmB7WqlLLwKPZhJl5VHPgB63dx -----END CERTIFICATE----- -----BEGIN CERTIFICATE----- MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI 2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx 1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV 5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY 1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl MrY= -----END CERTIFICATE----- railway-provider-hafas-0.1.2/src/profile/dart.rs000064400000000000000000000040461046102023000177370ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const PRODUCTS: &[&Product] = &[ &Product::unknown(), &Product::unknown(), &Product::unknown(), &Product::unknown(), &Product::unknown(), &BUS, ]; } #[derive(Debug)] pub struct DartProfile; impl Profile for DartProfile { fn url(&self) -> &'static str { "https://dart.hafas.de/bin/mgate.exe" } fn language(&self) -> &'static str { "en" } fn checksum_salt(&self) -> Option<&'static str> { None } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::America::Chicago } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({ "type": "WEB", "id": "BART", "name": "webapp" }); req_json["ver"] = json!("1.35"); req_json["auth"] = json!({ "type": "AID", "aid": "XNFGL2aSkxfDeK8N4waOZnsdJ" }); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "USD" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(DartProfile {}, "Main", "MAIN AVE/5TH ST").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(DartProfile {}, "100002702", "100004972").await } } railway-provider-hafas-0.1.2/src/profile/db.rs000064400000000000000000000126101046102023000173660ustar 00000000000000use crate::{Product, Profile}; use rcore::{Age, RemarkAssociation}; use serde_json::{json, Value}; use std::collections::HashMap; // TODO: parse Ausstattung, Öffnungszeiten, LocWithDetais, LoadFactors, LineWithAdditionalName, // JourneyWithPrice, Hints, Codes, IBNR // TODO: transform JourneysQuery mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCityExpress"), short: Cow::Borrowed("ICE"), }; pub const IC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCity & EuroCity"), short: Cow::Borrowed("IC/EC"), }; pub const RE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("RegionalExpress & InterRegio"), short: Cow::Borrowed("RE/IR"), }; pub const RB: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Regio"), short: Cow::Borrowed("RB"), }; pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("S-Bahn"), short: Cow::Borrowed("S"), }; pub const B: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("B"), }; pub const F: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Ferry"), short: Cow::Borrowed("F"), }; pub const U: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U"), }; pub const T: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Tram"), short: Cow::Borrowed("T"), }; pub const TAXI: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Group Taxi"), short: Cow::Borrowed("Taxi"), }; pub const PRODUCTS: &[&Product] = &[&ICE, &IC, &RE, &RB, &S, &B, &F, &U, &T, &TAXI]; } #[derive(Debug)] pub struct DbProfile; impl Profile for DbProfile { fn url(&self) -> &'static str { "https://reiseauskunft.bahn.de/bin/mgate.exe" } fn checksum_salt(&self) -> Option<&'static str> { Some("bdI8UVj40K5fvxwf") } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn salt(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["svcReqL"][0]["cfg"]["rtMode"] = json!("HYBRID"); req_json["client"] = json!({ "id": "DB", "v": "19040000", "type": "IPH", "name": "DB Navigator" }); req_json["ext"] = json!("DB.R20.12.b"); req_json["ver"] = json!("1.34"); req_json["auth"] = json!({ "type": "AID", "aid": "n91dB8Z77MLdoR0K" }); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "hafas-rs"); } fn price_currency(&self) -> &'static str { "EUR" } fn remark_association(&self, code: &str) -> RemarkAssociation { match code { // Bikes limited "FB" | // Bike free "KF" | // Bike times limited "FS" => RemarkAssociation::Bike, // Places for wheelchairs "RO" | // Accessible equipment "RG" | "EA" | "ER" | // Ramp for wheelchairs "EH" | // Boarding aid at center of train "ZM" | // Accessible only at limited stations "SI" => RemarkAssociation::Accessibility, // Ticket machine in train "FM" | "FZ" | // Reservation upfront at service points and vending machines possible "RC" => RemarkAssociation::Ticket, // Power sockers "LS" => RemarkAssociation::Ticket, // Air conditioning "KL" => RemarkAssociation::AirConditioning, // WiFi "WV" => RemarkAssociation::WiFi, // Only second class "K2" => RemarkAssociation::OnlySecondClass, // Hamburg mobility info link "HM" | // Schleswig-Holstein mobility / accesibility info link "SM" | // RRX Rhein-Ruhr-Express "N " | // Not specified "" => RemarkAssociation::None, _ => RemarkAssociation::Unknown } } fn age_to_hafas(&self, age: Age) -> &'static str { match age.0 { 0..=5 => "B", 6..=14 => "K", 15..=26 => "E", 27..=64 => "E", // using "Y" currently yields an error in HAFAS API 65.. => "E", // using "Y" currently yields an error in HAFAS API } } } #[cfg(test)] mod test { use crate::profile::test::{check_journey, check_search}; use std::error::Error; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(DbProfile {}, "Bayr", "Bayreuth Hbf").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(DbProfile {}, "8011167", "8000261").await } } railway-provider-hafas-0.1.2/src/profile/insa.rs000064400000000000000000000055451046102023000177440ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCityExpress"), short: Cow::Borrowed("ICE"), }; pub const IC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCity & EuroCity"), short: Cow::Borrowed("IC/EC"), }; pub const RE: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("RegionalExpress & RegionalBahn"), short: Cow::Borrowed("RE/RB"), }; pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("S-Bahn"), short: Cow::Borrowed("S"), }; pub const T: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Tram"), short: Cow::Borrowed("T"), }; pub const B: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("B"), }; pub const TT: Product = Product { // TODO: Correct? mode: Mode::RegionalTrain, name: Cow::Borrowed("Tourism Train"), short: Cow::Borrowed("TT"), }; pub const PRODUCTS: &[&Product] = &[&ICE, &IC, &Product::unknown(), &RE, &S, &T, &B, &B, &TT]; } #[derive(Debug)] pub struct InsaProfile; impl Profile for InsaProfile { fn url(&self) -> &'static str { "https://reiseauskunft.insa.de/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({ "type": "IPH", "id": "NASA", "v": "4000200", "name": "nasaPROD" }); req_json["ver"] = json!("1.44"); req_json["auth"] = json!({ "type": "AID", "aid": "nasa-apps" }); } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(InsaProfile {}, "Main", "Mainz Hbf").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(InsaProfile {}, "8000240", "8000105").await } } railway-provider-hafas-0.1.2/src/profile/irish_rail.rs000064400000000000000000000053211046102023000211270ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const IC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCity"), short: Cow::Borrowed("IC"), }; pub const COMMUTER: Product = Product { // TODO: Correct? mode: Mode::HighSpeedTrain, name: Cow::Borrowed("Commuter"), short: Cow::Borrowed("Commuter"), }; pub const DART: Product = Product { // TODO: Correct? mode: Mode::RegionalTrain, name: Cow::Borrowed("Dublin Area Rapid Transit"), short: Cow::Borrowed("DART"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const LUAS: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("LUAS Tram"), short: Cow::Borrowed("LUAS"), }; pub const PRODUCTS: &[&Product] = &[ &Product::unknown(), &IC, &Product::unknown(), &COMMUTER, &DART, &BUS, &LUAS, ]; } #[derive(Debug)] pub struct IrishRailProfile; impl Profile for IrishRailProfile { fn url(&self) -> &'static str { "https://journeyplanner.irishrail.ie/bin/mgate.exe" } fn checksum_salt(&self) -> Option<&'static str> { Some("i5s7m3q9z6b4k1c2") } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Dublin } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn mic_mac(&self) -> bool { true } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({ "type": "IPA", "id": "IRISHRAIL", "v": "4000100", "name": "IrishRailPROD-APPSTORE" }); req_json["ver"] = json!("1.33"); req_json["auth"] = json!({ "type": "AID", "aid": "P9bplgVCGnozdgQE" }); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(IrishRailProfile {}, "Ske", "Skerries").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(IrishRailProfile {}, "9909002", "9990840").await } } railway-provider-hafas-0.1.2/src/profile/ivb.rs000064400000000000000000000060721046102023000175660ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; // TODO pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("S-Bahn"), short: Cow::Borrowed("S"), }; pub const STADTBAHN: Product = Product { // TODO: Maybe suburban? mode: Mode::RegionalTrain, name: Cow::Borrowed("Stadtbahn"), short: Cow::Borrowed("Stadtbahn"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const TAXIBUS: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Taxibus"), short: Cow::Borrowed("Taxibus"), }; // TODO: This is bitmask 46 for some reason. Why? // pub const REGIONAL: Product = Product { // mode: Mode::RegionalTrain, // name: Cow::Borrowed("Regionalverkehr"), // short: Cow::Borrowed("Regionalverkehr"), // }; pub const FERN: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("Fernverkehr"), short: Cow::Borrowed("Fernverkehr"), }; pub const PRODUCTS: &[&Product] = &[ &S, &STADTBAHN, &Product::unknown(), &BUS, &Product::unknown(), &FERN, &Product::unknown(), &Product::unknown(), &TAXIBUS, ]; } #[derive(Debug)] pub struct IvbProfile; impl Profile for IvbProfile { fn url(&self) -> &'static str { "https://fahrplan.ivb.at/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Zurich } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({ "type": "WEB", "id": "VAO", "name": "webapp", }); req_json["ver"] = json!("1.32"); req_json["auth"] = json!({ "type": "AID", "aid": "wf7mcf9bv3nv8g5f" }); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } fn custom_pem_bundle(&self) -> Option<&'static [u8]> { Some(include_bytes!("./custom-certificates/ivb.crt.pem")) } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(IvbProfile {}, "Hauptbahn", "Wien Hauptbahnhof").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(IvbProfile {}, "490134900", "470118700").await } } railway-provider-hafas-0.1.2/src/profile/kvb.rs000064400000000000000000000067151046102023000175740ustar 00000000000000use crate::{ParseResult, Product, Profile}; use rcore::LoadFactor; use serde_json::{json, Value}; use std::collections::HashMap; use super::HafasLoadFactor; mod products { use crate::{Mode, Product}; use std::borrow::Cow; // TODO pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("S-Bahn"), short: Cow::Borrowed("S"), }; pub const STADTBAHN: Product = Product { // TODO: Maybe suburban? mode: Mode::RegionalTrain, name: Cow::Borrowed("Stadtbahn"), short: Cow::Borrowed("Stadtbahn"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const TAXIBUS: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Taxibus"), short: Cow::Borrowed("Taxibus"), }; // TODO: This is bitmask 46 for some reason. Why? // pub const REGIONAL: Product = Product { // mode: Mode::RegionalTrain, // name: Cow::Borrowed("Regionalverkehr"), // short: Cow::Borrowed("Regionalverkehr"), // }; pub const FERN: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("Fernverkehr"), short: Cow::Borrowed("Fernverkehr"), }; pub const PRODUCTS: &[&Product] = &[ &S, &STADTBAHN, &Product::unknown(), &BUS, &Product::unknown(), &FERN, &Product::unknown(), &Product::unknown(), &TAXIBUS, ]; } #[derive(Debug)] pub struct KvbProfile; impl Profile for KvbProfile { fn url(&self) -> &'static str { "https://auskunft.kvb.koeln/gate" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({ "type": "WEB", "id": "HAFAS", "name": "webapp", "l": "vs_webapp" }); req_json["ver"] = json!("1.42"); req_json["auth"] = json!({ "type": "AID", "aid": "Rt6foY5zcTTRXMQs" }); } fn parse_load_factor(&self, h: HafasLoadFactor) -> ParseResult { // TODO: Load factors correct? match h { 10 => Ok(LoadFactor::LowToMedium), 11 => Ok(LoadFactor::High), 12 => Ok(LoadFactor::VeryHigh), 13 => Ok(LoadFactor::ExceptionallyHigh), 5 => Ok(LoadFactor::LowToMedium), _ => Err(format!("Invalid load factor: {}", h).into()), } } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(KvbProfile {}, "Hauptbahn", "Köln Hbf").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(KvbProfile {}, "900593000", "900000687").await } } railway-provider-hafas-0.1.2/src/profile/mobil_nrw.rs000064400000000000000000000065221046102023000207760ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const REGIONAL_TRAIN: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("regional train"), short: Cow::Borrowed("regional train"), }; pub const URBAN_TRAIN: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("urban train"), short: Cow::Borrowed("urban train"), }; pub const SUBWAY: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("subway"), short: Cow::Borrowed("subway"), }; pub const TRAM: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("tram"), short: Cow::Borrowed("tram"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("bus"), short: Cow::Borrowed("bus"), }; pub const DIAL_A_RIDE: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("dial-a-ride"), short: Cow::Borrowed("dial-a-ride"), }; pub const LONG_DISTANCE_TRAIN: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("long-distance train"), short: Cow::Borrowed("long-distance train"), }; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("ICE"), short: Cow::Borrowed("ICE"), }; pub const EC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("EC/IC"), short: Cow::Borrowed("EC/IC"), }; pub const PRODUCTS: &[&Product] = &[ &ICE, &EC, &LONG_DISTANCE_TRAIN, ®IONAL_TRAIN, &URBAN_TRAIN, &BUS, &Product::unknown(), &SUBWAY, &TRAM, &DIAL_A_RIDE, ]; } #[derive(Debug)] pub struct MobilNrwProfile; impl Profile for MobilNrwProfile { fn url(&self) -> &'static str { "https://nrw.hafas.de/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn checksum_salt(&self) -> Option<&'static str> { None } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"IPH","id":"DB-REGIO-NRW","v":"6000300","name":"NRW"}); req_json["ver"] = json!("1.34"); req_json["auth"] = json!({"type":"AID","aid":"Kdf0LNRWYg5k3499"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(MobilNrwProfile {}, "Kreu", "Kreuztal").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(MobilNrwProfile {}, "8000076", "8000001").await } } railway-provider-hafas-0.1.2/src/profile/mobiliteit_lu.rs000064400000000000000000000053521046102023000216470ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const TGV: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("local train (TGV/ICE)"), short: Cow::Borrowed("TGV/ICE"), }; pub const IC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("national train (IC/RE/IRE)"), short: Cow::Borrowed("IC/RE/IRE"), }; pub const RB: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("local train (RB/TER)"), short: Cow::Borrowed("RB/TER"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const TRAM: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Tram"), short: Cow::Borrowed("Tram"), }; pub const PRODUCTS: &[&Product] = &[ &TGV, &IC, &IC, &RB, &Product::unknown(), &BUS, &Product::unknown(), &Product::unknown(), &TRAM, ]; } #[derive(Debug)] pub struct MobiliteitLuProfile; impl Profile for MobiliteitLuProfile { fn url(&self) -> &'static str { "https://cdt.hafas.de/gate" } fn language(&self) -> &'static str { "de" } fn checksum_salt(&self) -> Option<&'static str> { None } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Luxembourg } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({ "type": "WEB", "id": "MMILUX", "name": "webapp" }); req_json["ver"] = json!("1.43"); req_json["auth"] = json!({ "type": "AID", "aid": "SkC81GuwuzL4e0" }); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(MobiliteitLuProfile {}, "Lux", "Luxembourg, Wallis").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(MobiliteitLuProfile {}, "160904011", "200405060").await } } railway-provider-hafas-0.1.2/src/profile/mod.rs000064400000000000000000000323001046102023000175560ustar 00000000000000#[cfg(feature = "avv-profile")] pub mod avv; #[cfg(feature = "bart-profile")] pub mod bart; #[cfg(feature = "bls-profile")] pub mod bls; #[cfg(feature = "cfl-profile")] pub mod cfl; #[cfg(feature = "cmta-profile")] pub mod cmta; #[cfg(feature = "dart-profile")] pub mod dart; #[cfg(feature = "db-profile")] pub mod db; #[cfg(feature = "insa-profile")] pub mod insa; #[cfg(feature = "irish-rail-profile")] pub mod irish_rail; #[cfg(feature = "ivb-profile")] pub mod ivb; #[cfg(feature = "kvb-profile")] pub mod kvb; #[cfg(feature = "mobil-nrw-profile")] pub mod mobil_nrw; #[cfg(feature = "mobiliteit-lu-profile")] pub mod mobiliteit_lu; #[cfg(feature = "nahsh-profile")] pub mod nahsh; #[cfg(feature = "nvv-profile")] pub mod nvv; #[cfg(feature = "oebb-profile")] pub mod oebb; #[cfg(feature = "ooevv-profile")] pub mod ooevv; #[cfg(feature = "pkp-profile")] pub mod pkp; #[cfg(feature = "rejseplanen-profile")] pub mod rejseplanen; #[cfg(feature = "rmv-profile")] pub mod rmv; #[cfg(feature = "rsag-profile")] pub mod rsag; #[cfg(feature = "saarvv-profile")] pub mod saarvv; #[cfg(feature = "salzburg-profile")] pub mod salzburg; #[cfg(feature = "sbahn-muenchen-profile")] pub mod sbahn_muenchen; #[cfg(feature = "vgi-profile")] pub mod vgi; // Currently broken due to: // #[cfg(feature = "sncf-profile")] // pub mod sncf; // Currently broken due to: HAFAS Kernel: Date outside of the timetable period. // #[cfg(feature = "tpg-profile")] // pub mod tpg; #[cfg(feature = "resrobot-profile")] pub mod resrobot; #[cfg(feature = "svv-profile")] pub mod svv; #[cfg(feature = "vbb-profile")] pub mod vbb; #[cfg(feature = "vbn-profile")] pub mod vbn; #[cfg(feature = "verbundlinie-profile")] pub mod verbundlinie; #[cfg(feature = "vkg-profile")] pub mod vkg; #[cfg(feature = "vmt-profile")] pub mod vmt; #[cfg(feature = "vor-profile")] pub mod vor; #[cfg(feature = "vos-profile")] pub mod vos; #[cfg(feature = "vrn-profile")] pub mod vrn; #[cfg(feature = "vsn-profile")] pub mod vsn; #[cfg(feature = "vvt-profile")] pub mod vvt; #[cfg(feature = "vvv-profile")] pub mod vvv; // ADD PROFILE HERE // TODO: // BVG: Too many special things for now use chrono::DateTime; use chrono::FixedOffset; use chrono::NaiveDate; #[cfg(feature = "polylines")] use geojson::Feature; use rcore::Age; use rcore::ProductsSelection; use rcore::RemarkAssociation; use serde_json::Value; use std::collections::HashMap; use crate::Journey; use crate::Leg; use crate::Line; use crate::LoadFactor; use crate::Operator; use crate::ParseResult; use crate::Place; use crate::Product; use crate::Remark; use crate::Stop; use crate::TariffClass; use rcore::JourneysResponse; use rcore::LocationsResponse; use crate::parse::arrival_or_departure::*; use crate::parse::common::*; use crate::parse::date::*; use crate::parse::journey::*; use crate::parse::journeys_response::*; use crate::parse::leg::*; use crate::parse::line::*; use crate::parse::load_factor::*; use crate::parse::location::*; use crate::parse::locations_response::*; use crate::parse::operator::*; #[cfg(feature = "polylines")] use crate::parse::polyline::*; use crate::parse::products::*; use crate::parse::remark::*; use crate::parse::stopover::*; pub trait Profile: Send + Sync { fn url(&self) -> &'static str; fn checksum_salt(&self) -> Option<&'static str> { None } fn prepare_body(&self, req_json: &mut Value); fn prepare_headers(&self, headers: &mut HashMap<&str, &str>); fn price_currency(&self) -> &'static str; fn timezone(&self) -> chrono_tz::Tz; fn language(&self) -> &'static str { "en" } fn refresh_journey_use_out_recon_l(&self) -> bool { false } fn mic_mac(&self) -> bool { false } fn salt(&self) -> bool { false } fn products(&self) -> &'static [&'static Product]; fn custom_pem_bundle(&self) -> Option<&'static [u8]> { None } fn products_to_hafas(&self, selection: &ProductsSelection) -> u16 { let products = self.products(); let mut result = 0; for (i, product) in products.iter().enumerate() { if selection.contains(&product.mode) { result |= 1 << i; } } result } fn age_to_hafas(&self, _age: Age) -> &'static str { "E" } fn parse_common( &self, data: HafasCommon, tariff_class: TariffClass, ) -> ParseResult { default_parse_common(self, data, tariff_class) } fn parse_arrival_or_departure( &self, data: HafasArrivalOrDeparture, date: &NaiveDate, ) -> ParseResult { default_parse_arrival_or_departure(self, data, date) } fn parse_stopover( &self, data: HafasStopover, common: &CommonData, date: &NaiveDate, ) -> ParseResult { default_parse_stopover(self, data, common, date) } fn parse_remark(&self, data: HafasRemark) -> ParseResult { default_parse_remark(data, |c| self.remark_association(c)) } fn remark_association(&self, code: &str) -> RemarkAssociation { if code.is_empty() { RemarkAssociation::None } else { RemarkAssociation::Unknown } } fn parse_products(&self, p_cls: u16) -> Vec<&'static Product> { default_parse_products(p_cls, self.products()) } fn parse_product(&self, p_cls: u16) -> ParseResult<&'static Product> { default_parse_product(p_cls, self.products()) } #[cfg(feature = "polylines")] fn parse_polyline(&self, data: HafasPolyline) -> ParseResult> { default_parse_polyline(data) } fn parse_operator(&self, data: HafasOperator) -> ParseResult { default_parse_operator(data) } fn parse_locations_response( &self, data: HafasLocationsResponse, ) -> ParseResult { default_parse_locations_response(self, data) } fn parse_coords(&self, data: HafasCoords) -> (f32, f32) { default_parse_coords(data) } fn parse_place(&self, data: HafasPlace) -> ParseResult { default_parse_place(self, data) } fn parse_line(&self, data: HafasLine, operators: &[Operator]) -> ParseResult { default_parse_line(self, data, operators) } fn parse_leg( &self, data: HafasLeg, common: &CommonData, date: &NaiveDate, ) -> ParseResult> { default_parse_leg(self, data, common, date) } fn parse_journeys_response( &self, data: HafasJourneysResponse, tariff_class: TariffClass, ) -> ParseResult { default_parse_journeys_response(self, data, tariff_class) } fn parse_date( &self, time: Option, tz_offset: Option, date: &NaiveDate, ) -> ParseResult>> { default_parse_date(self, time, tz_offset, date) } fn parse_load_factor_entry( &self, h: HafasLoadFactorEntry, ) -> ParseResult> { default_parse_load_factor_entry(self, h) } fn parse_load_factor(&self, h: HafasLoadFactor) -> ParseResult { default_parse_load_factor(h) } fn parse_journey(&self, data: HafasJourney, common: &CommonData) -> ParseResult { default_parse_journey(self, data, common) } } impl Profile for Box { fn url(&self) -> &'static str { (**self).url() } fn checksum_salt(&self) -> Option<&'static str> { (**self).checksum_salt() } fn prepare_body(&self, req_json: &mut Value) { (**self).prepare_body(req_json) } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { (**self).prepare_headers(headers) } fn price_currency(&self) -> &'static str { (**self).price_currency() } fn timezone(&self) -> chrono_tz::Tz { (**self).timezone() } fn refresh_journey_use_out_recon_l(&self) -> bool { (**self).refresh_journey_use_out_recon_l() } fn mic_mac(&self) -> bool { (**self).mic_mac() } fn salt(&self) -> bool { (**self).salt() } fn products(&self) -> &'static [&'static Product] { (**self).products() } fn parse_common( &self, data: HafasCommon, tariff_class: TariffClass, ) -> ParseResult { (**self).parse_common(data, tariff_class) } fn parse_arrival_or_departure( &self, data: HafasArrivalOrDeparture, date: &NaiveDate, ) -> ParseResult { (**self).parse_arrival_or_departure(data, date) } fn parse_stopover( &self, data: HafasStopover, common: &CommonData, date: &NaiveDate, ) -> ParseResult { (**self).parse_stopover(data, common, date) } fn parse_remark(&self, data: HafasRemark) -> ParseResult { (**self).parse_remark(data) } fn parse_products(&self, p_cls: u16) -> Vec<&'static Product> { (**self).parse_products(p_cls) } fn parse_product(&self, p_cls: u16) -> ParseResult<&'static Product> { (**self).parse_product(p_cls) } #[cfg(feature = "polylines")] fn parse_polyline(&self, data: HafasPolyline) -> ParseResult> { (**self).parse_polyline(data) } fn parse_operator(&self, data: HafasOperator) -> ParseResult { (**self).parse_operator(data) } fn parse_locations_response( &self, data: HafasLocationsResponse, ) -> ParseResult { (**self).parse_locations_response(data) } fn parse_coords(&self, data: HafasCoords) -> (f32, f32) { (**self).parse_coords(data) } fn parse_place(&self, data: HafasPlace) -> ParseResult { (**self).parse_place(data) } fn parse_line(&self, data: HafasLine, operators: &[Operator]) -> ParseResult { (**self).parse_line(data, operators) } fn parse_leg( &self, data: HafasLeg, common: &CommonData, date: &NaiveDate, ) -> ParseResult> { (**self).parse_leg(data, common, date) } fn parse_journeys_response( &self, data: HafasJourneysResponse, tariff_class: TariffClass, ) -> ParseResult { (**self).parse_journeys_response(data, tariff_class) } fn parse_date( &self, time: Option, tz_offset: Option, date: &NaiveDate, ) -> ParseResult>> { (**self).parse_date(time, tz_offset, date) } fn parse_load_factor_entry( &self, h: HafasLoadFactorEntry, ) -> ParseResult> { (**self).parse_load_factor_entry(h) } fn parse_load_factor(&self, h: HafasLoadFactor) -> ParseResult { (**self).parse_load_factor(h) } fn parse_journey(&self, data: HafasJourney, common: &CommonData) -> ParseResult { (**self).parse_journey(data, common) } } #[cfg(test)] pub mod test { use crate::{client::HafasClient, Location, Place, Profile, Station}; use rcore::{JourneysOptions, LocationsOptions}; use rcore::{Provider, ReqwestRequesterBuilder}; pub async fn check_search, P: Profile + 'static>( profile: P, search: S, expected: S, ) -> Result<(), Box> { let client = HafasClient::new(profile, ReqwestRequesterBuilder::default()); let locations = client .locations(LocationsOptions { query: search.as_ref().to_string(), ..Default::default() }) .await?; let results = locations .into_iter() .flat_map(|p| match p { Place::Station(s) => s.name, Place::Location(Location::Address { address, .. }) => Some(address), Place::Location(Location::Point { name, .. }) => name, }) .collect::>(); assert!( results.iter().find(|s| s == &expected.as_ref()).is_some(), "expected {} to be contained in {:#?}", expected.as_ref(), results ); Ok(()) } pub async fn check_journey, P: Profile + 'static>( profile: P, from: S, to: S, ) -> Result<(), Box> { let client = HafasClient::new(profile, ReqwestRequesterBuilder::default()); let journeys = client .journeys( Place::Station(Station { id: from.as_ref().to_string(), ..Default::default() }), Place::Station(Station { id: to.as_ref().to_string(), ..Default::default() }), JourneysOptions::default(), ) .await?; assert!( !journeys.journeys.is_empty(), "expected journey from {} to {} to exist", from.as_ref(), to.as_ref() ); Ok(()) } } railway-provider-hafas-0.1.2/src/profile/nahsh.rs000064400000000000000000000065351046102023000201130ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; // TODO: Fix location, Parse journey with tickets, fix movement mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("High-speed rail"), short: Cow::Borrowed("ICE/HSR"), }; pub const IC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCity & EuroCity"), short: Cow::Borrowed("IC/EC"), }; pub const IR: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("Interregional"), short: Cow::Borrowed("IR"), }; pub const RB: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Regional & RegionalExpress"), short: Cow::Borrowed("RB/RE"), }; pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("S-Bahn"), short: Cow::Borrowed("S"), }; pub const B: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("B"), }; pub const F: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Ferry"), short: Cow::Borrowed("F"), }; pub const U: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U"), }; pub const T: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Tram"), short: Cow::Borrowed("T"), }; pub const ON_CALL: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("On-call transit"), short: Cow::Borrowed("on-call"), }; pub const PRODUCTS: &[&Product] = &[&ICE, &IC, &IR, &RB, &S, &B, &F, &U, &T, &ON_CALL]; } #[derive(Debug)] pub struct NahSHProfile; impl Profile for NahSHProfile { fn url(&self) -> &'static str { "https://nah.sh.hafas.de/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({ "type": "IPH", "id": "NAHSH", "v": "3000700", "name": "NAHSHPROD" }); req_json["ver"] = json!("1.30"); req_json["auth"] = json!({ "type": "AID", "aid": "r0Ot9FLFNAFxijLW" }); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(NahSHProfile {}, "Nahe", "Nahe Dorfstraße").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(NahSHProfile {}, "8000103", "8000199").await } } railway-provider-hafas-0.1.2/src/profile/nvv.rs000064400000000000000000000072401046102023000176150ustar 00000000000000use crate::{parse::load_factor::HafasLoadFactor, LoadFactor, ParseResult, Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCityExpress"), short: Cow::Borrowed("ICE"), }; pub const EC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("EuroCity/InterCity"), short: Cow::Borrowed("EC/IC"), }; pub const RE: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Regionalzug"), short: Cow::Borrowed("RE/RB"), }; pub const REGIOTRAM: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("RegioTram"), short: Cow::Borrowed("RegioTram"), }; pub const TRAM: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Tram"), short: Cow::Borrowed("Tram"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const SAMMELTAXI: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("AnrufSammelTaxi"), short: Cow::Borrowed("Sammeltaxi"), }; // TODO: public-transport-enabler differentiates between 8 (suburban) and 16 (subway) // TODO: public-transport-enabler also defines ferries and more regional trains. pub const PRODUCTS: &[&Product] = &[ &ICE, &EC, &RE, ®IOTRAM, ®IOTRAM, &TRAM, &BUS, &BUS, &Product::unknown(), &SAMMELTAXI, ®IOTRAM, ]; } #[derive(Debug)] pub struct NvvProfile; impl Profile for NvvProfile { fn url(&self) -> &'static str { "https://auskunft.nvv.de/auskunft/bin/app/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn checksum_salt(&self) -> Option<&'static str> { None } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"IPH","id":"NVV","v":"5000300","name":"NVVMobilPROD_APPSTORE"}); req_json["ver"] = json!("1.45"); req_json["ext"] = json!("NVV.6.0"); req_json["auth"] = json!({"type":"AID","aid":"Kt8eNOH7qjVeSxNA"}); } fn parse_load_factor(&self, h: HafasLoadFactor) -> ParseResult { // TODO: Load factors correct? match h { 10 => Ok(LoadFactor::LowToMedium), 11 => Ok(LoadFactor::High), 12 => Ok(LoadFactor::VeryHigh), 13 => Ok(LoadFactor::ExceptionallyHigh), _ => Err(format!("Invalid load factor: {}", h).into()), } } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(NvvProfile {}, "Ban", "Banteln").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(NvvProfile {}, "2200073", "2200042").await } } railway-provider-hafas-0.1.2/src/profile/oebb.rs000064400000000000000000000067521046102023000177220ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; // TODO: Fix weird POIs, Movement mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCityExpress & RailJet"), short: Cow::Borrowed("ICE/RJ"), }; pub const IC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCity & EuroCity"), short: Cow::Borrowed("IC/EC"), }; pub const D: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("Durchgangszug & EuroNight"), short: Cow::Borrowed("D/EN"), }; pub const R: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Regional & RegionalExpress"), short: Cow::Borrowed("R/REX"), }; pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("S-Bahn"), short: Cow::Borrowed("S"), }; pub const B: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("B"), }; pub const F: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Ferry"), short: Cow::Borrowed("F"), }; pub const U: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U"), }; pub const T: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Tram"), short: Cow::Borrowed("T"), }; pub const ON_CALL: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("on-call transit, lifts, etc"), short: Cow::Borrowed("on-call/lift"), }; pub const PRODUCTS: &[&Product] = &[ &ICE, &IC, &IC, &D, &R, &S, &B, &F, &U, &T, &Product::unknown(), &ON_CALL, &D, ]; } #[derive(Debug)] pub struct OebbProfile; impl Profile for OebbProfile { fn url(&self) -> &'static str { "https://fahrplan.oebb.at/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Vienna } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({ "type": "IPH", "id": "OEBB", "v": "6030600", "name": "oebbPROD-ADHOC" }); req_json["ver"] = json!("1.41"); req_json["auth"] = json!({ "type": "AID", "aid": "OWDL4fE4ixNiPBBm" }); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(OebbProfile {}, "Bri", "Brixlegg").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(OebbProfile {}, "1191801", "1190100").await } } railway-provider-hafas-0.1.2/src/profile/ooevv.rs000064400000000000000000000073671046102023000201540ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const BAHN_AND_S_BAHN: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Bahn & S-Bahn"), short: Cow::Borrowed("Bahn & S-Bahn"), }; pub const U_BAHN: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U-Bahn"), }; pub const STRASSENBAHN: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Straßenbahn"), short: Cow::Borrowed("Straßenbahn"), }; pub const STADTBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Stadtbus"), short: Cow::Borrowed("Stadtbus"), }; pub const REGIONALBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Regionalbus"), short: Cow::Borrowed("Regionalbus"), }; pub const FERNBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Fernbus"), short: Cow::Borrowed("Fernbus"), }; pub const SONSTIGE_BUSSE: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("sonstige Busse"), short: Cow::Borrowed("sonstige Busse"), }; pub const SEIL_: Product = Product { mode: Mode::Cablecar, name: Cow::Borrowed("Seil-/Zahnradbahn"), short: Cow::Borrowed("Seil-/Zahnradbahn"), }; pub const SCHIFF: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Schiff"), short: Cow::Borrowed("Schiff"), }; pub const AST: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Anrufsammeltaxi"), short: Cow::Borrowed("AST"), }; // TODO: Deduplicate bahn and s-bahn pub const PRODUCTS: &[&Product] = &[ &BAHN_AND_S_BAHN, &BAHN_AND_S_BAHN, &U_BAHN, &Product::unknown(), &STRASSENBAHN, &FERNBUS, ®IONALBUS, &STADTBUS, &SEIL_, &SCHIFF, &AST, &SONSTIGE_BUSSE, ]; } #[derive(Debug)] pub struct OoevvProfile; impl Profile for OoevvProfile { fn url(&self) -> &'static str { "https://app.verkehrsauskunft.at/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Vienna } fn checksum_salt(&self) -> Option<&'static str> { Some("6633673735743766726667323938336A") } fn mic_mac(&self) -> bool { true } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"AND","id":"VAO"}); req_json["ver"] = json!("1.27"); req_json["ext"] = json!("VAO.11"); req_json["auth"] = json!({"type": "USER", "aid": "and20201hf7mcf9bv3nv8g5f", "user": "mobile", "pw": "87a6f8ZbnBih32"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(OoevvProfile {}, "Vien", "Vienna Hauptbahnhof").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(OoevvProfile {}, "444670100", "431507400").await } } railway-provider-hafas-0.1.2/src/profile/pkp.rs000064400000000000000000000045121046102023000175750ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; // TODO: Trim stop name mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const EIC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("ExpressInterCity & ExpressInterCity Premium & InterCityExpress"), short: Cow::Borrowed("EIC/EIP/ICE"), }; pub const IC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCity & Twoje Linie Kolejowe & EuroCity & EuroNight"), short: Cow::Borrowed("IC/TLK/EC/EN"), }; pub const R: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Regional"), short: Cow::Borrowed("R"), }; pub const B: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("B"), }; pub const PRODUCTS: &[&Product] = &[&EIC, &EIC, &IC, &R, &Product::unknown(), &B]; } #[derive(Debug)] pub struct PkpProfile; impl Profile for PkpProfile { fn url(&self) -> &'static str { "https://mobil.rozklad-pkp.pl:8019/bin/mgate.exe" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Warsaw } fn language(&self) -> &'static str { "pl" } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({ "type": "AND", "id": "HAFAS" }); req_json["ver"] = json!("1.21"); req_json["auth"] = json!({ "type": "AID", "aid": "DrxJYtYZQpEBCtcb" }); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "Dalvik/2.1.0"); } fn price_currency(&self) -> &'static str { "PLN" } } #[cfg(test)] mod test { use crate::profile::test::{check_journey, check_search}; use std::error::Error; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(PkpProfile {}, "Warsa", "Warsaw Airport").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(PkpProfile {}, "5100069", "5100028").await } } railway-provider-hafas-0.1.2/src/profile/rejseplanen.rs000064400000000000000000000057461046102023000213230ustar 00000000000000use crate::{LoadFactor, ParseResult, Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; use super::HafasLoadFactor; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const IC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCity"), short: Cow::Borrowed("IC"), }; pub const ICL: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("ICL"), short: Cow::Borrowed("ICL"), }; pub const RE: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Regional"), short: Cow::Borrowed("RE"), }; pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("S-Tog A/B/Bx/C/E/F/H"), short: Cow::Borrowed("S"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("B"), }; pub const PRODUCTS: &[&Product] = &[&IC, &ICL, &RE, &Product::unknown(), &S, &BUS]; } #[derive(Debug)] pub struct RejseplanenProfile; impl Profile for RejseplanenProfile { fn url(&self) -> &'static str { "https://mobilapps.rejseplanen.dk/bin/iphone.exe" } fn language(&self) -> &'static str { "dk" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Copenhagen } fn checksum_salt(&self) -> Option<&'static str> { None } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"AND","id":"DK","v":"","name":""}); req_json["ver"] = json!("1.43"); req_json["ext"] = json!("DK.9"); req_json["auth"] = json!({"type":"AID","aid":"irkmpm9mdznstenr-android"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } fn parse_load_factor(&self, h: HafasLoadFactor) -> ParseResult { // TODO: Load factors correct? match h { 5 => Ok(LoadFactor::LowToMedium), 11 => Ok(LoadFactor::High), 12 => Ok(LoadFactor::VeryHigh), 13 => Ok(LoadFactor::ExceptionallyHigh), _ => Err(format!("Invalid load factor: {}", h).into()), } } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(RejseplanenProfile {}, "Rej", "Rejsby St.").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(RejseplanenProfile {}, "8600626", "8600020").await } } railway-provider-hafas-0.1.2/src/profile/resrobot.rs000064400000000000000000000072101046102023000206400ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const IC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("Train"), short: Cow::Borrowed("IC"), }; pub const IC_BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Express Bus"), short: Cow::Borrowed("IC_Bus"), }; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("Highspeed"), short: Cow::Borrowed("ICE"), }; pub const NIGHT_TRAIN: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("Night train"), short: Cow::Borrowed("NightTrain"), }; pub const PLANE: Product = Product { mode: Mode::Unknown, name: Cow::Borrowed("Air"), short: Cow::Borrowed("Plane"), }; pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("Commuter"), short: Cow::Borrowed("S"), }; pub const SHIP: Product = Product { mode: Mode::Unknown, name: Cow::Borrowed("Ferry"), short: Cow::Borrowed("Ship"), }; pub const TAXI: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Taxi"), short: Cow::Borrowed("Taxi"), }; pub const TRAM: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Tram"), short: Cow::Borrowed("Tram"), }; pub const U: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("Metro"), short: Cow::Borrowed("U"), }; pub const PRODUCTS: &[&Product] = &[ &PLANE, &ICE, &IC, &IC_BUS, &S, &U, &TRAM, &BUS, &SHIP, &TAXI, &NIGHT_TRAIN, ]; } #[derive(Debug)] pub struct ResrobotProfile; impl Profile for ResrobotProfile { fn url(&self) -> &'static str { "https://reseplanerare.resrobot.se/bin/mgate.exe" } fn language(&self) -> &'static str { "sv" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Stockholm } fn checksum_salt(&self) -> Option<&'static str> { None } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"WEB","id":"SAMTRAFIKEN","v":10001,"name":"webapp"}); req_json["ver"] = json!("1.73"); req_json["auth"] = json!({"type":"AID","aid":"h5o3n7f4t2m8l9x1"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "SEK" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search( ResrobotProfile {}, "Stock Central", "Stockholm Centralstation", ) .await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { // Stockholm City -> Arlanda C check_journey(ResrobotProfile {}, "740001617", "740000556").await } } railway-provider-hafas-0.1.2/src/profile/rmv.rs000064400000000000000000000075721046102023000176200ustar 00000000000000use crate::{parse::load_factor::HafasLoadFactor, LoadFactor, ParseResult, Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCityExpress/Fernzug"), short: Cow::Borrowed("ICE"), }; pub const EC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("EuroCity/InterCity/EuroNight/InterRegio"), short: Cow::Borrowed("EC/IC/EN/IR"), }; pub const RE: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("RegionalExpress/Regionalbahn"), short: Cow::Borrowed("RE/RB"), }; pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("S-Bahn"), short: Cow::Borrowed("S"), }; pub const U: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U"), }; pub const TRAM: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Straßenbahn"), short: Cow::Borrowed("Tram"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const SCHIFF: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Schiff"), short: Cow::Borrowed("Schiff"), }; pub const AST: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Anruf-Sammel-Taxi"), short: Cow::Borrowed("AST"), }; pub const SEILBAHN: Product = Product { mode: Mode::Cablecar, name: Cow::Borrowed("Seilbahn"), short: Cow::Borrowed("Seilbahn"), }; pub const PRODUCTS: &[&Product] = &[ &ICE, &EC, &RE, &S, &U, &TRAM, &BUS, &BUS, &SCHIFF, &AST, &SEILBAHN, ]; } #[derive(Debug)] pub struct RmvProfile; impl Profile for RmvProfile { fn url(&self) -> &'static str { "https://www.rmv.de/auskunft/bin/jp/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({ "type": "WEB", "id": "RMV", "name": "webapp" }); req_json["ver"] = json!("1.44"); req_json["ext"] = json!("RMV.1"); req_json["auth"] = json!({ "type": "AID", "aid": "x0k4ZR33ICN9CWmj" }); } fn parse_load_factor(&self, h: HafasLoadFactor) -> ParseResult { // TODO: Load factors correct? match h { 10 => Ok(LoadFactor::LowToMedium), 11 => Ok(LoadFactor::High), 12 => Ok(LoadFactor::VeryHigh), 13 => Ok(LoadFactor::ExceptionallyHigh), 5 => Ok(LoadFactor::LowToMedium), _ => Err(format!("Invalid load factor: {}", h).into()), } } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(RmvProfile {}, "Ham", "Hammelburg").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(RmvProfile {}, "3010011", "3011332").await } } railway-provider-hafas-0.1.2/src/profile/rsag.rs000064400000000000000000000071351046102023000177430ustar 00000000000000use crate::{LoadFactor, ParseResult, Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; use super::HafasLoadFactor; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCityExpress"), short: Cow::Borrowed("ICE"), }; pub const IC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCity & EuroCity"), short: Cow::Borrowed("IC/EC"), }; pub const IR: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterRegio/high-speed train"), short: Cow::Borrowed("IR/other"), }; pub const RE: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("regional train"), short: Cow::Borrowed("RE/RB"), }; pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("S-Bahn"), short: Cow::Borrowed("S"), }; pub const B: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("B"), }; pub const F: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Schiff"), short: Cow::Borrowed("F"), }; pub const U: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U"), }; pub const T: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Tram"), short: Cow::Borrowed("T"), }; pub const AST: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Taxi/on-call vehicle"), short: Cow::Borrowed("AST"), }; pub const PRODUCTS: &[&Product] = &[&ICE, &IC, &IR, &RE, &S, &B, &F, &U, &T, &AST]; } #[derive(Debug)] pub struct RsagProfile; impl Profile for RsagProfile { fn url(&self) -> &'static str { "https://fahrplan.rsag-online.de/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn checksum_salt(&self) -> Option<&'static str> { None } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"WEB","id":"RSAG","v":"","name":"webapp"}); req_json["ver"] = json!("1.42"); req_json["ext"] = json!("VBN.2"); req_json["auth"] = json!({"type":"AID","aid":"tF5JTs25rzUhGrrl"}); } fn parse_load_factor(&self, h: HafasLoadFactor) -> ParseResult { match h { 11 => Ok(LoadFactor::LowToMedium), 12 => Ok(LoadFactor::High), _ => Err(format!("Invalid load factor: {}", h).into()), } } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(RsagProfile {}, "Bri", "Bristow").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(RsagProfile {}, "8010304", "8010153").await } } railway-provider-hafas-0.1.2/src/profile/saarvv.rs000064400000000000000000000072251046102023000203110ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; // TODO: Fix movement? mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("Hochgeschwindigkeitszug"), short: Cow::Borrowed("ICE"), }; pub const IC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCity & EuroCity"), short: Cow::Borrowed("IC/EC"), }; pub const IR: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterRegio"), short: Cow::Borrowed("IR"), }; pub const RB: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Regionalzug"), short: Cow::Borrowed("RB"), }; pub const S_BAHN: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("S-Bahn"), short: Cow::Borrowed("S-Bahn"), }; pub const U: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U"), }; pub const S: Product = Product { // TODO: Correct? mode: Mode::RegionalTrain, name: Cow::Borrowed("Saarbahn"), short: Cow::Borrowed("S"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const SCHIFF: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Schiff"), short: Cow::Borrowed("Schiff"), }; pub const AST: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Anruf-Sammel-Taxi"), short: Cow::Borrowed("AST"), }; pub const SCHULBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Schulbus"), short: Cow::Borrowed("Schulbus"), }; // TODO: `1`, `2`, `4` bitmasks? pub const PRODUCTS: &[&Product] = &[ &Product::unknown(), &Product::unknown(), &Product::unknown(), &SCHULBUS, &AST, &SCHIFF, &BUS, &S, &U, &S_BAHN, &RB, &IR, &IC, &ICE, ]; } #[derive(Debug)] pub struct SaarvvProfile; impl Profile for SaarvvProfile { fn url(&self) -> &'static str { "https://saarfahrplan.de/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn checksum_salt(&self) -> Option<&'static str> { Some("HJtlubisvxiJxss") } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"AND","id":"ZPS-SAAR","v":"","name":"Saarvv"}); req_json["ver"] = json!("1.40"); req_json["auth"] = json!({"type":"AID","aid":"51XfsVqgbdA6oXzHrx75jhlocRg6Xe"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(SaarvvProfile {}, "Norhe", "Norheim").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(SaarvvProfile {}, "15541", "10609").await } } railway-provider-hafas-0.1.2/src/profile/salzburg.rs000064400000000000000000000071451046102023000206410ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const BAHN_AND_S_BAHN: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Bahn & S-Bahn"), short: Cow::Borrowed("Bahn & S-Bahn"), }; pub const U_BAHN: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U-Bahn"), }; pub const STRASSENBAHN: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Straßenbahn"), short: Cow::Borrowed("Straßenbahn"), }; pub const STADTBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Stadtbus"), short: Cow::Borrowed("Stadtbus"), }; pub const REGIONALBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Regionalbus"), short: Cow::Borrowed("Regionalbus"), }; pub const FERNBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Fernbus"), short: Cow::Borrowed("Fernbus"), }; pub const SONSTIGE_BUSSE: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("sonstige Busse"), short: Cow::Borrowed("sonstige Busse"), }; pub const SEIL_: Product = Product { mode: Mode::Cablecar, name: Cow::Borrowed("Seil-/Zahnradbahn"), short: Cow::Borrowed("Seil-/Zahnradbahn"), }; pub const SCHIFF: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Schiff"), short: Cow::Borrowed("Schiff"), }; pub const AST: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Anrufsammeltaxi"), short: Cow::Borrowed("AST"), }; // TODO: Deduplicate bahn and s-bahn pub const PRODUCTS: &[&Product] = &[ &BAHN_AND_S_BAHN, &BAHN_AND_S_BAHN, &U_BAHN, &Product::unknown(), &STRASSENBAHN, &FERNBUS, ®IONALBUS, &STADTBUS, &SEIL_, &SCHIFF, &AST, &SONSTIGE_BUSSE, ]; } #[derive(Debug)] pub struct SalzburgProfile; impl Profile for SalzburgProfile { fn url(&self) -> &'static str { "https://verkehrsauskunft.salzburg.gv.at/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Vienna } fn checksum_salt(&self) -> Option<&'static str> { None } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"WEB","id":"VAO","v":"","name":"webapp"}); req_json["ver"] = json!("1.32"); req_json["auth"] = json!({"type":"AID","aid":"wf7mcf9bv3nv8g5f"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(SalzburgProfile {}, "Zauc", "ZAUCHEN (STMK)").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(SalzburgProfile {}, "455001300", "455110200").await } } railway-provider-hafas-0.1.2/src/profile/sbahn_muenchen.rs000064400000000000000000000067001046102023000217610ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCityExpress"), short: Cow::Borrowed("ICE"), }; pub const IC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCity/EuroCity"), short: Cow::Borrowed("IC/EC"), }; pub const IRE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("Interregio/Schnellzug"), short: Cow::Borrowed("IRE"), }; pub const RE: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Regio- und Nahverkehr"), short: Cow::Borrowed("RE/RB"), }; pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("S-Bahn"), short: Cow::Borrowed("S"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const U: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U"), }; pub const TRAM: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Straßenbahn"), short: Cow::Borrowed("Tram"), }; pub const SAMMELTAXI: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Anrufsammeltaxi"), short: Cow::Borrowed("Sammeltaxi"), }; pub const PRODUCTS: &[&Product] = &[ &ICE, &IC, &IRE, &RE, &S, &BUS, &Product::unknown(), &U, &TRAM, &SAMMELTAXI, ]; } #[derive(Debug)] pub struct SBahnMuenchenProfile; impl Profile for SBahnMuenchenProfile { fn url(&self) -> &'static str { "https://s-bahn-muenchen.hafas.de/bin/540/mgate.exe" } fn language(&self) -> &'static str { "en" } fn checksum_salt(&self) -> Option<&'static str> { Some("ggnvMVV8RTt67gh1") } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn mic_mac(&self) -> bool { true } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"IPH","id":"DB-REGIO-MVV","v":"5010100","name":"MuenchenNavigator"}); req_json["ver"] = json!("1.34"); req_json["ext"] = json!("DB.R15.12.a"); req_json["auth"] = json!({"type":"AID","aid":"d491MVVhz9ZZts23"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(SBahnMuenchenProfile {}, "Arena", "Allianz-Arena, München").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(SBahnMuenchenProfile {}, "8004158", "8000261").await } } railway-provider-hafas-0.1.2/src/profile/sncf.rs000064400000000000000000000105511046102023000177340ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; // TODO: Profiles correct? mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const ICE: Product = Product { id: Cow::Borrowed("nationalExpress"), mode: Mode::Train, bitmasks: Cow::Borrowed(&[1]), name: Cow::Borrowed("InterCityExpress"), short: Cow::Borrowed("ICE"), }; pub const IC: Product = Product { id: Cow::Borrowed("national"), mode: Mode::Train, bitmasks: Cow::Borrowed(&[2]), name: Cow::Borrowed("InterCity & EuroCity"), short: Cow::Borrowed("IC/EC"), }; pub const RE: Product = Product { id: Cow::Borrowed("regionalExp"), mode: Mode::Train, bitmasks: Cow::Borrowed(&[4]), name: Cow::Borrowed("RegionalExpress & InterRegio"), short: Cow::Borrowed("RE/IR"), }; pub const RB: Product = Product { id: Cow::Borrowed("regional"), mode: Mode::Train, bitmasks: Cow::Borrowed(&[8]), name: Cow::Borrowed("Regio"), short: Cow::Borrowed("RB"), }; pub const S: Product = Product { id: Cow::Borrowed("suburban"), mode: Mode::Train, bitmasks: Cow::Borrowed(&[16]), name: Cow::Borrowed("S-Bahn"), short: Cow::Borrowed("S"), }; pub const B: Product = Product { id: Cow::Borrowed("bus"), mode: Mode::Bus, bitmasks: Cow::Borrowed(&[32]), name: Cow::Borrowed("Bus"), short: Cow::Borrowed("B"), }; pub const F: Product = Product { id: Cow::Borrowed("ferry"), mode: Mode::Watercraft, bitmasks: Cow::Borrowed(&[64]), name: Cow::Borrowed("Ferry"), short: Cow::Borrowed("F"), }; pub const U: Product = Product { id: Cow::Borrowed("subway"), mode: Mode::Train, bitmasks: Cow::Borrowed(&[128]), name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U"), }; pub const T: Product = Product { id: Cow::Borrowed("tram"), mode: Mode::Train, bitmasks: Cow::Borrowed(&[256]), name: Cow::Borrowed("Tram"), short: Cow::Borrowed("T"), }; pub const TAXI: Product = Product { id: Cow::Borrowed("taxi"), mode: Mode::Taxi, bitmasks: Cow::Borrowed(&[512]), name: Cow::Borrowed("Group Taxi"), short: Cow::Borrowed("Taxi"), }; pub const PRODUCTS: &[&Product] = &[&ICE, &IC, &RE, &RB, &S, &B, &F, &U, &T, &TAXI]; } #[derive(Debug)] pub struct SncfProfile; impl Profile for SncfProfile { fn url(&self) -> &'static str { "https://sncf-maps.hafas.de/bin/maps-ng/mgate.exe" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Paris } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({ "id": "SNCF_LIVEMAP", "type": "WEB", "name": "webapp", "l": "vs_webapp" }); req_json["id"] = json!("6tm47gqmkkk7hgcs"); req_json["ver"] = json!("1.18"); req_json["auth"] = json!({ "type": "AID", "aid": "hf7mcf9bv3nv8g5f" }); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "" } } #[cfg(test)] mod test { use crate::{ api::journeys::JourneysOptions, client::HafasClient, requester::hyper::HyperRustlsRequester, Place, Stop, }; use std::error::Error; use super::*; #[tokio::test] async fn test_path_available() -> Result<(), Box> { let client = HafasClient::new(SncfProfile {}, HyperRustlsRequester::new()); let journeys = client .journeys( Place::Stop(Stop { id: "008734243".to_string(), ..Default::default() }), Place::Stop(Stop { id: "008775774".to_string(), ..Default::default() }), JourneysOptions::default(), ) .await?; assert!(!journeys.journeys.is_empty()); Ok(()) } } railway-provider-hafas-0.1.2/src/profile/svv.rs000064400000000000000000000062741046102023000176300ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; // TODO: public-transport-enabler differentiates between 1 (high-speed) and 2 (suburban). pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("Bahn & S-Bahn"), short: Cow::Borrowed("S/Zug"), }; pub const U: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U"), }; pub const STR: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Strassenbahn"), short: Cow::Borrowed("Str"), }; pub const FERNBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Fernbus"), short: Cow::Borrowed("Bus"), }; pub const REGIONALBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Regionalbus"), short: Cow::Borrowed("Bus"), }; pub const STADTBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Stadtbus"), short: Cow::Borrowed("Bus"), }; pub const SEIL_: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Seil-/Zahnradbahn"), short: Cow::Borrowed("Seil-/Zahnradbahn"), }; pub const F: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Schiff"), short: Cow::Borrowed("F"), }; pub const PRODUCTS: &[&Product] = &[ &S, &S, &U, &Product::unknown(), &STR, &FERNBUS, ®IONALBUS, &STADTBUS, &SEIL_, &F, ]; } #[derive(Debug)] pub struct SvvProfile; impl Profile for SvvProfile { fn url(&self) -> &'static str { "https://fahrplan.salzburg-verkehr.at/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Vienna } fn checksum_salt(&self) -> Option<&'static str> { None } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"WEB","id":"VAO","v":"","name":"webapp"}); req_json["ver"] = json!("1.39"); req_json["ext"] = json!("VAO.11"); req_json["auth"] = json!({"type":"AID","aid":"wf7mcf9bv3nv8g5f"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(SvvProfile {}, "Sal", "Salzburg Hauptbahnhof").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(SvvProfile {}, "455086100", "455082100").await } } railway-provider-hafas-0.1.2/src/profile/tpg.rs000064400000000000000000000105361046102023000176000ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const TGV: Product = Product { id: Cow::Borrowed("tgv"), mode: Mode::Train, bitmasks: Cow::Borrowed(&[1]), name: Cow::Borrowed("TGV"), short: Cow::Borrowed("TGV"), }; pub const INTERCITES: Product = Product { id: Cow::Borrowed("intercites"), mode: Mode::Train, bitmasks: Cow::Borrowed(&[2]), name: Cow::Borrowed("Intercites"), short: Cow::Borrowed("Intercites"), }; pub const IR: Product = Product { id: Cow::Borrowed("ir"), mode: Mode::Train, bitmasks: Cow::Borrowed(&[4]), name: Cow::Borrowed("IR"), short: Cow::Borrowed("IR"), }; pub const TRAIN_DIRECT: Product = Product { id: Cow::Borrowed("train-direct"), mode: Mode::Train, bitmasks: Cow::Borrowed(&[8]), name: Cow::Borrowed("Train direct"), short: Cow::Borrowed("Train direct"), }; pub const BATEAU: Product = Product { id: Cow::Borrowed("bateau"), mode: Mode::Watercraft, bitmasks: Cow::Borrowed(&[16]), name: Cow::Borrowed("Bateau"), short: Cow::Borrowed("Bateau"), }; pub const REGIO_EXPRESS: Product = Product { id: Cow::Borrowed("regio-express"), mode: Mode::Train, bitmasks: Cow::Borrowed(&[32]), name: Cow::Borrowed("Regio Express"), short: Cow::Borrowed("Regio Express"), }; pub const BUS: Product = Product { id: Cow::Borrowed("bus"), mode: Mode::Bus, bitmasks: Cow::Borrowed(&[64]), name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const TRANSPORT_A_CABLES: Product = Product { id: Cow::Borrowed("transport-a-cables"), mode: Mode::Gondola, bitmasks: Cow::Borrowed(&[128]), name: Cow::Borrowed("Transport a cables"), short: Cow::Borrowed("Transport a cables"), }; pub const TRAM: Product = Product { id: Cow::Borrowed("tram"), mode: Mode::Train, bitmasks: Cow::Borrowed(&[512]), name: Cow::Borrowed("Tram"), short: Cow::Borrowed("Tram"), }; pub const PRODUCTS: &[&Product] = &[ &TGV, &INTERCITES, &IR, &TRAIN_DIRECT, &BATEAU, ®IO_EXPRESS, &BUS, &TRANSPORT_A_CABLES, &TRAM, ]; } #[derive(Debug)] pub struct TpgProfile; impl Profile for TpgProfile { fn url(&self) -> &'static str { "https://tpg-webapp.hafas.de/bin/mgate.exe" } fn language(&self) -> &'static str { "fr" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn checksum_salt(&self) -> Option<&'static str> { None } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"WEB","id":"HAFAS","v":"","name":"webapp"}); req_json["ver"] = json!("1.40"); req_json["auth"] = json!({"type":"AID","aid":"9CZsdl5PqX8n5D6b"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use crate::{ api::journeys::JourneysOptions, client::HafasClient, requester::hyper::HyperRustlsRequester, Place, Stop, }; use std::error::Error; use super::*; #[tokio::test] async fn test_path_available() -> Result<(), Box> { let client = HafasClient::new(TpgProfile {}, HyperRustlsRequester::new()); let journeys = client .journeys( Place::Stop(Stop { id: "100449".to_string(), ..Default::default() }), Place::Stop(Stop { id: "100451".to_string(), ..Default::default() }), JourneysOptions::default(), ) .await?; assert!(!journeys.journeys.is_empty()); Ok(()) } } railway-provider-hafas-0.1.2/src/profile/vbb.rs000064400000000000000000000052641046102023000175610ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("S-Bahn"), short: Cow::Borrowed("S"), }; pub const U: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U"), }; pub const T: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Tram"), short: Cow::Borrowed("T"), }; pub const B: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("B"), }; pub const F: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Fähre"), short: Cow::Borrowed("F"), }; pub const E: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("IC/ICE"), short: Cow::Borrowed("E"), }; pub const R: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("RB/RE"), short: Cow::Borrowed("R"), }; pub const PRODUCTS: &[&Product] = &[&S, &U, &T, &B, &F, &E, &R]; } #[derive(Debug)] pub struct VbbProfile; impl Profile for VbbProfile { fn url(&self) -> &'static str { "https://fahrinfo.vbb.de/bin/mgate.exe" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({ "type": "WEB", "id": "VBB", "name": "VBB WebApp", "l": "vs_webapp_vbb" }); req_json["ver"] = json!("1.45"); req_json["auth"] = json!({ "type": "AID", "aid": "hafas-vbb-webapp" }); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(VbbProfile {}, "Rose", "U Rosenthaler Platz (Berlin)").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(VbbProfile {}, "900003201", "900024101").await } } railway-provider-hafas-0.1.2/src/profile/vbn.rs000064400000000000000000000062661046102023000176000ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCityExpress"), short: Cow::Borrowed("ICE"), }; pub const IC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCity, EuroCity, CityNightLine, InterRegio"), short: Cow::Borrowed("IC/EC/CNL/IR"), }; pub const NAHV: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Nahverkehr"), short: Cow::Borrowed("Nahv"), }; pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("S-Bahn"), short: Cow::Borrowed("S"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const SCHIFF: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Schiff"), short: Cow::Borrowed("Schiff"), }; pub const U: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U"), }; pub const TRAM: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Tram"), short: Cow::Borrowed("Tram"), }; pub const AST: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Anrufverkehr"), short: Cow::Borrowed("AST"), }; pub const PRODUCTS: &[&Product] = &[&ICE, &IC, &IC, &NAHV, &S, &BUS, &SCHIFF, &U, &TRAM, &AST]; } #[derive(Debug)] pub struct VbnProfile; impl Profile for VbnProfile { fn url(&self) -> &'static str { "https://fahrplaner.vbn.de/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn checksum_salt(&self) -> Option<&'static str> { Some("SP31mBufSyCLmNxp") } fn mic_mac(&self) -> bool { true } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"IPH","id":"VBN","v":"6000000","name":"vbn"}); req_json["ver"] = json!("1.42"); req_json["auth"] = json!({"type":"AID","aid":"kaoxIXLn03zCr2KR"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(VbnProfile {}, "Brem", "Bremerhaven Hbf").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(VbnProfile {}, "9014418", "9093627").await } } railway-provider-hafas-0.1.2/src/profile/verbundlinie.rs000064400000000000000000000072371046102023000215000ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const BAHN_AND_S_BAHN: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Bahn & S-Bahn"), short: Cow::Borrowed("Bahn & S-Bahn"), }; pub const U_BAHN: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U-Bahn"), }; pub const STRASSENBAHN: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Straßenbahn"), short: Cow::Borrowed("Straßenbahn"), }; pub const STADTBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Stadtbus"), short: Cow::Borrowed("Stadtbus"), }; pub const REGIONALBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Regionalbus"), short: Cow::Borrowed("Regionalbus"), }; pub const FERNBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Fernbus"), short: Cow::Borrowed("Fernbus"), }; pub const SONSTIGE_BUSSE: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("sonstige Busse"), short: Cow::Borrowed("sonstige Busse"), }; pub const SEIL_: Product = Product { mode: Mode::Cablecar, name: Cow::Borrowed("Seil-/Zahnradbahn"), short: Cow::Borrowed("Seil-/Zahnradbahn"), }; pub const SCHIFF: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Schiff"), short: Cow::Borrowed("Schiff"), }; pub const AST: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Anrufsammeltaxi"), short: Cow::Borrowed("AST"), }; // TODO: Separate bahn and s-bahn pub const PRODUCTS: &[&Product] = &[ &BAHN_AND_S_BAHN, &BAHN_AND_S_BAHN, &Product::unknown(), &U_BAHN, &STRASSENBAHN, &FERNBUS, ®IONALBUS, &STADTBUS, &SEIL_, &SCHIFF, &AST, &SONSTIGE_BUSSE, ]; } #[derive(Debug)] pub struct VerbundlinieProfile; impl Profile for VerbundlinieProfile { fn url(&self) -> &'static str { "https://verkehrsauskunft.verbundlinie.at/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Vienna } fn checksum_salt(&self) -> Option<&'static str> { None } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"WEB","id":"VAO","v":"","name":"webapp"}); req_json["ver"] = json!("1.32"); req_json["ext"] = json!("VAO.13"); req_json["auth"] = json!({"type":"AID","aid":"wf7mcf9bv3nv8g5f"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(VerbundlinieProfile {}, "Graz", "Graz Ostbahnhof").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(VerbundlinieProfile {}, "460413500", "460415400").await } } railway-provider-hafas-0.1.2/src/profile/vgi.rs000064400000000000000000000060431046102023000175710ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const TRAIN_EXP: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("High-speed train"), short: Cow::Borrowed("Train"), }; pub const TRAIN_REG: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Regional train"), short: Cow::Borrowed("Train"), }; pub const ZUG: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Nahverkehrszug"), short: Cow::Borrowed("Zug"), }; pub const FERRY: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Ferry"), short: Cow::Borrowed("Ferry"), }; pub const SUBWAY: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("Subway"), short: Cow::Borrowed("Subway"), }; pub const TRAM: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Tram"), short: Cow::Borrowed("Tram"), }; pub const ON_DEMAND: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("On-demand traffic"), short: Cow::Borrowed("on demand"), }; pub const PRODUCTS: &[&Product] = &[ &BUS, &TRAIN_EXP, &TRAIN_REG, &ZUG, &BUS, &FERRY, &SUBWAY, &TRAM, &ON_DEMAND, ]; } #[derive(Debug)] pub struct VgiProfile; impl Profile for VgiProfile { fn url(&self) -> &'static str { "https://fpa.invg.de/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn checksum_salt(&self) -> Option<&'static str> { None } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"IPH","id":"INVG","v":"1040000","name":"invgPROD-APPSTORE-LIVE"}); req_json["ver"] = json!("1.39"); req_json["auth"] = json!({"type":"AID","aid":"GITvwi3BGOmTQ2a5"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(VgiProfile {}, "Ingol Hbf", "Ingolstadt, Hauptbahnhof Ost").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(VgiProfile {}, "8000183", "84999").await } } railway-provider-hafas-0.1.2/src/profile/vkg.rs000064400000000000000000000070461046102023000175770ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const BAHN_AND_S: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Bahn & S-Bahn"), short: Cow::Borrowed("Bahn & S"), }; pub const U: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U"), }; pub const STRASSENBAHN: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Straßenbahn"), short: Cow::Borrowed("Straßenbahn"), }; pub const STADTBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Stadtbus"), short: Cow::Borrowed("Stadtbus"), }; pub const REGIONALBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Regionalbus"), short: Cow::Borrowed("Regionalbus"), }; pub const FERNBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Fernbus"), short: Cow::Borrowed("Fernbus"), }; pub const SONSTIGE_BUSSE: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("sonstige Busse"), short: Cow::Borrowed("sonstige Busse"), }; pub const SEIL_: Product = Product { mode: Mode::Cablecar, name: Cow::Borrowed("Seil-/Zahnradbahn"), short: Cow::Borrowed("Seil-/Zahnradbahn"), }; pub const SCHIFF: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Schiff"), short: Cow::Borrowed("Schiff"), }; pub const AST: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Anrufsammeltaxi"), short: Cow::Borrowed("AST"), }; // TODO: Separate Bahn and S pub const PRODUCTS: &[&Product] = &[ &BAHN_AND_S, &BAHN_AND_S, &U, &Product::unknown(), &STRASSENBAHN, &FERNBUS, ®IONALBUS, &STADTBUS, &SEIL_, &SCHIFF, &AST, &SONSTIGE_BUSSE, ]; } #[derive(Debug)] pub struct VkgProfile; impl Profile for VkgProfile { fn url(&self) -> &'static str { "https://routenplaner.kaerntner-linien.at/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Vienna } fn checksum_salt(&self) -> Option<&'static str> { None } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"WEB","id":"VAO","v":"","name":"webapp"}); req_json["ver"] = json!("1.39"); req_json["auth"] = json!({"type":"AID","aid":"wf7mcf9bv3nv8g5f"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(VkgProfile {}, "Zau", "ZAUCHEN (STMK)").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(VkgProfile {}, "420512200", "420649500").await } } railway-provider-hafas-0.1.2/src/profile/vmt.rs000064400000000000000000000047701046102023000176170ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("long-distance train"), short: Cow::Borrowed("ICE/IC/EC"), }; pub const RE: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("regional train"), short: Cow::Borrowed("RE/RB"), }; pub const TRAM: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("tram"), short: Cow::Borrowed("tram"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("bus"), short: Cow::Borrowed("bus"), }; pub const PRODUCTS: &[&Product] = &[ &ICE, &ICE, &ICE, &RE, &RE, &TRAM, &Product::unknown(), &Product::unknown(), &BUS, ]; } #[derive(Debug)] pub struct VmtProfile; impl Profile for VmtProfile { fn url(&self) -> &'static str { "https://vmt.hafas.de/bin/ticketing/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn checksum_salt(&self) -> Option<&'static str> { Some("7x8d3n2a5m1b3c6z") } fn mic_mac(&self) -> bool { true } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"IPH","id":"HAFAS","v":"2040100","name":"VMT"}); req_json["ver"] = json!("1.34"); req_json["auth"] = json!({"type":"AID","aid":"t2h7u1e6r4i8n3g7e0n"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(VmtProfile {}, "Zeil", "Zeilfeld").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(VmtProfile {}, "190014", "167280").await } } railway-provider-hafas-0.1.2/src/profile/vor.rs000064400000000000000000000076411046102023000176170ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const BAHN_AND_S_BAHN: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Bahn & S-Bahn"), short: Cow::Borrowed("Bahn & S-Bahn"), }; pub const U_BAHN: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U-Bahn"), }; pub const STRASSENBAHN: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Straßenbahn"), short: Cow::Borrowed("Straßenbahn"), }; pub const STADTBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Stadtbus"), short: Cow::Borrowed("Stadtbus"), }; pub const REGIONALBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Regionalbus"), short: Cow::Borrowed("Regionalbus"), }; pub const FERNBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Fernbus"), short: Cow::Borrowed("Fernbus"), }; pub const SONSTIGE_BUSSE: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("sonstige Busse"), short: Cow::Borrowed("sonstige Busse"), }; pub const SEIL_: Product = Product { mode: Mode::Cablecar, name: Cow::Borrowed("Seil-/Zahnradbahn"), short: Cow::Borrowed("Seil-/Zahnradbahn"), }; pub const SCHIFF: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Schiff"), short: Cow::Borrowed("Schiff"), }; pub const AST: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Anrufsammeltaxi"), short: Cow::Borrowed("AST"), }; // TODO: Separate bahn and S-Bahn pub const PRODUCTS: &[&Product] = &[ &BAHN_AND_S_BAHN, &BAHN_AND_S_BAHN, &U_BAHN, &Product::unknown(), &STRASSENBAHN, &FERNBUS, ®IONALBUS, &STADTBUS, &SONSTIGE_BUSSE, &SEIL_, &SCHIFF, &AST, ]; } #[derive(Debug)] pub struct VorProfile; impl Profile for VorProfile { fn url(&self) -> &'static str { "https://anachb.vor.at/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Vienna } fn checksum_salt(&self) -> Option<&'static str> { Some("6633673735743766726667323938336A") } fn mic_mac(&self) -> bool { true } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"AND","id":"VAO","v":"","name":""}); req_json["ver"] = json!("1.27"); req_json["ext"] = json!("VAO.11"); // TODO: Two different auths? // req_json["auth"] = json!({"type":"AID","aid":"and20201hf7mcf9bv3nv8g5f"}); req_json["auth"] = json!({ "type": "USER", "aid": "and20201hf7mcf9bv3nv8g5f", "user": "mobile", "pw": "87a6f8ZbnBih32", }); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(VorProfile {}, "Zeill", "ZEILLERN").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(VorProfile {}, "431277900", "415003300").await } } railway-provider-hafas-0.1.2/src/profile/vos.rs000064400000000000000000000063401046102023000176130ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("ICE"), short: Cow::Borrowed("ICE"), }; pub const IC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("IC/EC"), short: Cow::Borrowed("IC/EC"), }; pub const IR: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("IR, sonstiger Schnellzug"), short: Cow::Borrowed("IR"), }; pub const NAHVERKEHR: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Nahverkehr"), short: Cow::Borrowed("Nahverkehr"), }; pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("S-Bahn"), short: Cow::Borrowed("S"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const SCHIFF: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Schiff"), short: Cow::Borrowed("Schiff"), }; pub const U: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U"), }; pub const T: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Tram"), short: Cow::Borrowed("T"), }; pub const AST: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Anrufverkehr"), short: Cow::Borrowed("AST"), }; pub const PRODUCTS: &[&Product] = &[&ICE, &IC, &IR, &NAHVERKEHR, &S, &BUS, &SCHIFF, &U, &T, &AST]; } #[derive(Debug)] pub struct VosProfile; impl Profile for VosProfile { fn url(&self) -> &'static str { "https://fahrplan.vos.info/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn checksum_salt(&self) -> Option<&'static str> { None } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"WEB","id":"SWO","name":"webapp"}); req_json["ver"] = json!("1.42"); req_json["auth"] = json!({"type":"AID","aid":"PnYowCQP7Tp1V"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(VosProfile {}, "Frer", "Freren Markt").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(VosProfile {}, "9071733", "9071574").await } } railway-provider-hafas-0.1.2/src/profile/vrn.rs000064400000000000000000000056561046102023000176220ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const RE: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("regional train"), short: Cow::Borrowed("RE/RB"), }; pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("urban train"), short: Cow::Borrowed("S"), }; pub const U: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("subway"), short: Cow::Borrowed("U"), }; pub const TRAM: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("tram"), short: Cow::Borrowed("Tram"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const TAXI: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("dial-a-ride"), short: Cow::Borrowed("taxi"), }; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("long-distance train"), short: Cow::Borrowed("ICE/IC/EC/EN"), }; pub const PRODUCTS: &[&Product] = &[ &ICE, &ICE, &ICE, &RE, &S, &BUS, &Product::unknown(), &U, &TRAM, &TAXI, ]; } #[derive(Debug)] pub struct VrnProfile; impl Profile for VrnProfile { fn url(&self) -> &'static str { "https://vrn.hafas.de/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn checksum_salt(&self) -> Option<&'static str> { None } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"IPH","id":"DB-REGIO-VRN","v":"6000400","name":"VRN"}); req_json["ver"] = json!("1.34"); req_json["ext"] = json!("DB.R19.04.a"); req_json["auth"] = json!({"type":"AID","aid":"p091VRNZz79KtUz5"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(VrnProfile {}, "Frei", "Freinsheim").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(VrnProfile {}, "8000236", "8003932").await } } railway-provider-hafas-0.1.2/src/profile/vsn.rs000064400000000000000000000064751046102023000176230ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const ICE: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("InterCityExpress"), short: Cow::Borrowed("ICE"), }; pub const IC: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("Fernzug"), short: Cow::Borrowed("IC/EC/CNL"), }; pub const RE: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("RegionalExpress & InterRegio"), short: Cow::Borrowed("RE/IR"), }; pub const NV: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Nahverhehr"), short: Cow::Borrowed("NV"), }; pub const S: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("S-Bahn"), short: Cow::Borrowed("S"), }; pub const BUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Bus"), short: Cow::Borrowed("Bus"), }; pub const F: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Schiff"), short: Cow::Borrowed("F"), }; pub const U: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U"), }; pub const T: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Straßen-/Stadtbahn"), short: Cow::Borrowed("T"), }; pub const AST: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Anruf-Sammel-Taxi"), short: Cow::Borrowed("AST"), }; pub const PRODUCTS: &[&Product] = &[&ICE, &IC, &RE, &NV, &S, &BUS, &F, &U, &T, &AST]; } #[derive(Debug)] pub struct VsnProfile; impl Profile for VsnProfile { fn url(&self) -> &'static str { "https://fahrplaner.vsninfo.de/hafas/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Berlin } fn checksum_salt(&self) -> Option<&'static str> { Some("SP31mBufSyCLmNxp") } fn mic_mac(&self) -> bool { true } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"IPA","id":"VSN","v":"5030100","name":"vsn"}); req_json["ver"] = json!("1.42"); req_json["auth"] = json!({"type":"AID","aid":"Mpf5UPC0DmzV8jkg"}); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(VsnProfile {}, "Morin", "Moringen Schule").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(VsnProfile {}, "9014418", "9093627").await } } railway-provider-hafas-0.1.2/src/profile/vvt.rs000064400000000000000000000070521046102023000176240ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const BAHN_AND_S: Product = Product { mode: Mode::RegionalTrain, name: Cow::Borrowed("Bahn & S-Bahn"), short: Cow::Borrowed("Bahn & S"), }; pub const U: Product = Product { mode: Mode::Subway, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U"), }; pub const STRASSENBAHN: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Straßenbahn"), short: Cow::Borrowed("Straßenbahn"), }; pub const STADTBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Stadtbus"), short: Cow::Borrowed("Stadtbus"), }; pub const REGIONALBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Regionalbus"), short: Cow::Borrowed("Regionalbus"), }; pub const FERNBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Fernbus"), short: Cow::Borrowed("Fernbus"), }; pub const SONSTIGE_BUSSE: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("sonstige Busse"), short: Cow::Borrowed("sonstige Busse"), }; pub const SEIL_: Product = Product { mode: Mode::Cablecar, name: Cow::Borrowed("Seil-/Zahnradbahn"), short: Cow::Borrowed("Seil-/Zahnradbahn"), }; pub const SCHIFF: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Schiff"), short: Cow::Borrowed("Schiff"), }; pub const AST: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Anrufsammeltaxi"), short: Cow::Borrowed("AST"), }; // TODO: Deduplicate Bahn and S-Bahn pub const PRODUCTS: &[&Product] = &[ &BAHN_AND_S, &BAHN_AND_S, &U, &Product::unknown(), &STRASSENBAHN, &FERNBUS, ®IONALBUS, &STADTBUS, &SEIL_, &SCHIFF, &AST, &SONSTIGE_BUSSE, ]; } #[derive(Debug)] pub struct VvtProfile; impl Profile for VvtProfile { fn url(&self) -> &'static str { "https://smartride.vvt.at/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Vienna } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({ "type": "WEB", "id": "VAO", "name": "webapp" }); req_json["ver"] = json!("1.39"); req_json["auth"] = json!({ "type": "AID", "aid": "wf7mcf9bv3nv8g5f" }); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(VvtProfile {}, "Unter", "Unter-Danegg Ort").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(VvtProfile {}, "476152300", "476603100").await } } railway-provider-hafas-0.1.2/src/profile/vvv.rs000064400000000000000000000076271046102023000176360ustar 00000000000000use crate::{Product, Profile}; use serde_json::{json, Value}; use std::collections::HashMap; mod products { use crate::{Mode, Product}; use std::borrow::Cow; pub const BAHN_AND_S_BAHN: Product = Product { mode: Mode::HighSpeedTrain, name: Cow::Borrowed("Bahn & S-Bahn"), short: Cow::Borrowed("Bahn & S-Bahn"), }; pub const U_BAHN: Product = Product { mode: Mode::SuburbanTrain, name: Cow::Borrowed("U-Bahn"), short: Cow::Borrowed("U-Bahn"), }; pub const STRASSENBAHN: Product = Product { mode: Mode::Tram, name: Cow::Borrowed("Straßenbahn"), short: Cow::Borrowed("Straßenbahn"), }; pub const STADTBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Stadtbus"), short: Cow::Borrowed("Stadtbus"), }; pub const REGIONALBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Regionalbus"), short: Cow::Borrowed("Regionalbus"), }; pub const FERNBUS: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("Fernbus"), short: Cow::Borrowed("Fernbus"), }; pub const SONSTIGE_BUSSE: Product = Product { mode: Mode::Bus, name: Cow::Borrowed("sonstige Busse"), short: Cow::Borrowed("sonstige Busse"), }; pub const SEIL_: Product = Product { mode: Mode::Cablecar, name: Cow::Borrowed("Seil-/Zahnradbahn"), short: Cow::Borrowed("Seil-/Zahnradbahn"), }; pub const SCHIFF: Product = Product { mode: Mode::Ferry, name: Cow::Borrowed("Schiff"), short: Cow::Borrowed("Schiff"), }; pub const AST: Product = Product { mode: Mode::OnDemand, name: Cow::Borrowed("Anrufsammeltaxi"), short: Cow::Borrowed("AST"), }; // TODO: Deduplicate Bahn and S-Bahn pub const PRODUCTS: &[&Product] = &[ &BAHN_AND_S_BAHN, &BAHN_AND_S_BAHN, &U_BAHN, &Product::unknown(), &STRASSENBAHN, &FERNBUS, ®IONALBUS, &STADTBUS, &SEIL_, &SCHIFF, &AST, &SONSTIGE_BUSSE, ]; } #[derive(Debug)] pub struct VvvProfile; impl Profile for VvvProfile { fn url(&self) -> &'static str { "https://fahrplan.vmobil.at/bin/mgate.exe" } fn language(&self) -> &'static str { "de" } fn timezone(&self) -> chrono_tz::Tz { chrono_tz::Europe::Vienna } fn checksum_salt(&self) -> Option<&'static str> { Some("6633673735743766726667323938336A") } fn mic_mac(&self) -> bool { true } fn refresh_journey_use_out_recon_l(&self) -> bool { true } fn products(&self) -> &'static [&'static Product] { products::PRODUCTS } fn prepare_body(&self, req_json: &mut Value) { req_json["client"] = json!({"type":"AND","id":"VAO","v":"","name":""}); req_json["ver"] = json!("1.27"); req_json["ext"] = json!("VAO.11"); // TODO: Two different auths // req_json["auth"] = json!({"type":"AID","aid":"and20201hf7mcf9bv3nv8g5f"}); req_json["auth"] = json!({ "aid": "and20201hf7mcf9bv3nv8g5f", "type": "USER", "user": "mobile", }); } fn prepare_headers(&self, headers: &mut HashMap<&str, &str>) { headers.insert("User-Agent", "my-awesome-e5f276d8fe6cprogram"); } fn price_currency(&self) -> &'static str { "EUR" } } #[cfg(test)] mod test { use std::error::Error; use crate::profile::test::{check_journey, check_search}; use super::*; #[tokio::test] async fn test_search() -> Result<(), Box> { check_search(VvvProfile {}, "Ober", "Ober-Piesting Bahnhof").await } #[tokio::test] async fn test_path_available() -> Result<(), Box> { check_journey(VvvProfile {}, "480031300", "480195700").await } } railway-provider-hafas-0.1.2/src/serialize.rs000064400000000000000000000011241046102023000173260ustar 00000000000000pub(crate) mod duration { use chrono::Duration; use serde::{Deserialize, Deserializer, Serializer}; pub(crate) fn serialize(v: &Option, s: S) -> Result where S: Serializer, { if let Some(d) = v { s.serialize_some(&d.num_minutes()) } else { s.serialize_none() } } pub(crate) fn deserialize<'de, D>(d: D) -> Result, D::Error> where D: Deserializer<'de>, { let v = Option::::deserialize(d)?; Ok(v.map(Duration::minutes)) } }