continue working on traveldings (get live checkin thing working maybe??)

This commit is contained in:
Schrottkatze 2024-09-10 21:37:18 +02:00
parent 258d4639d7
commit bd3674accf
Signed by: schrottkatze
SSH key fingerprint: SHA256:hXb3t1vINBFCiDCmhRABHX5ocdbLiKyCdKI4HK2Rbbc
5 changed files with 274 additions and 30 deletions

1
Cargo.lock generated
View file

@ -1722,6 +1722,7 @@ dependencies = [
"reqwest", "reqwest",
"serde", "serde",
"serde_json", "serde_json",
"thiserror",
"tokio", "tokio",
] ]

View file

@ -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"]}

View file

@ -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,
}

View file

@ -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;

View file

@ -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,
} }