Skip to content

Module Cogs & Commands

The ModulSearch Cog (src.oscar.cogs.modul) is the central interaction point. It manages the connection between Discord slash commands and the university module data.

Architecture

classDiagram
    class ModulSearch {
        +bot : Bot
        +catalogue : Catalogue
        +module_autocomplete(interaction, current)
        +search_module(interaction, module_name)
        +filter(interaction)
        +plan(interaction)
        +start(interaction)
        +feedback(interaction)
    }

    class Catalogue {
        -_module_catalogue : list
        -_modules : list~ModuleMatch~
        +find_match(module_name)
        +get_module_list()
    }

    class Views {
        <<Interface>>
        ModuleView
        SelectView
        SemesterplanView
        FeedbackView
        StartView
    }

    ModulSearch --> Catalogue : queries
    ModulSearch ..> Views : creates

ModulSearch Class

The class initializes the Catalogue (cached module list) on startup using the 2018 view ID to ensure fast data retrieval.

src.oscar.cogs.modul.ModulSearch

ModulSearch(bot: Bot)

Bases: Cog

Class for module related commands

Source code in src/oscar/cogs/modul.py
def __init__(self, bot: commands.Bot):
    self.bot: commands.Bot = bot
    self.module_catalogue: Catalogue = Catalogue(720)
    logger.info("Loaded ModulSearch cog")

search_module async

search_module(interaction: Interaction, modul: str)

Command for searching a single module by name

Parameters:

Name Type Description Default
interaction Interaction

automatically provided interaction object.

required
modul str

The id of a picked suggestion, or a module title typed by hand.

required
Source code in src/oscar/cogs/modul.py
@app_commands.command(name="module", description="Modul suchen und ansehen (search a module)")
@app_commands.autocomplete(modul=module_autocomplete)
async def search_module(self, interaction: discord.Interaction, modul: str):
    """ Command for searching a single module by name

        Parameters:
            interaction: automatically provided interaction object.
            modul: The id of a picked suggestion, or a module title typed by hand.
    """
    _ = await interaction.response.defer(ephemeral=True)

    language = get_user_language(interaction.user.id)

    try:
        module = resolve_module(modul)
    except IndexError:
        await interaction.followup.send(
            t(language, "not_found", MODULE_TEXTS).format(name=modul),
            ephemeral=True,
        )
        return

    await interaction.followup.send(
        view=ModuleView(
            interaction.user.id,
            module
        ),
        ephemeral=True,
    )

filter async

filter(interaction: Interaction)

Command to open the filter dialog for module filtering by different criteria

Parameters:

Name Type Description Default
interaction Interaction

automatically provided interaction object.

required
Source code in src/oscar/cogs/modul.py
@app_commands.command(
    name="filter",
    description="Module filtern nach CP, Sprache, Prüfung (filter modules)",
)
async def filter(self, interaction: discord.Interaction):
    """ Command to open the filter dialog for module filtering by different criteria

        Parameters:
            interaction: automatically provided interaction object.
    """
    # ephemeral: the mask belongs to one student, nobody else may click it
    _ = await interaction.response.send_message(
        view=SelectView(interaction.user.id),
        ephemeral=True,
    )

plan async

plan(interaction: Interaction)

Command to open the semester plan dialog for the user

Parameters:

Name Type Description Default
interaction Interaction

automatically provided interaction object.

required
Source code in src/oscar/cogs/modul.py
@app_commands.command(
    name="semesterplan",
    description="Deine gemerkten Module (your semester plan)",
)
async def plan(self, interaction: discord.Interaction):
    """ Command to open the semester plan dialog for the user

        Parameters:
            interaction: automatically provided interaction object.
    """
    # ephemeral: a semester plan is personal, and its delete buttons write to the
    # plan of the student the view was built for
    await interaction.response.defer(ephemeral=True)
    user_id = interaction.user.id
    _ = await interaction.followup.send(
        view=SemesterplanView(user_id=user_id),
        ephemeral=True,
    )

start async

start(interaction: Interaction)

Command to open the start dialog for the user

Parameters:

Name Type Description Default
interaction Interaction

automatically provided interaction object.

required
Source code in src/oscar/cogs/modul.py
@app_commands.command(
    name="start",
    description="Erste Schritte: Studiengang und Semester wählen (first steps)",
)
async def start(self, interaction: discord.Interaction):
    """ Command to open the start dialog for the user

        Parameters:
            interaction: automatically provided interaction object.
    """
    member = interaction.user
    semester_number: int = -1
    user_id = interaction.user.id

    for role in member.roles:
        match = re.match(r"Semester\s+(\d+)", role.name)
        if match:
            semester_number = int(match.group(1))  # Hier hast du die Zahl als Integer
            break


    # ephemeral: these buttons overwrite the settings of the student named here
    _ = await interaction.response.send_message(
        view=StartView(
            user_id,
            semester_number
        ),
        ephemeral=True,
    )

feedback async

feedback(interaction: Interaction)

Command to open the feedback dialog for the user

Parameters:

Name Type Description Default
interaction Interaction

automatically provided interaction object.

required
Source code in src/oscar/cogs/modul.py
@app_commands.command(
    name="feedback",
    description="Sag uns, was du von OSCAR hältst (send feedback)"
)
async def feedback(self,interaction: discord.Interaction):
    """ Command to open the feedback dialog for the user

        Parameters:
            interaction: automatically provided interaction object.
    """
    await open_feedback(interaction)

Module Setup

Required function for discord.py to load this extension.

src.oscar.cogs.modul.setup async

setup(bot: Bot)

Setup the Module cog.

Source code in src/oscar/cogs/modul.py
async def setup(bot: commands.Bot):
    """ Setup the Module cog."""
    # SERVER_ID = int(os.getenv("DISCORD_SERVER_ID"))
    # guild = discord.Object(id=SERVER_ID)

    await bot.add_cog(ModulSearch(bot))

Technical Implementation Details

module_autocomplete

Note: This is a helper method bound via the @autocomplete decorator to the /module command.

Provides live suggestions while the user types.

  • Logic: Uses the Catalogue class to perform a fuzzy search (via rapidfuzz) on cached module titles.
  • Performance: Limits results to the top 5 matches to ensure UI responsiveness.
  • Ambiguity: Some modules share a title. Such an entry shows its id in brackets, for example Introduction to Simulation (100372). A unique title is shown as it is.
  • Value: Every choice carries the module id as its value. resolve_module turns that id back into a module, so a picked entry always opens the module the user saw. A title typed by hand is still looked up with Module.from_name.
  • Parameters:
    • interaction: The Discord interaction context.
    • current (str): The partial string typed by the user.

Smart Semester Detection

The commands /start and /feedback feature automatic context detection. They iterate through the user's roles to find a pattern matching r"Semester\s+(\d+)".

  • Goal: Pre-fills the semester selection in the UI (e.g., StartView or FeedbackView) to reduce user friction.
  • Fallback: If no role matches, the semester defaults to -1, prompting a manual selection.

Command Mapping

Slash Command Python Method View Class
/module search_module ModuleView
/filter filter SelectView
/semesterplan plan SemesterplanView
/feedback feedback FeedbackView
/start start StartView