564 lines
19 KiB
Python
564 lines
19 KiB
Python
"""Offline demo data for librus-tui.
|
||
|
||
Start the server with --demo to explore the interface without a real
|
||
Librus account. All dates are generated relative to today so the dashboard
|
||
and timetable always look alive.
|
||
"""
|
||
|
||
import asyncio
|
||
import copy
|
||
from datetime import date, datetime, timedelta
|
||
|
||
WEEKDAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
|
||
|
||
SUBJECTS = [
|
||
"Język polski",
|
||
"Matematyka",
|
||
"Język angielski",
|
||
"Fizyka",
|
||
"Chemia",
|
||
"Biologia",
|
||
"Historia",
|
||
"Informatyka",
|
||
"Geografia",
|
||
"Wychowanie fizyczne",
|
||
]
|
||
|
||
TEACHERS = {
|
||
"polski": {"FirstName": "Maria", "LastName": "Wiśniewska"},
|
||
"matma": {"FirstName": "Piotr", "LastName": "Zieliński"},
|
||
"angielski": {"FirstName": "Sarah", "LastName": "Kowalczyk"},
|
||
"fizyka": {"FirstName": "Andrzej", "LastName": "Lewandowski"},
|
||
"chemia": {"FirstName": "Ewa", "LastName": "Szymańska"},
|
||
"biologia": {"FirstName": "Katarzyna", "LastName": "Wójcik"},
|
||
"historia": {"FirstName": "Tomasz", "LastName": "Kamiński"},
|
||
"informatyka": {"FirstName": "Michał", "LastName": "Nowicki"},
|
||
"geografia": {"FirstName": "Agnieszka", "LastName": "Dąbrowska"},
|
||
"wf": {"FirstName": "Marek", "LastName": "Jankowski"},
|
||
}
|
||
|
||
|
||
def iso(d):
|
||
return d.strftime("%Y-%m-%d")
|
||
|
||
|
||
def grade(value, weight, category, teacher, days_ago=3, comment="No comment", **extra):
|
||
base = {
|
||
"Grade": value,
|
||
"Weight": weight,
|
||
"Comment": comment,
|
||
"Category": category,
|
||
"isFinal": False,
|
||
"isSemester": False,
|
||
"separator": False,
|
||
"underlined": False,
|
||
"Semester": 1,
|
||
"AddDate": iso(date.today() - timedelta(days=days_ago)),
|
||
"AddedBy": copy.deepcopy(teacher),
|
||
}
|
||
base.update(extra)
|
||
return base
|
||
|
||
|
||
def build_grades():
|
||
t = TEACHERS
|
||
return {
|
||
"Język polski": [
|
||
grade("5", "2", "Sprawdzian", t["polski"], 12, "Świetna analiza lektury."),
|
||
grade("4", "1", "Kartkówka", t["polski"], 9),
|
||
grade("5", "1", "Aktywność", t["polski"], 2),
|
||
grade("4", "2", "Wypracowanie", t["polski"], 21, "Bardzo dobry styl, drobne błędy."),
|
||
],
|
||
"Matematyka": [
|
||
grade("5", "3", "Sprawdzian", t["matma"], 7, "Bezbłędnie!"),
|
||
grade("4", "1", "Kartkówka", t["matma"], 3),
|
||
grade("3", "2", "Praca domowa", t["matma"], 14),
|
||
grade("5", "2", "Sprawdzian", t["matma"], 28),
|
||
grade("4", "1", "Aktywność", t["matma"], 1),
|
||
],
|
||
"Język angielski": [
|
||
grade("5", "2", "Test", t["angielski"], 5),
|
||
grade("5", "1", "Speaking", t["angielski"], 10),
|
||
grade("4", "2", "Essay", t["angielski"], 19),
|
||
],
|
||
"Fizyka": [
|
||
grade("4", "2", "Sprawdzian", t["fizyka"], 8),
|
||
grade("3", "1", "Kartkówka", t["fizyka"], 16),
|
||
grade("4", "1", "Doświadczenie", t["fizyka"], 4),
|
||
],
|
||
"Chemia": [
|
||
grade("4", "2", "Sprawdzian", t["chemia"], 11),
|
||
grade("5", "1", "Kartkówka", t["chemia"], 6),
|
||
],
|
||
"Biologia": [
|
||
grade("5", "2", "Sprawdzian", t["biologia"], 13),
|
||
grade("5", "1", "Projekt", t["biologia"], 20),
|
||
],
|
||
"Historia": [
|
||
grade("4", "2", "Sprawdzian", t["historia"], 9),
|
||
grade("4", "1", "Kartkówka", t["historia"], 2),
|
||
grade("5", "1", "Wypowiedź", t["historia"], 25),
|
||
],
|
||
"Informatyka": [
|
||
grade("6", "2", "Projekt", t["informatyka"], 6, "Wyjątkowo dobrze zaprojektowana aplikacja."),
|
||
grade("5", "1", "Kartkówka", t["informatyka"], 15),
|
||
],
|
||
"Geografia": [
|
||
grade("4", "2", "Sprawdzian", t["geografia"], 10),
|
||
grade("4", "1", "Mapa", t["geografia"], 5),
|
||
],
|
||
"Wychowanie fizyczne": [
|
||
grade("5", "1", "Aktywność", t["wf"], 3),
|
||
grade("5", "1", "Test sprawności", t["wf"], 17),
|
||
],
|
||
}
|
||
|
||
|
||
def lesson(no, subject, teacher, room, start, end, **extra):
|
||
item = {
|
||
"Lesson": no,
|
||
"Subject": subject,
|
||
"isSubstitution": False,
|
||
"isCancelled": False,
|
||
"Teacher": copy.deepcopy(teacher),
|
||
"HourFrom": start,
|
||
"HourTo": end,
|
||
"Classroom": room,
|
||
}
|
||
item.update(extra)
|
||
return item
|
||
|
||
|
||
PLAN = {
|
||
"Monday": [
|
||
(1, "Język polski", "polski", "A12", "08:00", "08:45"),
|
||
(2, "Matematyka", "matma", "B04", "08:55", "09:40"),
|
||
(3, "Język angielski", "angielski", "A07", "09:50", "10:35"),
|
||
(4, "Historia", "historia", "A21", "10:45", "11:30"),
|
||
(6, "Wychowanie fizyczne", "wf", "SALA", "12:45", "13:30"),
|
||
],
|
||
"Tuesday": [
|
||
(1, "Fizyka", "fizyka", "C02", "08:00", "08:45"),
|
||
(2, "Chemia", "chemia", "C03", "08:55", "09:40"),
|
||
(3, "Matematyka", "matma", "B04", "09:50", "10:35"),
|
||
(5, "Język polski", "polski", "A12", "11:45", "12:30"),
|
||
(7, "Informatyka", "informatyka", "INF", "13:45", "14:30"),
|
||
],
|
||
"Wednesday": [
|
||
(1, "Biologia", "biologia", "B11", "08:00", "08:45"),
|
||
(2, "Geografia", "geografia", "A19", "08:55", "09:40"),
|
||
(3, "Język angielski", "angielski", "A07", "09:50", "10:35"),
|
||
(4, "Matematyka", "matma", "B04", "10:45", "11:30"),
|
||
(6, "Historia", "historia", "A21", "12:45", "13:30"),
|
||
],
|
||
"Thursday": [
|
||
(1, "Matematyka", "matma", "B04", "08:00", "08:45"),
|
||
(2, "Fizyka", "fizyka", "C02", "08:55", "09:40"),
|
||
(3, "Język polski", "polski", "A12", "09:50", "10:35"),
|
||
(5, "Wychowanie fizyczne", "wf", "SALA", "11:45", "12:30"),
|
||
],
|
||
"Friday": [
|
||
(1, "Chemia", "chemia", "C03", "08:00", "08:45"),
|
||
(2, "Biologia", "biologia", "B11", "08:55", "09:40"),
|
||
(3, "Informatyka", "informatyka", "INF", "09:50", "10:35"),
|
||
(4, "Język angielski", "angielski", "A07", "10:45", "11:30"),
|
||
],
|
||
}
|
||
|
||
SUBSTITUTIONS = {
|
||
"Wednesday": {4: {"isSubstitution": True, "Classroom": "A02"}},
|
||
"Friday": {3: {"isCancelled": True, "isSubstitution": True}},
|
||
}
|
||
|
||
|
||
def build_timetable(week=0):
|
||
today = date.today()
|
||
week_start = today - timedelta(days=today.weekday()) + timedelta(weeks=week)
|
||
week_end = week_start + timedelta(days=6)
|
||
data = {}
|
||
|
||
for index, day in enumerate(WEEKDAYS[:5]):
|
||
entries = []
|
||
for no, subject, teacher_key, room, start, end in PLAN[day]:
|
||
extra = SUBSTITUTIONS.get(day, {}).get(no, {}) if week == 0 else {}
|
||
item = lesson(
|
||
no,
|
||
subject,
|
||
TEACHERS[teacher_key],
|
||
extra.get("Classroom", room),
|
||
start,
|
||
end,
|
||
**{k: v for k, v in extra.items() if k != "Classroom"},
|
||
)
|
||
item["Date"] = iso(week_start + timedelta(days=index))
|
||
entries.append(item)
|
||
if week == 1 and day == "Thursday":
|
||
entries.append(
|
||
{
|
||
"Lesson": "-",
|
||
"Subject": "Konsultacje z matematyki",
|
||
"isSubstitution": False,
|
||
"isCancelled": False,
|
||
"Teacher": copy.deepcopy(TEACHERS["matma"]),
|
||
"HourFrom": "15:00",
|
||
"HourTo": "16:00",
|
||
"Classroom": "B04",
|
||
"Date": iso(week_start + timedelta(days=3)),
|
||
"isAdditional": True,
|
||
}
|
||
)
|
||
if entries:
|
||
data[day] = entries
|
||
|
||
return {
|
||
"Timetable": data,
|
||
"weekStart": iso(week_start),
|
||
"weekEnd": iso(week_end),
|
||
"week": week,
|
||
}
|
||
|
||
|
||
def build_exams():
|
||
today = date.today()
|
||
return [
|
||
{
|
||
"Lesson": "Matematyka",
|
||
"AddedBy": copy.deepcopy(TEACHERS["matma"]),
|
||
"Type": "Sprawdzian",
|
||
"StartTime": "09:50",
|
||
"EndTime": "10:35",
|
||
"Date": iso(today + timedelta(days=2)),
|
||
"AddDate": iso(today - timedelta(days=4)) + " 14:12:00",
|
||
"Content": "Funkcja kwadratowa: postać ogólna, kanoniczna i iloczynowa. Zadania 1-8, str. 142.",
|
||
},
|
||
{
|
||
"Lesson": "Język polski",
|
||
"AddedBy": copy.deepcopy(TEACHERS["polski"]),
|
||
"Type": "Wypracowanie",
|
||
"StartTime": "08:00",
|
||
"EndTime": "08:45",
|
||
"Date": iso(today + timedelta(days=4)),
|
||
"AddDate": iso(today - timedelta(days=2)) + " 10:03:00",
|
||
"Content": "Rozprawka: „Czy warto znać przeszłość?”. Minimum 300 słów, odwołanie do lektury.",
|
||
},
|
||
{
|
||
"Lesson": "Fizyka",
|
||
"AddedBy": copy.deepcopy(TEACHERS["fizyka"]),
|
||
"Type": "Kartkówka",
|
||
"StartTime": "08:55",
|
||
"EndTime": "09:40",
|
||
"Date": iso(today + timedelta(days=7)),
|
||
"AddDate": iso(today - timedelta(days=1)) + " 16:44:00",
|
||
"Content": "Zasady dynamiki Newtona. Wzory i jednostki.",
|
||
},
|
||
{
|
||
"Lesson": "Informatyka",
|
||
"AddedBy": copy.deepcopy(TEACHERS["informatyka"]),
|
||
"Type": "Projekt",
|
||
"StartTime": "",
|
||
"EndTime": "",
|
||
"Date": iso(today + timedelta(days=10)),
|
||
"AddDate": iso(today - timedelta(days=6)) + " 09:15:00",
|
||
"Content": "Aplikacja terminalowa w Pythonie: minimum 3 podkomendy, obsługa błędów i README.",
|
||
},
|
||
]
|
||
|
||
|
||
def build_attendances():
|
||
today = date.today()
|
||
entries = [
|
||
("Matematyka", "Obecność", "ob.", True, 0),
|
||
("Język polski", "Obecność", "ob.", True, 0),
|
||
("Fizyka", "Spóźnienie", "sp.", False, 1),
|
||
("Chemia", "Nieobecność", "nb.", False, 2),
|
||
("Historia", "Obecność", "ob.", True, 2),
|
||
("Język angielski", "Zwolnienie", "zw.", True, 3),
|
||
("Biologia", "Nieobecność", "nb.", False, 4),
|
||
("Informatyka", "Obecność", "ob.", True, 5),
|
||
("Geografia", "Obecność", "ob.", True, 6),
|
||
("Wychowanie fizyczne", "Obecność", "ob.", True, 7),
|
||
("Matematyka", "Nieobecność", "nb.", False, 8),
|
||
("Język polski", "Obecność", "ob.", True, 9),
|
||
]
|
||
result = []
|
||
for subject, kind, short, presence, days_ago in entries:
|
||
teacher_key = {
|
||
"Matematyka": "matma",
|
||
"Język polski": "polski",
|
||
"Fizyka": "fizyka",
|
||
"Chemia": "chemia",
|
||
"Historia": "historia",
|
||
"Język angielski": "angielski",
|
||
"Biologia": "biologia",
|
||
"Informatyka": "informatyka",
|
||
"Geografia": "geografia",
|
||
"Wychowanie fizyczne": "wf",
|
||
}[subject]
|
||
d = today - timedelta(days=days_ago)
|
||
result.append(
|
||
{
|
||
"Lesson": subject,
|
||
"Type": kind,
|
||
"Short": short,
|
||
"isPresence": presence,
|
||
"Added": iso(d) + " 18:00:00",
|
||
"Date": iso(d),
|
||
"AddedBy": copy.deepcopy(TEACHERS[teacher_key]),
|
||
}
|
||
)
|
||
return result
|
||
|
||
|
||
MESSAGES = [
|
||
{
|
||
"from": "Maria Wiśniewska",
|
||
"subject": "Wyniki próbnej matury z języka polskiego",
|
||
"date": "07.11.2026 14:22",
|
||
"link": "1081-1",
|
||
},
|
||
{
|
||
"from": "Piotr Zieliński",
|
||
"subject": "Konsultacje przed sprawdzianem",
|
||
"date": "06.11.2026 09:10",
|
||
"link": "1074-1",
|
||
},
|
||
{
|
||
"from": "Sekretariat",
|
||
"subject": "Zebranie z rodzicami – 12 listopada",
|
||
"date": "05.11.2026 16:45",
|
||
"link": "1069-1",
|
||
},
|
||
{
|
||
"from": "Michał Nowicki",
|
||
"subject": "Dodatkowe zajęcia z algorytmiki",
|
||
"date": "04.11.2026 11:03",
|
||
"link": "1062-1",
|
||
},
|
||
]
|
||
|
||
MESSAGE_BODIES = {
|
||
"1081-1": {
|
||
"subject": "Wyniki próbnej matury z języka polskiego",
|
||
"from": "Maria Wiśniewska (nauczyciel)",
|
||
"date": "07.11.2026 14:22",
|
||
"read": "Tak",
|
||
"content": (
|
||
"<p>Dzień dobry,</p>"
|
||
"<p>wyniki próbnej matury z języka polskiego są już dostępne w dzienniku. "
|
||
"Średnia klasy to <b>78%</b>. Gratulacje dla wszystkich, którzy przekroczyli 80%.</p>"
|
||
"<p>Omówienie arkusza odbędzie się w <b>poniedziałek</b> na lekcji 3. Proszę przynieść "
|
||
"zeszyty i arkusze.</p>"
|
||
"<p>Pozdrawiam,<br>Maria Wiśniewska</p>"
|
||
),
|
||
"attachments": [
|
||
{"name": "klucz_odpowiedzi.pdf", "nice": "1081-1/klucz.pdf"},
|
||
],
|
||
},
|
||
"1074-1": {
|
||
"subject": "Konsultacje przed sprawdzianem",
|
||
"from": "Piotr Zieliński (nauczyciel)",
|
||
"date": "06.11.2026 09:10",
|
||
"read": "Tak",
|
||
"content": (
|
||
"<p>Przypominam, że w czwartek o <b>15:00</b> w sali B04 odbędą się konsultacje "
|
||
"przed sprawdzianem z funkcji kwadratowej.</p>"
|
||
"<p>Przygotujcie pytania do zadań z rozdziału 5.</p>"
|
||
),
|
||
"attachments": [],
|
||
},
|
||
"1069-1": {
|
||
"subject": "Zebranie z rodzicami – 12 listopada",
|
||
"from": "Sekretariat (administracja)",
|
||
"date": "05.11.2026 16:45",
|
||
"read": "Nie",
|
||
"content": (
|
||
"<p>Uprzejmie informujemy, że zebranie z rodzicami odbędzie się "
|
||
"<b>12 listopada 2026 r. o godz. 17:00</b> w sali gimnastycznej.</p>"
|
||
"<p>Po spotkaniu ogólnym zapraszamy na spotkania z wychowawcami.</p>"
|
||
),
|
||
"attachments": [
|
||
{"name": "harmonogram_zebran.pdf", "nice": "1069-1/harmonogram.pdf"},
|
||
],
|
||
},
|
||
"1062-1": {
|
||
"subject": "Dodatkowe zajęcia z algorytmiki",
|
||
"from": "Michał Nowicki (nauczyciel)",
|
||
"date": "04.11.2026 11:03",
|
||
"read": "Tak",
|
||
"content": (
|
||
"<p>Zapraszam na dodatkowe zajęcia z algorytmiki w każdą środę o 15:30 "
|
||
"w sali INF. Pracujemy nad projektami do olimpiady informatycznej.</p>"
|
||
),
|
||
"attachments": [],
|
||
},
|
||
}
|
||
|
||
|
||
def build_free_days():
|
||
today = date.today()
|
||
return [
|
||
{
|
||
"Name": "Wigilia szkolna",
|
||
"DateFrom": iso(date(today.year, 12, 23)),
|
||
"DateTo": iso(date(today.year, 12, 31)),
|
||
},
|
||
{
|
||
"Name": "Dzień Edukacji Narodowej",
|
||
"DateFrom": iso(date(today.year, 10, 14)),
|
||
"DateTo": iso(date(today.year, 10, 14)),
|
||
},
|
||
{
|
||
"Name": "Ferie zimowe",
|
||
"DateFrom": iso(date(today.year + 1, 1, 26)),
|
||
"DateTo": iso(date(today.year + 1, 2, 8)),
|
||
},
|
||
{
|
||
"Name": "Wielkanoc",
|
||
"DateFrom": iso(date(today.year + 1, 3, 28)),
|
||
"DateTo": iso(date(today.year + 1, 4, 2)),
|
||
},
|
||
]
|
||
|
||
|
||
def build_teacher_free_days():
|
||
today = date.today()
|
||
return [
|
||
{
|
||
"Teacher": copy.deepcopy(TEACHERS["matma"]),
|
||
"DateFrom": iso(today + timedelta(days=5)),
|
||
"DateTo": iso(today + timedelta(days=5)),
|
||
"TimeFrom": "10:00:00",
|
||
"TimeTo": "14:00:00",
|
||
},
|
||
{
|
||
"Teacher": copy.deepcopy(TEACHERS["angielski"]),
|
||
"DateFrom": iso(today + timedelta(days=9)),
|
||
"DateTo": iso(today + timedelta(days=11)),
|
||
},
|
||
]
|
||
|
||
|
||
def build_conferences():
|
||
today = date.today()
|
||
return [
|
||
{
|
||
"Name": "Zebranie śródroczne",
|
||
"Topic": "Wyniki w nauce i frekwencja",
|
||
"Room": "A12",
|
||
"Date": iso(today + timedelta(days=4)),
|
||
"Time": "17:00:00",
|
||
},
|
||
{
|
||
"Name": "Konsultacje indywidualne",
|
||
"Topic": "Matematyka – przygotowanie do matury",
|
||
"Room": "B04",
|
||
"Date": iso(today + timedelta(days=11)),
|
||
"Time": "16:30:00",
|
||
},
|
||
]
|
||
|
||
|
||
class DemoLibrus:
|
||
"""Drop-in replacement for librus.Librus backed by sample data."""
|
||
|
||
demo = True
|
||
credentials_rejected = False
|
||
headers = {}
|
||
|
||
def __init__(self):
|
||
self._grades = build_grades()
|
||
self._attendances = build_attendances()
|
||
|
||
async def mktoken(self, login, password):
|
||
await asyncio.sleep(0.35)
|
||
return True
|
||
|
||
async def activate_api_access(self):
|
||
return True
|
||
|
||
async def get_me(self):
|
||
return {
|
||
"FirstName": "Alex",
|
||
"LastName": "Nowak",
|
||
"Tutor": copy.deepcopy(TEACHERS["polski"]),
|
||
"SchoolYearStarts": "2026-09-01",
|
||
"SchoolYearMiddles": "2027-01-31",
|
||
"SchoolYearEnds": "2027-06-25",
|
||
"Type": "Uczeń",
|
||
"Class": "3 A",
|
||
}
|
||
|
||
async def get_school(self):
|
||
return {
|
||
"Name": "I Liceum Ogólnokształcące im. Mikołaja Kopernika",
|
||
"Street": "ul. Szkolna",
|
||
"BuildingNumber": "12",
|
||
"PostCode": "00-001",
|
||
"Town": "Warszawa",
|
||
}
|
||
|
||
async def get_lucky_number(self):
|
||
return 7
|
||
|
||
async def get_behaviour_grade(self):
|
||
return {
|
||
"Grade": "bardzo dobre",
|
||
"Teacher": copy.deepcopy(TEACHERS["polski"]),
|
||
}
|
||
|
||
async def get_grades(self):
|
||
return copy.deepcopy(self._grades)
|
||
|
||
async def get_timetable(self, week=0):
|
||
return build_timetable(week)
|
||
|
||
async def get_exams(self):
|
||
return build_exams()
|
||
|
||
async def get_attendances(self):
|
||
return copy.deepcopy(self._attendances)
|
||
|
||
async def get_notifications(self, days=14):
|
||
return {
|
||
"grades": sum(len(v) for v in self._grades.values()),
|
||
"exams": len(build_exams()),
|
||
"absences": sum(1 for a in self._attendances if not a["isPresence"]),
|
||
"conferences": len(build_conferences()),
|
||
}
|
||
|
||
async def get_messages(self):
|
||
return copy.deepcopy(MESSAGES)
|
||
|
||
async def get_message(self, link):
|
||
body = copy.deepcopy(
|
||
MESSAGE_BODIES.get(link, MESSAGE_BODIES["1081-1"])
|
||
)
|
||
body["attachments"] = [
|
||
dict(
|
||
attachment,
|
||
source="https://synergia.librus.pl/wiadomosci/pobierz_zalacznik/"
|
||
+ attachment["nice"],
|
||
direct="/get",
|
||
)
|
||
for attachment in body["attachments"]
|
||
]
|
||
return body
|
||
|
||
async def download_file(self, link):
|
||
return {
|
||
"headers": {
|
||
"Content-Type": "application/pdf",
|
||
"Content-Disposition": 'attachment; filename="demo.pdf"',
|
||
},
|
||
"content": b"%PDF-1.4\n% librus-tui demo attachment\n",
|
||
}
|
||
|
||
async def get_free_days(self):
|
||
return build_free_days()
|
||
|
||
async def get_teacher_free_days(self):
|
||
return build_teacher_free_days()
|
||
|
||
async def get_parent_teacher_conferences(self):
|
||
return build_conferences()
|