continue working on traveldings (get live checkin thing working maybe??)
This commit is contained in:
parent
258d4639d7
commit
bd3674accf
5 changed files with 274 additions and 30 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1722,6 +1722,7 @@ dependencies = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ serde = { version = "1.0.209", features = ["derive"] }
|
||||||
serde_json = "1.0.128"
|
serde_json = "1.0.128"
|
||||||
reqwest = {version = "0.12.7", default-features = false, features = ["rustls-tls", "charset", "http2"]}
|
reqwest = {version = "0.12.7", default-features = false, features = ["rustls-tls", "charset", "http2"]}
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
thiserror = "1"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
chrono = { version = "0.4", features = ["serde"]}
|
chrono = { version = "0.4", features = ["serde"]}
|
||||||
clap = { version = "4.5", features = ["derive"]}
|
clap = { version = "4.5", features = ["derive"]}
|
||||||
|
|
|
@ -1,9 +1,182 @@
|
||||||
use crate::traewelling::TraewellingClient;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use chrono::Local;
|
||||||
|
use reqwest::StatusCode;
|
||||||
|
use serde::Serialize;
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
|
use crate::traewelling::{
|
||||||
|
model::{JsonableData, Status, StopJourneyPart},
|
||||||
|
RequestErr, TraewellingClient,
|
||||||
|
};
|
||||||
|
|
||||||
pub async fn get_current_journey() -> anyhow::Result<()> {
|
pub async fn get_current_journey() -> anyhow::Result<()> {
|
||||||
let client = TraewellingClient::new()?;
|
let client = TraewellingClient::new()?;
|
||||||
|
|
||||||
println!("active: {:#?}", client.get_active_checkin().await?);
|
let mut state;
|
||||||
|
let mut cur_active_checkin = None;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match client.get_active_checkin().await {
|
||||||
|
Ok(status) => {
|
||||||
|
cur_active_checkin = Some(status);
|
||||||
|
state = State::Live;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
if err == RequestErr::WithStatus(StatusCode::NOT_FOUND) {
|
||||||
|
state = State::NoCheckin;
|
||||||
|
cur_active_checkin = None;
|
||||||
|
} else {
|
||||||
|
state = State::NoConnectionOrSomethingElseDoesntWork;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match (state, &cur_active_checkin) {
|
||||||
|
(State::Live | State::NoConnectionOrSomethingElseDoesntWork, Some(status)) => {
|
||||||
|
let live = state == State::Live;
|
||||||
|
let out = CurrentJourneyOutput::new(&status, live);
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
serde_json::to_string(&out)
|
||||||
|
.expect("serde should not make you sad but it does because it's serde")
|
||||||
|
);
|
||||||
|
sleep(Duration::from_secs(20)).await;
|
||||||
|
}
|
||||||
|
(_, None) | (State::NoCheckin, Some(_)) => {
|
||||||
|
println!("null");
|
||||||
|
sleep(Duration::from_secs(60)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||||
|
enum State {
|
||||||
|
Live,
|
||||||
|
NoConnectionOrSomethingElseDoesntWork,
|
||||||
|
NoCheckin,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct CurrentJourneyOutput {
|
||||||
|
live: bool,
|
||||||
|
// Journey progress, 0.0-1.0
|
||||||
|
progress: Option<f32>,
|
||||||
|
time_left: Option<i64>,
|
||||||
|
icon: String,
|
||||||
|
|
||||||
|
// Invalid data received?
|
||||||
|
departure_err: bool,
|
||||||
|
departure_planned: Option<i64>,
|
||||||
|
departure_real: Option<i64>,
|
||||||
|
departure_station: String,
|
||||||
|
departure_ril100: Option<String>,
|
||||||
|
departure_platform_data_available: bool,
|
||||||
|
departure_platform_planned: Option<String>,
|
||||||
|
departure_platform_real: Option<String>,
|
||||||
|
|
||||||
|
// Invalid data received?
|
||||||
|
arrival_err: bool,
|
||||||
|
arrival_planned: Option<i64>,
|
||||||
|
arrival_real: Option<i64>,
|
||||||
|
arrival_station: String,
|
||||||
|
arrival_ril100: Option<String>,
|
||||||
|
arrival_platform_data_available: bool,
|
||||||
|
arrival_platform_planned: Option<String>,
|
||||||
|
arrival_platform_real: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CurrentJourneyOutput {
|
||||||
|
fn new(checkin: &Status, live: bool) -> Self {
|
||||||
|
let JsonableData {
|
||||||
|
time_err: departure_err,
|
||||||
|
time_planned: departure_planned,
|
||||||
|
time_real: departure_real,
|
||||||
|
station: departure_station,
|
||||||
|
ril100: departure_ril100,
|
||||||
|
platform_data_available: departure_platform_data_available,
|
||||||
|
platform_planned: departure_platform_planned,
|
||||||
|
platform_real: departure_platform_real,
|
||||||
|
} = checkin.train.origin.get_time_data(StopJourneyPart::Origin);
|
||||||
|
let JsonableData {
|
||||||
|
time_err: arrival_err,
|
||||||
|
time_planned: arrival_planned,
|
||||||
|
time_real: arrival_real,
|
||||||
|
station: arrival_station,
|
||||||
|
ril100: arrival_ril100,
|
||||||
|
platform_data_available: arrival_platform_data_available,
|
||||||
|
platform_planned: arrival_platform_planned,
|
||||||
|
platform_real: arrival_platform_real,
|
||||||
|
} = checkin
|
||||||
|
.train
|
||||||
|
.destination
|
||||||
|
.get_time_data(StopJourneyPart::Destination);
|
||||||
|
|
||||||
|
let (progress, time_left) = if !departure_err && !arrival_err {
|
||||||
|
let departure = departure_real.unwrap_or(departure_planned.unwrap());
|
||||||
|
let arrival = arrival_real.unwrap_or(arrival_planned.unwrap());
|
||||||
|
let dur = arrival - departure;
|
||||||
|
|
||||||
|
let now = Local::now().timestamp();
|
||||||
|
|
||||||
|
let progress = ((now - departure) as f32) / dur as f32;
|
||||||
|
let time_left = arrival - now;
|
||||||
|
|
||||||
|
(Some(progress), Some(time_left))
|
||||||
|
} else {
|
||||||
|
(None, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
let icon = match checkin.train.category.as_str() {
|
||||||
|
"nationalExpress" | "national" => "longDistanceTrans",
|
||||||
|
"regionalExp" | "regional" => "regionalTrans",
|
||||||
|
"suburban" => "localTrans",
|
||||||
|
"subway" => "subTrans",
|
||||||
|
"bus" => "bus",
|
||||||
|
"tram" => "tram",
|
||||||
|
"ferry" => "ferry",
|
||||||
|
_ => "other",
|
||||||
|
}
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
CurrentJourneyOutput {
|
||||||
|
live,
|
||||||
|
progress,
|
||||||
|
time_left,
|
||||||
|
icon,
|
||||||
|
departure_err,
|
||||||
|
departure_planned,
|
||||||
|
departure_real,
|
||||||
|
departure_station,
|
||||||
|
departure_ril100,
|
||||||
|
departure_platform_data_available,
|
||||||
|
departure_platform_planned,
|
||||||
|
departure_platform_real,
|
||||||
|
arrival_err,
|
||||||
|
arrival_planned,
|
||||||
|
arrival_real,
|
||||||
|
arrival_station,
|
||||||
|
arrival_ril100,
|
||||||
|
arrival_platform_data_available,
|
||||||
|
arrival_platform_planned,
|
||||||
|
arrival_platform_real,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TransportType {
|
||||||
|
// FV, ob jetzt NJ, IC, ICE... egal
|
||||||
|
LongDistanceTrans,
|
||||||
|
RegionalTrans,
|
||||||
|
// S-bahn...
|
||||||
|
LocalTrans,
|
||||||
|
// U-bahn
|
||||||
|
SubTrans,
|
||||||
|
Bus,
|
||||||
|
Tram,
|
||||||
|
Ferry,
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::{fmt, fs};
|
||||||
use model::{Container, Status};
|
use model::{Container, Status};
|
||||||
use reqwest::{
|
use reqwest::{
|
||||||
header::{self, HeaderMap},
|
header::{self, HeaderMap},
|
||||||
Client, ClientBuilder,
|
Client, ClientBuilder, StatusCode,
|
||||||
};
|
};
|
||||||
|
|
||||||
const KEY_PATH: &str = "/home/jade/Docs/traveldings-key";
|
const KEY_PATH: &str = "/home/jade/Docs/traveldings-key";
|
||||||
|
@ -19,7 +19,6 @@ impl TraewellingClient {
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
let token = fs::read_to_string(KEY_PATH)?;
|
let token = fs::read_to_string(KEY_PATH)?;
|
||||||
let key = header::HeaderValue::from_str(&format!("Bearer {token}"))?;
|
let key = header::HeaderValue::from_str(&format!("Bearer {token}"))?;
|
||||||
println!("meow");
|
|
||||||
headers.insert("Authorization", key);
|
headers.insert("Authorization", key);
|
||||||
headers.insert(
|
headers.insert(
|
||||||
header::ACCEPT,
|
header::ACCEPT,
|
||||||
|
@ -33,16 +32,17 @@ impl TraewellingClient {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_active_checkin(&self) -> anyhow::Result<Status> {
|
pub async fn get_active_checkin(&self) -> Result<Status, RequestErr> {
|
||||||
let txt = self
|
let res = self
|
||||||
.client
|
.client
|
||||||
.get(Self::fmt_url("user/statuses/active"))
|
.get(Self::fmt_url("user/statuses/active"))
|
||||||
.send()
|
.send()
|
||||||
.await?
|
|
||||||
.text()
|
|
||||||
.await?;
|
.await?;
|
||||||
|
if res.status() != StatusCode::OK {
|
||||||
|
return Err(RequestErr::WithStatus(res.status()));
|
||||||
|
}
|
||||||
|
|
||||||
println!("{txt}");
|
let txt = res.text().await?;
|
||||||
|
|
||||||
let res: Container<Status> = serde_json::de::from_str(&txt)?;
|
let res: Container<Status> = serde_json::de::from_str(&txt)?;
|
||||||
Ok(res.data)
|
Ok(res.data)
|
||||||
|
@ -53,4 +53,35 @@ impl TraewellingClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
|
||||||
|
pub enum RequestErr {
|
||||||
|
#[error("Couldn't deserialize the json :(")]
|
||||||
|
DeserializationError,
|
||||||
|
#[error("an error related to connect happened!!")]
|
||||||
|
RelatedToConnect,
|
||||||
|
#[error("error haz status: {0}")]
|
||||||
|
WithStatus(StatusCode),
|
||||||
|
#[error("fuck if i know what went wrong :333 am silly ")]
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<serde_json::Error> for RequestErr {
|
||||||
|
fn from(value: serde_json::Error) -> Self {
|
||||||
|
eprintln!("serde error: {value:?}");
|
||||||
|
Self::DeserializationError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<reqwest::Error> for RequestErr {
|
||||||
|
fn from(value: reqwest::Error) -> Self {
|
||||||
|
if let Some(status) = value.status() {
|
||||||
|
Self::WithStatus(status)
|
||||||
|
} else if value.is_connect() {
|
||||||
|
Self::RelatedToConnect
|
||||||
|
} else {
|
||||||
|
Self::Other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub mod model;
|
pub mod model;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use chrono::{DateTime, FixedOffset};
|
use chrono::{DateTime, FixedOffset, Timelike};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
|
@ -9,39 +9,77 @@ pub struct Container<D> {
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Status {
|
pub struct Status {
|
||||||
train: TransportResource,
|
pub train: TransportResource,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TransportResource {
|
pub struct TransportResource {
|
||||||
category: String,
|
pub category: String,
|
||||||
line_name: String,
|
pub line_name: String,
|
||||||
distance: u32,
|
pub distance: u32,
|
||||||
duration: u32,
|
pub duration: u32,
|
||||||
operator: OperatorResource,
|
pub operator: Option<OperatorResource>,
|
||||||
origin: StopOverResource,
|
pub origin: StopOverResource,
|
||||||
destination: StopOverResource,
|
pub destination: StopOverResource,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct StopOverResource {
|
pub struct StopOverResource {
|
||||||
name: String,
|
pub name: String,
|
||||||
ril_identifier: Option<String>,
|
pub ril_identifier: Option<String>,
|
||||||
arrival: Option<DateTime<FixedOffset>>,
|
pub arrival_planned: Option<DateTime<FixedOffset>>,
|
||||||
arrival_planned: Option<DateTime<FixedOffset>>,
|
pub arrival_real: Option<DateTime<FixedOffset>>,
|
||||||
arrival_real: Option<DateTime<FixedOffset>>,
|
pub departure_planned: Option<DateTime<FixedOffset>>,
|
||||||
departure: Option<DateTime<FixedOffset>>,
|
pub departure_real: Option<DateTime<FixedOffset>>,
|
||||||
departure_planned: Option<DateTime<FixedOffset>>,
|
pub platform: Option<String>,
|
||||||
departure_real: Option<DateTime<FixedOffset>>,
|
pub departure_platform_planned: Option<String>,
|
||||||
platform: Option<String>,
|
pub departure_platform_real: Option<String>,
|
||||||
departure_platform_planned: Option<String>,
|
}
|
||||||
departure_platform_real: Option<String>,
|
|
||||||
|
// ????
|
||||||
|
pub struct JsonableData {
|
||||||
|
pub time_err: bool,
|
||||||
|
pub time_planned: Option<i64>,
|
||||||
|
pub time_real: Option<i64>,
|
||||||
|
pub station: String,
|
||||||
|
pub ril100: Option<String>,
|
||||||
|
pub platform_data_available: bool,
|
||||||
|
pub platform_planned: Option<String>,
|
||||||
|
pub platform_real: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// What the meaning of the stop in the journey is
|
||||||
|
pub enum StopJourneyPart {
|
||||||
|
Origin,
|
||||||
|
Destination,
|
||||||
|
}
|
||||||
|
impl StopOverResource {
|
||||||
|
pub fn get_time_data(&self, journey_part: StopJourneyPart) -> JsonableData {
|
||||||
|
let (time_planned, time_real) = match journey_part {
|
||||||
|
StopJourneyPart::Origin => (self.departure_planned, self.departure_real),
|
||||||
|
StopJourneyPart::Destination => (self.arrival_planned, self.arrival_real),
|
||||||
|
};
|
||||||
|
|
||||||
|
let time_err = time_planned == None;
|
||||||
|
|
||||||
|
JsonableData {
|
||||||
|
time_err,
|
||||||
|
time_planned: time_planned.map(|ts| ts.timestamp()),
|
||||||
|
time_real: time_real.map(|ts| ts.timestamp()),
|
||||||
|
station: self.name.clone(),
|
||||||
|
ril100: self.ril_identifier.clone(),
|
||||||
|
platform_data_available: self.departure_platform_planned.is_none()
|
||||||
|
|| self.departure_platform_real.is_none(),
|
||||||
|
platform_planned: self.departure_platform_planned.clone(),
|
||||||
|
platform_real: self.departure_platform_real.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct OperatorResource {
|
pub struct OperatorResource {
|
||||||
name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue