# librus-tui A terminal-looking web frontend for **Librus Synergia**, built on the async Synergia client from [dani3l0/librusik](https://github.com/dani3l0/librusik) (`lib/api/librus.py`, stable branch). The whole UI is a single-page phosphor-green "TUI" in the browser: keyboard navigation, command bar, box-drawn panels, live clock, scanlines and an OAuth boot log. The Python backend talks to Synergia and keeps your Librus cookies server-side; the browser only gets an opaque session cookie. ``` _ ___ ____ ____ _ _ ____ | | |_ _| __ )| _ \| | | / ___| | | | || _ \| |_) | | | \___ \ | |___ | || |_) | _ <| |_| |___) | |_____|___|____/|_| \_\\___/|____/ // TUI ``` ## Quick start ```bash ./run.sh # real Librus account, http://127.0.0.1:8765 ./run.sh --demo # offline sample data, no Librus account needed ``` `run.sh` creates `.venv`, installs the two dependencies, and starts the server. To run it manually: ```bash python3 -m venv .venv .venv/bin/pip install -r requirements.txt .venv/bin/python app.py --demo ``` Then open . In demo mode use the `[ enter demo mode ]` button; in real mode log in with your Synergia credentials. CLI options: | option | default | description | | --- | --- | --- | | `--host` | `127.0.0.1` | bind address | | `--port` | `8765` | HTTP port | | `--demo` | off | use the offline dataset | | `--debug` | off | verbose logs / access log | Environment equivalents: `LIBRUS_HOST`, `LIBRUS_PORT`, `LIBRUS_DEMO=1`. ## Views | key | view | contents | | --- | --- | --- | | `1` | dashboard | profile, lucky number, behaviour, counters, today's lessons, upcoming breaks | | `2` | grades | weighted subject averages, colour-coded grade chips, comments, teacher, date | | `3` | timetable | week grid, substitutions, cancellations, extra activities | | `4` | homework | tests and homework sorted by date, full descriptions | | `5` | attendance | register with absences, lateness and excused lessons | | `6` | messages | inbox + reader + attachment downloads | | `7` | free days | school/teacher free days and parent-teacher conferences | ### Keyboard - `1`–`7` — switch view - `j` / `k`, arrows — scroll; in messages move through the inbox - `g` / `G` — top / bottom - `r` — refresh - `:` — command line (`:grades`, `:week 1`, `:reload`, `:logout`, …) - `?` — help - `/` — filter grades (grades view) - `[` / `]` — previous / next week (timetable view) - `q` — log out - `Esc` — close modal / leave command line ## How it works ``` browser ── fetch /api/* ──> aiohttp app ── HTTPS ──> synergia.librus.pl │ │ │ session cookie only ├─ librus.py (OAuth + gateway API + message scraping) └──────────────────────────┘ ``` 1. `POST /api/login` performs the same flow as librusik: Synergia portal session, `api.librus.pl/OAuth/Authorization?client_id=46`, the 2FA redirect, then `Auth/TokenInfo` / `Auth/UserInfo` to verify the resulting cookies. 2. Cookies (`DZIENNIKSID`, `SDZIENNIKSID`, …) stay in an in-memory `Librus` object on the server. The browser gets a random `librustui_session` cookie. 3. `/api/*` endpoints wrap the client methods from librusik and return JSON. Message HTML is sanitised with BeautifulSoup before it reaches the browser. 4. Sessions expire after 12 h idle / 24 h absolute; restarting the server invalidates all of them. ### Files ``` app.py aiohttp server, sessions, API endpoints, sanitizer librus.py async Synergia client (adapted from librusik, MIT) demo.py offline dataset behind --demo static/index.html TUI shell static/style.css phosphor terminal theme static/js/00-core.js state, helpers, API wrapper, chrome static/js/10-auth.js login, session chrome, clock static/js/20-dashboard.js static/js/22-grades.js static/js/24-timetable.js static/js/26-more-views.js static/js/30-app.js navigation, commands, keyboard, bootstrap ``` ## Security notes - Bind address defaults to `127.0.0.1`, not `0.0.0.0`. - Credentials are sent from the browser to this server, then to Librus; they are **not** logged or written to disk. - Session cookies are `HttpOnly`, `SameSite=Lax`. Responses set a strict CSP, `X-Frame-Options: DENY`, `nosniff` and `no-referrer`. - Cross-origin `POST`s are rejected by an `Origin` check. - Message content is treated as untrusted and sanitised server-side (scripts, event handlers, iframes and unsafe URL schemes are removed). - If you expose this on the internet, put it behind HTTPS (e.g. Caddy/nginx), set `--host 127.0.0.1`, and consider adding authentication in front of it. ## Limitations - Librus has no public API contract. If Synergia changes its markup or gateway responses, parts can break; the UI shows a "signal lost" panel with the upstream error instead of crashing. - The messages reader scrapes the HTML portal, exactly like librusik, so it is the most fragile view. - Attachments are proxied through the server and require the original session cookies to still be valid. - One process keeps sessions in memory; there is no multi-user persistence. Restart = everyone logs in again. ## Credits Client flow, endpoints and scraping logic are adapted from [dani3l0/librusik](https://github.com/dani3l0/librusik), MIT licensed, copyright © 2022 dani3l0. This project is unofficial and not affiliated with Librus. Use it with your own account at your own risk.