fuckass shit

This commit is contained in:
2025-07-03 19:55:57 +02:00
parent 243d90f261
commit d09800adc8
4 changed files with 320 additions and 0 deletions

5
.gitignore vendored
View File

@@ -371,3 +371,8 @@ compile_commands.json
CTestTestfile.cmake
_deps
# Added by cargo
/target

12
Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "ctable"
version = "0.1.0"
edition = "2024"
[dependencies]
dotenv = "0.15"
chrono = "0.4"
tui = "0.19"
crossterm = "0.26"
anyhow = "1.0"
untis = "0.3.0"

42
src/get/get.rs Normal file
View File

@@ -0,0 +1,42 @@
/// Code straight copied from the docs.
/// Should work.
use dotenvy::dotenv;
use std::env;
fn main() -> Result<(), untis::Error> {
// Get the school by its id.
let school = untis::schools::get_by_id(&42)?;
// Log in with your credentials. The school's details are filled in automatically.
let result = school.client_login(
&env::var("UNTIS_USERNAME")?,
&env::var("UNTIS_PASSWORD")? // avoid password in plaintext cause its fucking stupid as fuck. use env instead cuase securrrrre.
);
let mut client: untis::Client;
// Match the result to handle specific error cases.
match result {
Ok(v) => client = v,
Err(untis::Error::Rpc(err)) => {
if err.code == untis::jsonrpc::ErrorCode::InvalidCredentials.as_isize() {
println!("Invalid credentials");
}
return Err(untis::Error::Rpc(err));
}
Err(err) => return Err(err)?,
};
let date = chrono::Local::now().date_naive() + chrono::Duration::weeks(2);
// Get the client's own timetable until 2 weeks from now.
let timetable = client.own_timetable_until(&date.into())?;
for lesson in timetable {
println!("{:?}", lesson);
}
Ok(())
} // fn main

261
src/main.rs Normal file
View File

