Skip to content

Commit 2702944

Browse files
CarrotManMattDonian960MattyTheHackercssbhamdevThatsmusic99
authored
Add everest command (#537)
Signed-off-by: Matt Norton <matt@carrotmanmatt.com> Signed-off-by: Holly <25277367+Thatsmusic99@users.noreply.github.com> Co-authored-by: Donian960 <daniellawson960@gmail.com> Co-authored-by: MattyTheHacker <18513864+MattyTheHacker@users.noreply.github.com> Co-authored-by: CSS <66796201+cssbhamdev@users.noreply.github.com> Co-authored-by: Holly <25277367+Thatsmusic99@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: automatic-pr-updater[bot] <217796550+automatic-pr-updater[bot]@users.noreply.github.com>
1 parent 7ffb913 commit 2702944

2 files changed

Lines changed: 186 additions & 0 deletions

File tree

cogs/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
)
2222
from .delete_all import DeleteAllCommandsCog
2323
from .edit_message import EditMessageCommandCog
24+
from .everest import EverestCommandCog
2425
from .get_token_authorisation import GetTokenAuthorisationCommandCog
2526
from .induct import (
2627
EnsureMembersInductedCommandCog,
@@ -60,6 +61,7 @@
6061
"DeleteAllCommandsCog",
6162
"EditMessageCommandCog",
6263
"EnsureMembersInductedCommandCog",
64+
"EverestCommandCog",
6365
"GetTokenAuthorisationCommandCog",
6466
"InductContextCommandsCog",
6567
"InductSendMessageCog",
@@ -100,6 +102,7 @@ def setup(bot: "TeXBot") -> None:
100102
DeleteAllCommandsCog,
101103
EditMessageCommandCog,
102104
EnsureMembersInductedCommandCog,
105+
EverestCommandCog,
103106
GetTokenAuthorisationCommandCog,
104107
InductContextCommandsCog,
105108
InductSendMessageCog,

cogs/everest.py

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
"""Contains cog classes for any Everest interactions."""
2+
3+
import logging
4+
from enum import Enum
5+
from typing import TYPE_CHECKING, override
6+
7+
import discord
8+
9+
from utils import TeXBotBaseCog
10+
11+
if TYPE_CHECKING:
12+
from collections.abc import Sequence
13+
from collections.abc import Set as AbstractSet
14+
from logging import Logger
15+
from typing import Final
16+
17+
from utils import TeXBotApplicationContext, TeXBotAutocompleteContext
18+
19+
__all__: "Sequence[str]" = ("EverestCommandCog",)
20+
21+
logger: "Final[Logger]" = logging.getLogger("TeX-Bot")
22+
23+
MOUNT_EVEREST_TOTAL_STEPS: "Final[int]" = 44250
24+
25+
26+
class _CourseTypes(Enum):
27+
B_SC = "B.Sc."
28+
M_SCI = "M.Sci."
29+
30+
def get_course_year_weighting(self, course_year: int) -> float:
31+
"""Calculate the grade weighting the given year has for this course type."""
32+
match (self, course_year):
33+
case _CourseTypes.B_SC, 1:
34+
return 0
35+
case _CourseTypes.M_SCI, 1:
36+
return 0
37+
case _CourseTypes.B_SC, 2:
38+
return 0.25
39+
case _CourseTypes.M_SCI, 2:
40+
return 0.2
41+
case _CourseTypes.B_SC, 3:
42+
return 0.75
43+
case _CourseTypes.M_SCI, 3:
44+
return 0.4
45+
case _CourseTypes.M_SCI, 4:
46+
return 0.4
47+
case _:
48+
INVALID_COURSE_YEAR_OR_TYPE_MESSAGE: Final[str] = (
49+
f"Cannot calculate weighting for given course year ('{course_year}') "
50+
f"and type ('{self}')."
51+
)
52+
raise ValueError(INVALID_COURSE_YEAR_OR_TYPE_MESSAGE)
53+
54+
@override
55+
def __str__(self) -> str:
56+
return self.value
57+
58+
59+
class EverestCommandCog(TeXBotBaseCog):
60+
"""Cog class that defines the "/everest" command and its call-back method."""
61+
62+
async def autocomplete_get_course_years(
63+
self, ctx: "TeXBotAutocompleteContext"
64+
) -> "AbstractSet[discord.OptionChoice] | AbstractSet[int] | AbstractSet[str]":
65+
"""Autocomplete for the course year option."""
66+
try:
67+
selected_course_type: _CourseTypes | str = ctx.options["course-type"]
68+
except KeyError:
69+
return {1, 2, 3, 4}
70+
71+
if not isinstance(selected_course_type, _CourseTypes):
72+
selected_course_type = selected_course_type.strip()
73+
74+
if not selected_course_type:
75+
return {1, 2, 3, 4}
76+
77+
try:
78+
selected_course_type = _CourseTypes[selected_course_type]
79+
except ValueError:
80+
return set()
81+
82+
match selected_course_type:
83+
case _CourseTypes.B_SC:
84+
return {1, 2, 3}
85+
case _CourseTypes.M_SCI:
86+
return {1, 2, 3, 4}
87+
88+
@discord.slash_command( # type: ignore[no-untyped-call, misc]
89+
name="everest", description="How many steps of everest is your assignment worth?"
90+
)
91+
@discord.option( # type: ignore[no-untyped-call, misc]
92+
name="course-type",
93+
description="The type of your university course.",
94+
input_type=str,
95+
choices=( # NOTE: Display name is stored in the enum's value.
96+
discord.OptionChoice(name=course_type.value, value=course_type.name)
97+
for course_type in _CourseTypes
98+
),
99+
required=True,
100+
parameter_name="raw_course_type",
101+
)
102+
@discord.option( # type: ignore[no-untyped-call, misc]
103+
name="course-year",
104+
description="The current year of your university course.",
105+
input_type=int,
106+
autocomplete=autocomplete_get_course_years, # NOTE: Choices cannot be used for validation as they are static an preclude the ability to have dynamic autocomplete
107+
required=True,
108+
parameter_name="course_year",
109+
)
110+
@discord.option( # type: ignore[no-untyped-call, misc]
111+
name="percentage-of-module",
112+
description="The percentage of the module that the assignment is worth.",
113+
input_type=float,
114+
autocomplete=discord.utils.basic_autocomplete( # NOTE: Pycord documents that they accept any iterable, testing shows that they only accept lists (generators do not work correctly).
115+
[
116+
discord.OptionChoice(name=f"{percentage * 5:.1f}%", value=percentage * 5)
117+
for percentage in range(1, 21)
118+
]
119+
),
120+
required=True,
121+
parameter_name="percentage_of_module",
122+
)
123+
async def everest( # type: ignore[misc]
124+
self,
125+
ctx: "TeXBotApplicationContext",
126+
raw_course_type: str,
127+
course_year: int,
128+
percentage_of_module: float,
129+
) -> None:
130+
"""Calculate how many steps of Mount Everest an assignment is worth."""
131+
try:
132+
course_type: _CourseTypes = _CourseTypes[raw_course_type]
133+
except KeyError:
134+
await self.command_send_error(
135+
ctx=ctx, message=f"Invalid course type: '{raw_course_type}'."
136+
)
137+
return
138+
139+
if course_year < 1 or course_year > 10:
140+
await self.command_send_error(
141+
ctx=ctx, message=f"Invalid course year: '{course_year}'."
142+
)
143+
return
144+
145+
if percentage_of_module < 0 or percentage_of_module > 100:
146+
await self.command_send_error(
147+
ctx=ctx,
148+
message=(
149+
f"Percentage of module: '{percentage_of_module}' is not valid. "
150+
"Please enter a percentage between 0 - 100."
151+
),
152+
)
153+
return
154+
155+
try:
156+
course_year_weighting: float = course_type.get_course_year_weighting(course_year)
157+
except KeyError:
158+
await self.command_send_error(
159+
ctx,
160+
message=(
161+
f"Invalid course year ('{course_year}') for course type '{course_type}'."
162+
),
163+
)
164+
return
165+
166+
logger.debug("User %s used '/everest' command", ctx.user)
167+
168+
await ctx.respond(
169+
content=(
170+
f"**Course**: {course_type}\n"
171+
f"**Year**: {course_year}\n"
172+
f"**Percentage of Module**: {percentage_of_module:.1f}%\n"
173+
f"This assignment is worth { # NOTE: Assumes all modules are 20 credits
174+
int(
175+
(percentage_of_module / 100)
176+
* (1 / 6)
177+
* course_year_weighting
178+
* MOUNT_EVEREST_TOTAL_STEPS
179+
)
180+
} steps of Mt. Everest!"
181+
),
182+
ephemeral=True,
183+
)

0 commit comments

Comments
 (0)