librus-tui
A terminal-looking web frontend for Librus Synergia, built on the async
Synergia client from 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
./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:
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 viewj/k, arrows — scroll; in messages move through the inboxg/G— top / bottomr— refresh:— command line (:grades,:week 1,:reload,:logout, …)?— help/— filter grades (grades view)[/]— previous / next week (timetable view)q— log outEsc— 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)
└──────────────────────────┘
POST /api/loginperforms the same flow as librusik: Synergia portal session,api.librus.pl/OAuth/Authorization?client_id=46, the 2FA redirect, thenAuth/TokenInfo/Auth/UserInfoto verify the resulting cookies.- Cookies (
DZIENNIKSID,SDZIENNIKSID, …) stay in an in-memoryLibrusobject on the server. The browser gets a randomlibrustui_sessioncookie. /api/*endpoints wrap the client methods from librusik and return JSON. Message HTML is sanitised with BeautifulSoup before it reaches the browser.- 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, not0.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,nosniffandno-referrer. - Cross-origin
POSTs are rejected by anOrigincheck. - 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, MIT licensed, copyright © 2022 dani3l0.
This project is unofficial and not affiliated with Librus. Use it with your own account at your own risk.