@@ -0,0 +1,261 @@
use anyhow::{Context, Result};
use chrono::{Datelike, Duration, Local, NaiveDate};
use crossterm::{
event::{self, Event, KeyCode},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use dotenv::dotenv;
use std::{env, io};
use tui::{
backend::CrosstermBackend,
layout::{Constraint, Direction, Layout},
style::{Color, Modifier, Style},
text::{Span, Spans},
widgets::{Block, Borders, Paragraph},
Terminal,
};
use untis_rs::Client;
struct App {
current_date: NaiveDate,
timetable: Vec<untis_rs::models::Period>,
}
impl App {
fn new() -> Self {
let current_date = Local::now().date_naive();
App {
current_date,
timetable: Vec::new(),
}
}
fn fetch_timetable(&mut self) -> Result<()> {
dotenv().ok();
let username = env::var("UNTIS_USERNAME").context("UNTIS_USERNAME not set")?;
let password = env::var("UNTIS_PASSWORD").context("UNTIS_PASSWORD not set")?;
let school = env::var("UNTIS_SCHOOL").context("UNTIS_SCHOOL not set")?;
let server = env::var("UNTIS_SERVER").context("UNTIS_SERVER not set")?;
let mut client = Client::new(&username, &password, &school, &server);
client.login()?;
// Get start and end of the current week (Monday to Friday)
let start = self
.current_date
.pred_opt()
.unwrap()
.week(chrono::Weekday::Mon)
.first_day();
let end = start + Duration::days(4);
self.timetable = client.get_timetable(start, end)?;
Ok(())
}
fn next_week(&mut self) {
self.current_date += Duration::weeks(1);
}
fn prev_week(&mut self) {
self.current_date -= Duration::weeks(1);
}
fn get_week_range(&self) -> (NaiveDate, NaiveDate) {
let start = self
.current_date
.pred_opt()
.unwrap()
.week(chrono::Weekday::Mon)
.first_day();
let end = start + Duration::days(4);
(start, end)
}
}
fn main() -> Result<()> {
// Setup terminal
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
// Create app
let mut app = App::new();
if let Err(e) = app.fetch_timetable() {
println!("Failed to fetch timetable: {}", e);
}
// Main loop
let res = run_app(&mut terminal, &mut app);
// Cleanup terminal
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
terminal.show_cursor()?;
if let Err(err) = res {
println!("Error: {:?}", err);
}
Ok(())
}
fn run_app<B: tui::backend::Backend>(
terminal: &mut Terminal<B>,
app: &mut App,
) -> Result<(), anyhow::Error> {
loop {
terminal.draw(|f| ui(f, app))?;
if let Event::Key(key) = event::read()? {
match key.code {
KeyCode::Char('q') => return Ok(()),
KeyCode::Char('n') => {
app.next_week();
if let Err(e) = app.fetch_timetable() {
println!("Failed to fetch timetable: {}", e);
}
}
KeyCode::Char('p') => {
app.prev_week();
if let Err(e) = app.fetch_timetable() {
println!("Failed to fetch timetable: {}", e);
}
}
_ => {}
}
}
}
}
fn ui<B: tui::backend::Backend>(f: &mut tui::Frame<B>, app: &App) {
let (week_start, week_end) = app.get_week_range();
let title = format!(
"Timetable - Week {} ({} - {})",
week_start.iso_week().week(),
week_start.format("%d.%m.%Y"),
week_end.format("%d.%m.%Y")
);
// Create a layout
let chunks = Layout::default()
.direction(Direction::Vertical)
.margin(1)
.constraints([Constraint::Length(3), Constraint::Min(0)].as_ref())
.split(f.size());
// Title block
let title_block = Block::default()
.borders(Borders::ALL)
.style(Style::default().fg(Color::White));
let title_paragraph = Paragraph::new(title)
.block(title_block)
.style(Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD));
f.render_widget(title_paragraph, chunks[0]);
// Timetable content
let mut periods_by_day: Vec<Vec<&untis_rs::models::Period>> = vec![vec![]; 5]; // Monday to Friday
for period in &app.timetable {
let weekday = period.start_time.weekday().num_days_from_monday();
if weekday < 5 {
periods_by_day[weekday as usize].push(period);
}
}
let day_chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints(
[
Constraint::Percentage(20),
Constraint::Percentage(20),
Constraint::Percentage(20),
Constraint::Percentage(20),
Constraint::Percentage(20),
]
.as_ref(),
)
.split(chunks[1]);
let days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"];
for (i, day) in days.iter().enumerate() {
let day_block = Block::default()
.title(*day)
.borders(Borders::ALL)
.style(Style::default().fg(Color::White));
let mut day_content = Vec::new();
for period in &periods_by_day[i] {
let start = period.start_time.format("%H:%M").to_string();
let end = period.end_time.format("%H:%M").to_string();
let subject = period
.subjects
.first()
.map(|s| s.name.clone())
.unwrap_or_default();
let teacher = period
.teachers
.first()
.map(|t| t.name.clone())
.unwrap_or_default();
let room = period
.rooms
.first()
.map(|r| r.name.clone())
.unwrap_or_default();
let period_text = Spans::from(vec![
Span::styled(
format!("{} - {}: ", start, end),
Style::default().fg(Color::Yellow),
),
Span::styled(
format!("{} ", subject),
Style::default().fg(Color::Green),
),
Span::styled(
format!("({}) ", teacher),
Style::default().fg(Color::Blue),
),
Span::styled(
format!("[{}]", room),
Style::default().fg(Color::Magenta),
),
]);
day_content.push(period_text);
}
if day_content.is_empty() {
day_content.push(Spans::from(Span::styled(
"No classes",
Style::default().fg(Color::Gray),
)));
}
let day_paragraph = Paragraph::new(day_content).block(day_block);
f.render_widget(day_paragraph, day_chunks[i]);
}
// Help text at the bottom
let help_text = Spans::from(vec![
Span::styled("n", Style::default().fg(Color::Yellow)),
Span::raw(" - next week, "),
Span::styled("p", Style::default().fg(Color::Yellow)),
Span::raw(" - previous week, "),
Span::styled("q", Style::default().fg(Color::Yellow)),
Span::raw(" - quit"),
]);
let help_paragraph = Paragraph::new(help_text);
f.render_widget(
help_paragraph,
Layout::default()
.constraints([Constraint::Length(1)].as_ref())
.margin(1)
.split(f.size())[0],
);
}