Compare commits

..

3 commits

Author SHA1 Message Date
Schrottkatze b4dfef1d6d
traveldings stuff! 2024-09-11 02:09:08 +02:00
Schrottkatze 54a1e34fa6
refactor bottomBar smol bit 2024-09-10 22:07:27 +02:00
Schrottkatze bd3674accf
continue working on traveldings (get live checkin thing working maybe??) 2024-09-10 21:37:18 +02:00
9 changed files with 375 additions and 49 deletions

1
Cargo.lock generated
View file

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

View file

@ -1,3 +1,6 @@
(include "bottomBar/workspaces.yuck")
(include "bottomBar/traveldings.yuck")
(defwindow bottomBar
:monitor 0
:stacking "fg"
@ -19,7 +22,7 @@
)
(box
:halign "center"
(label :text "mid")
(traveldings)
)
(box
:halign "end"
@ -39,21 +42,3 @@
)
)
)
(defwidget workspaceWidget []
(box
:class "workspaces"
(for workspace in workspaces
(button
:class "${workspace.urgent ? "urgent" : ""} ${workspace.focused ? "focused" : 0}"
:onclick "swaymsg workspace ${workspace.name}"
(label :text "${workspace.name}")
)
)
)
)
(deflisten workspaces
:initial "[]"
"bar-ws-monitor"
)

View file

@ -0,0 +1,50 @@
(defwidget traveldings []
(revealer
:class "traveldings"
:transition "crossfade"
:reveal { traveldings_data != "null" }
(traveldingsBarWidget)
)
)
(defwidget traveldingsBarWidget []
(overlay
(box
:width 640
(label
:halign "start"
:text "${traveldings_data.line} -> ${strlength(traveldings_data.arrival_station) > 24 ? "${substring(traveldings_data.arrival_station, 0, 24)}…" : traveldings_data.arrival_station}${traveldings_data.arrival_platform_data_available ? " (Gl. ${traveldings_data.arrival_platform_real})" : ""}"
)
(label
:halign "end"
:text { traveldings_data.time_left >= 3600 ? formattime(traveldings_data.time_left, "noch %-Hh %-Mmin", "Etc/UTC") : formattime(traveldings_data.time_left, "noch %-Mmin", "Etc/UTC") }
)
)
(box
(progress
:value {traveldings_data.progress * 100}
:orientation "horizontal"
)
)
)
)
(defwindow traveldingsWindow
:monitor 0
:stacking "overlay"
:exclusive "false"
:geometry (geometry
:x "0%"
:y "6.6%"
:width "30%"
:height "20%"
:anchor "bottom center")
(box
:class "traveldingsWindow"
(label :text "TODO"))
)
(deflisten traveldings_data
:initial "null"
"traveldings current"
)

View file

@ -0,0 +1,17 @@
(defwidget workspaceWidget []
(box
:class "workspaces"
(for workspace in workspaces
(button
:class "${workspace.urgent ? "urgent" : ""} ${workspace.focused ? "focused" : 0}"
:onclick "swaymsg workspace ${workspace.name}"
(label :text "${workspace.name}")
)
)
)
)
(deflisten workspaces
:initial "[]"
"bar-ws-monitor"
)

View file

@ -14,3 +14,20 @@ label {
.workspaces button.focused {
background-color: #504935;
}
.traveldings progressbar trough {
border: none;
background-color: #3c3836;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.traveldings progressbar progress {
background-color: #79740e;
border-bottom-left-radius: 0;
}
.traveldingsWindow {
border-radius: 15px;
}

View file

@ -8,6 +8,7 @@ serde = { version = "1.0.209", features = ["derive"] }
serde_json = "1.0.128"
reqwest = {version = "0.12.7", default-features = false, features = ["rustls-tls", "charset", "http2"]}
tokio = { version = "1", features = ["full"] }
thiserror = "1"
anyhow = "1"
chrono = { version = "0.4", features = ["serde"]}
clap = { version = "4.5", features = ["derive"]}

View file

@ -1,9 +1,184 @@
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<()> {
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(())
}
#[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,
line: 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,
line: checkin.train.line_name.clone(),
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 reqwest::{
header::{self, HeaderMap},
Client, ClientBuilder,
Client, ClientBuilder, StatusCode,
};
const KEY_PATH: &str = "/home/jade/Docs/traveldings-key";
@ -19,7 +19,6 @@ impl TraewellingClient {
let mut headers = HeaderMap::new();
let token = fs::read_to_string(KEY_PATH)?;
let key = header::HeaderValue::from_str(&format!("Bearer {token}"))?;
println!("meow");
headers.insert("Authorization", key);
headers.insert(
header::ACCEPT,
@ -33,16 +32,17 @@ impl TraewellingClient {
})
}
pub async fn get_active_checkin(&self) -> anyhow::Result<Status> {
let txt = self
pub async fn get_active_checkin(&self) -> Result<Status, RequestErr> {
let res = self
.client
.get(Self::fmt_url("user/statuses/active"))
.send()
.await?
.text()
.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)?;
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;

View file

@ -1,4 +1,4 @@
use chrono::{DateTime, FixedOffset};
use chrono::{DateTime, FixedOffset, Timelike};
use serde::Deserialize;
#[derive(Deserialize, Debug)]
@ -9,39 +9,88 @@ pub struct Container<D> {
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Status {
train: TransportResource,
pub train: TransportResource,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct TransportResource {
category: String,
line_name: String,
distance: u32,
duration: u32,
operator: OperatorResource,
origin: StopOverResource,
destination: StopOverResource,
pub category: String,
pub line_name: String,
pub distance: u32,
pub duration: u32,
pub operator: Option<OperatorResource>,
pub origin: StopOverResource,
pub destination: StopOverResource,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct StopOverResource {
name: String,
ril_identifier: Option<String>,
arrival: Option<DateTime<FixedOffset>>,
arrival_planned: Option<DateTime<FixedOffset>>,
arrival_real: Option<DateTime<FixedOffset>>,
departure: Option<DateTime<FixedOffset>>,
departure_planned: Option<DateTime<FixedOffset>>,
departure_real: Option<DateTime<FixedOffset>>,
platform: Option<String>,
departure_platform_planned: Option<String>,
departure_platform_real: Option<String>,
pub name: String,
pub ril_identifier: Option<String>,
pub arrival_planned: Option<DateTime<FixedOffset>>,
pub arrival_real: Option<DateTime<FixedOffset>>,
pub departure_planned: Option<DateTime<FixedOffset>>,
pub departure_real: Option<DateTime<FixedOffset>>,
pub platform: Option<String>,
pub departure_platform_planned: Option<String>,
pub departure_platform_real: Option<String>,
pub arrival_platform_planned: Option<String>,
pub arrival_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, platform_planned, platform_real) = match journey_part {
StopJourneyPart::Origin => (
self.departure_planned,
self.departure_real,
self.departure_platform_planned.clone(),
self.departure_platform_real.clone(),
),
StopJourneyPart::Destination => (
self.arrival_planned,
self.arrival_real,
self.arrival_platform_planned.clone(),
self.arrival_platform_real.clone(),
),
};
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: platform_planned.is_some() || platform_real.is_some(),
platform_planned,
platform_real,
}
}
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct OperatorResource {
name: String,
pub name: String,
}