From c6958d22f3f56963ded374a227870d134ad4736a Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Fri, 11 Feb 2022 22:11:20 +0100 Subject: [PATCH] Add command to generate course guides, add autocompletion for some commands, create json file with course information & abbreviations --- cogs/slash/school_slash.py | 47 +++++++++++--- data/courses.py | 64 ++++++++++++++++++++ files/courses.json | 121 +++++++++++++++++++++++++++++++++++++ 3 files changed, 223 insertions(+), 9 deletions(-) create mode 100644 data/courses.py create mode 100644 files/courses.json diff --git a/cogs/slash/school_slash.py b/cogs/slash/school_slash.py index 525e43d..03c8305 100644 --- a/cogs/slash/school_slash.py +++ b/cogs/slash/school_slash.py @@ -1,7 +1,8 @@ from discord.ext import commands -from discord.commands import slash_command, ApplicationContext, Option +from discord.commands import slash_command, ApplicationContext, Option, AutocompleteContext from data import schedule +from data.courses import load_courses, find_course_from_name from data.embeds.food import Menu from data.embeds.deadlines import Deadlines from functions import les, config @@ -10,13 +11,26 @@ from functions.timeFormatters import skip_weekends from startup.didier import Didier +# Preload autocomplete constants to allow for smoother results +courses = load_courses() +days = ["Morgen", "Overmorgen", "Maandag", "Dinsdag", "Woensdag", "Donderdag", "Vrijdag"] + + +def day_autocomplete(ctx: AutocompleteContext) -> list[str]: + return [day for day in days if day.lower().startswith(ctx.value.lower())] + + +def course_autocomplete(ctx: AutocompleteContext) -> list[str]: + return [course for course in courses if course.lower().startswith(ctx.value.lower())] + + class SchoolSlash(commands.Cog): def __init__(self, client: Didier): self.client: Didier = client @slash_command(name="eten", description="Menu in de UGent resto's op een bepaalde dag") async def _food_slash(self, ctx: ApplicationContext, - dag: Option(str, description="Dag", required=False, default=None) + dag: Option(str, description="Dag", required=False, default=None, autocomplete=day_autocomplete) ): embed = Menu(dag).to_embed() await ctx.respond(embed=embed) @@ -28,21 +42,21 @@ class SchoolSlash(commands.Cog): @slash_command(name="les", description="Lessenrooster voor [Dag] (default vandaag)",) async def _schedule_slash(self, ctx: ApplicationContext, - day: Option(str, description="Dag", required=False, default=None) + dag: Option(str, description="Dag", required=False, default=None, autocomplete=day_autocomplete) ): """It's late and I really don't want to refactor the original right now""" - if day is not None: - day = day.lower() + if dag is not None: + dag = dag.lower() - date = les.find_target_date(day) + date = les.find_target_date(dag) # Person explicitly requested a weekend-day - if day is not None and day.lower() in ("morgen", "overmorgen") and date.weekday() > 4: - return await ctx.respond(f"{capitalize(day)} is het weekend.", ephemeral=True) + if dag is not None and dag.lower() in ("morgen", "overmorgen") and date.weekday() > 4: + return await ctx.respond(f"{capitalize(dag)} is het weekend.", ephemeral=True) date = skip_weekends(date) - s = schedule.Schedule(date, int(config.get("year")), int(config.get("semester")), day is not None) + s = schedule.Schedule(date, int(config.get("year")), int(config.get("semester")), dag is not None) if s.semester_over: return await ctx.respond("Het semester is afgelopen.", ephemeral=True) @@ -54,6 +68,21 @@ class SchoolSlash(commands.Cog): return await ctx.respond(embed=s.create_schedule().to_embed()) + @slash_command(name="fiche", description="Zoek de studiefiche voor een vak.") + async def _study_guide_slash(self, ctx: ApplicationContext, + vak: Option(str, description="Naam van het vak. Afkortingen werken ook, maar worden niet ge-autocompletet.", + required=True, autocomplete=course_autocomplete)): + # Find code corresponding to the search query + course = find_course_from_name(vak, courses) + + # Code not found + if course is None: + return await ctx.respond(f"Onbekend vak: \"{vak}\".", ephemeral=True) + + # Get the guide for the current year + year = 2018 + int(config.get("year")) + return await ctx.respond(f"https://studiekiezer.ugent.be/studiefiche/nl/{course.code}/{year}") + def setup(client: Didier): client.add_cog(SchoolSlash(client)) diff --git a/data/courses.py b/data/courses.py new file mode 100644 index 0000000..ce49649 --- /dev/null +++ b/data/courses.py @@ -0,0 +1,64 @@ +from dataclasses import dataclass +from typing import Optional + +import dacite +import json + + +@dataclass +class Course: + abbreviations: list[str] + code: str + name: str + year: int + alt: Optional[str] = None + + +def load_courses() -> dict[str, Course]: + """Create a list of all courses""" + with open("files/courses.json", "r") as file: + data = json.load(file) + + courses = {} + + for course_name in data: + # Add name into the dict to allow flexibility + course_data = data[course_name] + course_data["name"] = course_name + + courses[course_name] = dacite.from_dict(data_class=Course, data=course_data) + + return courses + + +def find_course_from_name(name: str, courses: Optional[dict[str, Course]] = None, case_insensitive: bool = True) -> Optional[Course]: + # Allow passing a course dict in to avoid having to create it all the time + if courses is None: + courses = load_courses() + + if case_insensitive: + name = name.lower() + + def _perhaps_lower(inp: str) -> str: + """Cast a string to lowercase if necessary""" + if case_insensitive: + return inp.lower() + + return inp + + # Iterate over all courses to look for a match + for course_name, course in courses.items(): + # Check name first + if _perhaps_lower(course_name) == name: + return course + + # Then abbreviations + for abbreviation in course.abbreviations: + if _perhaps_lower(abbreviation) == name: + return course + + # Finally alternative names + if course.alt is not None and _perhaps_lower(course.alt) == name: + return course + + return None diff --git a/files/courses.json b/files/courses.json new file mode 100644 index 0000000..005ea83 --- /dev/null +++ b/files/courses.json @@ -0,0 +1,121 @@ +{ + "Algoritmen en Datastructuren 2": { + "abbreviations": ["AD2"], + "code": "C003777", + "year": 2 + }, + "Algoritmen en Datastructuren 3": { + "abbreviations": ["AD3"], + "code": "C003782", + "year": 3 + }, + "Artificiële Intelligentie": { + "abbreviations": ["AI"], + "code": "C003756", + "year": 3 + }, + "Automaten, Berekenbaarheid en Complexiteit": { + "abbreviations": ["ABC"], + "code": "C003785", + "year": 3 + }, + "Besturingssystemen": { + "abbreviations": ["BS"], + "code": "E019010", + "year": 3 + }, + "Communicatienetwerken": { + "abbreviations": ["Comnet"], + "code": "E008620", + "year": 2 + }, + "Computationele Biologie": { + "abbreviations": ["Compbio"], + "code": "C003789", + "year": 3 + }, + "Computerarchitectuur": { + "abbreviations": ["CA", "Comparch"], + "code": "E034110", + "year": 2 + }, + "Functioneel Programmeren": { + "abbreviations": ["FP", "Funcprog"], + "code": "C003775", + "year": 2 + }, + "Informatiebeveiliging": { + "abbreviations": ["Infosec"], + "alt": "Information Security", + "code": "E019400", + "year": 3 + }, + "Inleiding tot Elektrotechniek": { + "abbreviations": [], + "alt": "Elektrotechniek", + "code": "C003806", + "year": 3 + }, + "Inleiding tot Telecommunicatie": { + "abbreviations": ["Telecom"], + "code": "C003787", + "year": 3 + }, + "Logisch Programmeren": { + "abbreviations": ["LP", "Logprog", "Prolog"], + "code": "C003783", + "year": 3 + }, + "Modelleren en Simuleren": { + "abbreviations": ["Modsim"], + "code": "C003786", + "year": 3 + }, + "Multimedia": { + "abbreviations": ["MM"], + "code": "C002126", + "year": 2 + }, + "Parallelle Computersystemen": { + "abbreviations": ["PCS"], + "alt": "Parallel Computer Systems", + "code": "E034140", + "year": 3 + }, + "Statistiek en Probabiliteit": { + "abbreviations": ["Stat","Statistiek", "Statprob"], + "code": "C003778", + "year": 2 + }, + "Software Engineering Lab 1": { + "abbreviations": ["SEL1"], + "code": "C003780", + "year": 2 + }, + "Software Engineering Lab 2": { + "abbreviations": ["SEL2"], + "code": "C003784", + "year": 3 + }, + "Systeemprogrammeren": { + "abbreviations": ["Sysprog"], + "code": "C003776", + "year": 2 + }, + "Webdevelopment": { + "abbreviations": ["Webdev"], + "code": "C003779", + "year": 2 + }, + "Wetenschappelijk Rekenen": { + "abbreviations": ["Wetrek"], + "code": "C001521", + "year": 2 + }, + "Wiskundige Modellering in de Ingenieurswetenschappen": { + "abbreviations": ["Wimo"], + "alt": "Wiskundige Modellering", + "code": "C003788", + "year": 3 + } +} \ No newline at end of file