"""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": ( "
Dzień dobry,
" "wyniki próbnej matury z języka polskiego są już dostępne w dzienniku. " "Średnia klasy to 78%. Gratulacje dla wszystkich, którzy przekroczyli 80%.
" "Omówienie arkusza odbędzie się w poniedziałek na lekcji 3. Proszę przynieść " "zeszyty i arkusze.
" "Pozdrawiam,
Maria Wiśniewska
Przypominam, że w czwartek o 15:00 w sali B04 odbędą się konsultacje " "przed sprawdzianem z funkcji kwadratowej.
" "Przygotujcie pytania do zadań z rozdziału 5.
" ), "attachments": [], }, "1069-1": { "subject": "Zebranie z rodzicami – 12 listopada", "from": "Sekretariat (administracja)", "date": "05.11.2026 16:45", "read": "Nie", "content": ( "Uprzejmie informujemy, że zebranie z rodzicami odbędzie się " "12 listopada 2026 r. o godz. 17:00 w sali gimnastycznej.
" "Po spotkaniu ogólnym zapraszamy na spotkania z wychowawcami.
" ), "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": ( "Zapraszam na dodatkowe zajęcia z algorytmiki w każdą środę o 15:30 " "w sali INF. Pracujemy nad projektami do olimpiady informatycznej.
" ), "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()