145 lines
5.5 KiB
Markdown
145 lines
5.5 KiB
Markdown
# 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 <http://127.0.0.1:8765>. 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.
|