Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .documentation_pictures/test_cases/courses.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions Test_Cases.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,27 @@
![](.documentation_pictures/test_cases/.outline%20blah.png)
1. `.outline`
![](.documentation_pictures/test_cases/.outline.png)
1. `/courses`
![](.documentation_pictures/test_cases/courses.png)
1. `/courses department:<existing department>`
![](.documentation_pictures/test_cases/courses_department_math.png)
1. `/courses department:<non existent department>`
![](.documentation_pictures/test_cases/courses_department_blah.png)
1. `/courses level:<number between 100-999>`
![](.documentation_pictures/test_cases/courses_level_200.png)
1. `/courses level:<number outside 100-999>`
![](.documentation_pictures/test_cases/courses_level_1.png)
1. `/courses term:<valid term> year:<valid year>`
![](.documentation_pictures/test_cases/courses_term_and_year_set.png)
1. `/courses term:<invalid term> year:<valid year>`
![](.documentation_pictures/test_cases/courses_term_blah.png)
1. `/courses term:<set> year:<unset>` OR `/courses term:<unset> year:<set>`
![](.documentation_pictures/test_cases/courses_term_or_year_unset.png)
1. `/courses department:<existent department> level:<valid number>`
![](.documentation_pictures/test_cases/courses_department_math_level_200.png)
1. `/courses department:<existent department> term:<valid term> year:<valid year>`
![](.documentation_pictures/test_cases/courses_department_phys_term_summer_year_2020.png)
1. `/courses level:<valid number> term:<valid term> year:<valid year>`
![](.documentation_pictures/test_cases/courses_level_300_term_spring_year_2021.png)
1. `/courses department:<existent department> level:<valid number> term:<valid term> year:<valid year>`
![](.documentation_pictures/test_cases/courses_department_stat_level_300_term_fall_year_2024.png)
130 changes: 130 additions & 0 deletions wall_e/extensions/sfu.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@
import json # dont need since requests has built in json encoding and decoding
import re
import time
from typing import Optional

import aiohttp

import discord
from discord import app_commands
from discord.ext import commands

from utilities.global_vars import bot, wall_e_config

from utilities.embed import embed, WallEColour
from utilities.file_uploading import start_file_uploading
from utilities.setup_logger import Loggers
from utilities.paginate import paginate_embed


class SFU(commands.Cog):
Expand Down Expand Up @@ -502,6 +507,131 @@ async def outline(self, ctx, *course):
if e_obj is not False:
await ctx.send(embed=e_obj, reference=ctx.message)

async def _embed_followup_error_message(self, interaction: discord.Interaction, title, desc):
e_obj = await embed(
self.logger, interaction=interaction,
title=title,
description=desc,
colour=WallEColour.ERROR,
)
if e_obj:
msg = await interaction.followup.send(embed=e_obj)
await asyncio.sleep(10)
await msg.delete()

def _parse_courses_to_embed(self, courses: list) -> list:
content_to_embed = []
number_of_courses_per_page = 20
number_of_courses = 0
content = ""

for i, course in enumerate(courses):
try:
course_title = course["title"]
course_number = course["text"]
except Exception:
self.logger.debug("[SFU courses()] cannot find course title or number, skipping")
continue

content += f"\n{course_number} - {course_title}"

number_of_courses += 1
if number_of_courses % number_of_courses_per_page == 0:
number_of_courses = 0
content_to_embed.append(
[["Code - Title", content]]
)
content = ""

# Needed in the off chance that a course title cannot be found and that course is the last on the list
if content:
content_to_embed.append(
[["Code - Title", content]]
)

return content_to_embed

@app_commands.command(name="courses", description="Gets all offered courses")
@app_commands.describe(department="Specify the department to search. Examples: STAT, PHYS, MATH")
@app_commands.describe(level="Specify the level of courses to filter for. Examples: 100, 200, 400")
@app_commands.describe(term="Specify the semester to search for. Requires year to be specified. "
"Examples: Spring, Summer, Fall")
@app_commands.describe(year="Specify the year to search for. Requires term to be specified. "
"Examples: 2020, 2024, 2025")
async def courses(self, interaction: discord.Interaction, department: str = "", level: Optional[int] = None,
term: str = "registration", year: str = "registration"):
self.logger.info(
f"[SFU courses()] courses command detected from user {interaction.user} with arguments: "
f"department {department}, level {level}, term {term}, year {year}"
)
await interaction.response.defer()
err_msg_title = "SFU Courses Error"

# The default course selection if not specified
departments = ["CMPT", "MATH", "MACM"]
if department:
Comment thread
modernNeo marked this conversation as resolved.
departments = [department.upper()]

if level is not None and (level < 100 or level >= 1000):
self.logger.debug("[SFU courses()] invalid level argument")
desc = ("Invalid level argument. Level must be within 100 and 999.\n"
"Example: `/courses level:200`")
await self._embed_followup_error_message(interaction, err_msg_title, desc)
return

if (term == "registration" and year != "registration") or (term != "registration" and year == "registration"):
self.logger.debug("[SFU courses()] invalid term/year arguments")
desc = ("Both term and year arguments must be either set or unset.\n"
"Example: `/courses term:summer year:2021` # set\n"
"Example: `/courses` # unset")
await self._embed_followup_error_message(interaction, err_msg_title, desc)
return

courses = []
for department in departments:
url = f"http://www.sfu.ca/bin/wcm/course-outlines?{year}/{term}/{department}/"
self.logger.debug(f"[SFU courses()] url for get constructed: {url}")

res = await self.req.get(url)
if res.status == 200:
self.logger.debug(f"[SFU courses()] get request for {department} successful, parsing data")
res_json = await res.json()

# parse data for displaying, sorting, and filtering
for course in res_json:
course["text"] = f"{department}{course['text']}"

# we assume all courses have 3 digits
course["value"] = int(course["value"][:3])
if level is None or (course["value"]//100 == level//100):
courses.append(course)
else:
self.logger.debug(f"[SFU courses()] get request for {department} resulted in {res.status}")
Comment thread
modernNeo marked this conversation as resolved.

if len(courses) == 0:
self.logger.debug("[SFU courses()] resulted in no content")
desc = (f"Couldn't find anything for `department: {', '.join(departments)}`, "
f"`level: {level if level is not None else 'all'}`, `term: {term}`, `year: {year}`\n"
f"Maybe no courses are being offered at that time.")
await self._embed_followup_error_message(interaction, err_msg_title, desc)
return

if len(departments) > 1:
courses = sorted(courses, key=lambda k: k["value"])

self.logger.debug("[SFU courses()] parsing data from GET request")
content_to_embed = self._parse_courses_to_embed(courses)

title = (f"{', '.join(departments)} {f'{(level // 100) * 100} level -' if level is not None else '-'} "
f"{'Next term' if term == 'registration' and year == 'registration' else f'{term.title()} {year}'}"
f"\n(Total Courses: {len(courses)})\n")

await paginate_embed(
self.logger, bot, content_to_embed=content_to_embed,
title=title,
interaction=interaction
)

async def cog_unload(self) -> None:
await self.req.close()
await super().cog_unload()
Expand Down