From 39ac356e5e410fe5378d9a728f5979c8d6e6b1dc Mon Sep 17 00:00:00 2001 From: Florian Fischer Date: Tue, 10 Sep 2024 14:08:23 +0200 Subject: support building a timetable without untis specifying the lesson's periods Move all untis related configuration into the webuntis section. --- lessons.py | 130 +++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 100 insertions(+), 30 deletions(-) (limited to 'lessons.py') diff --git a/lessons.py b/lessons.py index 71a90e6..6ac20ea 100755 --- a/lessons.py +++ b/lessons.py @@ -17,8 +17,21 @@ import toml import webuntis +def die(msg: str, status=1) -> t.NoReturn: + """print a message and exit""" + print(msg, file=sys.stderr) + sys.exit(status) + + +@dataclass +class Period: + """Data representing a period where a lesson could take place""" + start: datetime.datetime + code = None + + @dataclass -class SessionCredentials: +class WebuntisSessionCredentials: """Data required to create a new webuntis session""" school: str server: str @@ -142,6 +155,46 @@ def filter_periods(timetable: webuntis.objects.PeriodList, ] +DAYS_MAP = {'Mo': 1, 'Di': 2, 'Mi': 3, 'Do': 4, 'Fr': 5} + +TIME_SLOTS = { + '1': datetime.timedelta(hours=8), + '2': datetime.timedelta(hours=8, minutes=45), + '3': datetime.timedelta(hours=9, minutes=45), + '4': datetime.timedelta(hours=10, minutes=30), + '5': datetime.timedelta(hours=11, minutes=30), + '6': datetime.timedelta(hours=12, minutes=15), + '7': datetime.timedelta(hours=13), + '8': datetime.timedelta(hours=13, minutes=45), + '9': datetime.timedelta(hours=14, minutes=30), + '10': datetime.timedelta(hours=15, minutes=20), + '11': datetime.timedelta(hours=16), + '12': datetime.timedelta(hours=16, minutes=45), +} + + +def build_periods(config: str, start: datetime.datetime, + end: datetime.datetime) -> list[Period]: + """Build a list of periods from a configuration string""" + + periods = [] + week = start.isocalendar().week + while True: + year = start.year if week >= start.isocalendar().week else end.year + for period in config.split(';'): + weekday, slot = period.split(':') + period_day = datetime.datetime.fromisocalendar( + year=year, week=week, day=DAYS_MAP[weekday]) + period_start = period_day + TIME_SLOTS[slot] + periods.append(Period(period_start)) + + week = week % 52 + 1 + if week > end.isocalendar().week and year == end.isocalendar().year: + break + + return periods + + def main(): """Programm entry point loading data from webuntis and printing the remaining lessons""" parser = argparse.ArgumentParser() @@ -156,6 +209,10 @@ def main(): parser.add_argument('--subject', help='The subject abbreviation used in webuntis') parser.add_argument('--class', help='The class name') + parser.add_argument('--periods', + help=('The periods when you teach the class.' + 'Format: :[;]*'), + type=str) parser.add_argument('--show-events', action='store_true', help='Show ICS calendar events during the lessons') @@ -168,35 +225,48 @@ def main(): config.update({k: v for k, v in args.__dict__.items() if v}) if 'class' not in config or 'subject' not in config: - print('Class and subject must be provided') - sys.exit(1) - - # use the webuntis username as teacher fallback - if 'teacher' not in config: - config['teacher'] = config['username'] - - session_info = SessionCredentials(config) - with webuntis.Session(useragent="muhq's avail-lessons script", - **session_info.__dict__).login() as session: - - # Unfortunately pylint does not understand the type (_itemclass) - # of the webuntis ListResults, therfore we supress the errors when - # accessing members of the _itemclass. - year = [ - y for y in session.schoolyears() - if y.start <= config['start'] <= y.end # pylint: disable=no-member - ][0] - - if 'end' not in config: - config['end'] = year.end - - klasse = session.klassen().filter(name=config['class'])[0] # pylint: disable=no-member - - timetable = session.timetable_extended(klasse=klasse, - start=config['start'], - end=config['end']) - - periods = filter_periods(timetable, config) + die('Class and subject must be provided') + + periods = None + if 'webuntis' in config: + # use the webuntis username as teacher fallback + if 'teacher' not in config: + config['teacher'] = config['webuntis']['username'] + + session_info = WebuntisSessionCredentials(config['webuntis']) + with webuntis.Session(useragent="muhq's avail-lessons script", + **session_info.__dict__).login() as session: + + # Unfortunately pylint does not understand the type (_itemclass) + # of the webuntis ListResults, therfore we supress the errors when + # accessing members of the _itemclass. + year = [ + y for y in session.schoolyears() + if y.start <= config['start'] <= y.end # pylint: disable=no-member + ] + if not year: + die('No school year is active') + year = year[0] + + if 'end' not in config: + config['end'] = year.end + + klasse = session.klassen().filter(name=config['class'])[0] # pylint: disable=no-member + + timetable = session.timetable_extended(klasse=klasse, + start=config['start'], + end=config['end']) + + periods = filter_periods(timetable, config) + elif args.periods: + start = config.setdefault( + 'start', datetime.datetime.fromisocalendar(2023, 37, 2)) + end = config.setdefault('end', + datetime.datetime.fromisocalendar(2024, 30, 5)) + periods = build_periods(args.periods, start, end) + + if not periods: + die('Webuntis credentials or manual periods must be provided') # load ICS events evs = load_events(config) -- cgit v1.2.3