Skip to content

Commit 307a83e

Browse files
davidtso1219madhavarshney
authored andcommitted
First version of grade command
1 parent 8ce645b commit 307a83e

File tree

3 files changed

+284
-0
lines changed

3 files changed

+284
-0
lines changed

bots/py/bot.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ def before_invoke(self, ctx):
2020
"commands.server",
2121
"commands.scrape",
2222
"commands.stats",
23+
"commands.gradescrape",
24+
"commands.grade",
2325
]
2426

2527
if __name__ == "__main__":

bots/py/commands/grade.py

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import json
2+
import discord
3+
import asyncio
4+
from discord.ext import commands
5+
from emojis import emojis, numeric_emojis, num_emojis
6+
from page import Page
7+
8+
years = ["2015-2016", "2016-2017", "2017-2018", "2018-2019", "2019-2020"]
9+
10+
11+
@commands.command()
12+
async def grade(ctx, *, msg: str = " "):
13+
14+
prof_and_class = msg.split(" for ")
15+
usage_str = '**Usage:** \n`1. ?grade "Professor" for "Subject" "(Class Code)"`\n`2. ?grade "Professor" for everything`'
16+
17+
if len(prof_and_class) != 2:
18+
await ctx.channel.send(usage_str)
19+
return
20+
21+
target_prof = prof_and_class[0].split()
22+
the_class = prof_and_class[1].split()
23+
depart = the_class[0]
24+
25+
if len(the_class) > 2:
26+
await ctx.channel.send(usage_str)
27+
return
28+
29+
elif len(the_class) == 2:
30+
classCode = the_class[1]
31+
32+
else:
33+
classCode = ""
34+
35+
data = getProfessorData(target_prof, depart, classCode)
36+
37+
if ifEmpty(data):
38+
await ctx.channel.send(content=ctx.message.author.mention + " No Result!")
39+
return
40+
41+
pages = generatePages(data)
42+
43+
embed = pages[0].getEmbed()
44+
msg = await ctx.channel.send(content=ctx.message.author.mention, embed=embed)
45+
await showReactions(msg, pages[0])
46+
47+
# Check function for the wait_for function later.
48+
def check(reaction, user):
49+
emoji = str(reaction.emoji)
50+
return (
51+
(emoji in num_emojis or emoji in emojis.values())
52+
and user == ctx.author
53+
and reaction.message.id == msg.id
54+
and user.id != ctx.bot.user.id
55+
)
56+
57+
# Get the message from users.
58+
curr = pages[0]
59+
60+
try:
61+
62+
while True:
63+
64+
reaction, user = await ctx.bot.wait_for(
65+
"reaction_add", timeout=20.0, check=check
66+
)
67+
68+
if str(reaction.emoji) == emojis["next"]:
69+
curr = curr.next
70+
71+
elif str(reaction.emoji) == emojis["back"]:
72+
curr = curr.prev
73+
74+
elif str(reaction.emoji) == emojis["exit"]:
75+
await msg.delete()
76+
return
77+
78+
await msg.edit(embed=curr.getEmbed())
79+
await showReactions(msg, curr)
80+
81+
except asyncio.TimeoutError:
82+
await msg.clear_reactions()
83+
return
84+
85+
86+
def getProfessorData(target_prof, subject, classCode):
87+
88+
with open("data/grades.json") as f:
89+
jsonData = json.load(f)
90+
91+
interested = ["quarter", "subject", "number", "A", "B", "C", "D", "F", "W"]
92+
allData = {}
93+
94+
for year, instructors in jsonData.items():
95+
96+
found = []
97+
data_of_this_year = {}
98+
99+
for instructor_data in instructors:
100+
101+
instructor_name = instructor_data["professor"]
102+
flag = True
103+
104+
for target_prof_name in target_prof:
105+
106+
if target_prof_name.lower() not in instructor_name.lower():
107+
flag = False
108+
break
109+
110+
if (
111+
flag
112+
and subject.upper() == instructor_data["subject"]
113+
and (classCode.upper() in instructor_data["number"] or not classCode)
114+
):
115+
116+
if instructor_name not in found:
117+
found.append(instructor_name)
118+
data_of_this_year[instructor_name] = []
119+
120+
new_data = {}
121+
for i in interested:
122+
new_data[i] = instructor_data[i]
123+
124+
data_of_this_year[instructor_name].append(new_data)
125+
126+
allData[year] = data_of_this_year
127+
128+
return allData
129+
130+
131+
def generatePages(data):
132+
pages = []
133+
letters = ["A", "B", "C", "D", "F", "W"]
134+
135+
for year in years[::-1]:
136+
137+
instructors = data[year]
138+
139+
title = f"In {year}"
140+
msg = ""
141+
142+
for instructor in instructors.keys():
143+
144+
classList = instructors[instructor]
145+
msg += f'**For Professor {" ".join(instructor.split(",")[::-1])}\n**'
146+
147+
for classData in classList:
148+
msg += f"`{classData['quarter']}" + " " * (
149+
len("spring") - len(classData["quarter"])
150+
)
151+
152+
classNumber = classData["number"][-4:].replace("0", " ")
153+
classNumber = classNumber.replace("F", " ")
154+
155+
if len(classNumber) < 4:
156+
classNumber = " " * (4 - len(classNumber)) + classNumber
157+
158+
msg += f"` `{classNumber}` "
159+
160+
for letter in letters:
161+
try:
162+
num = int(classData[letter])
163+
except ValueError:
164+
num = 0
165+
166+
if num < 10:
167+
msg += f"` {num} {letter}` "
168+
else:
169+
msg += f"` {num} {letter}` "
170+
171+
msg += "\n"
172+
173+
msg += "\n\n"
174+
175+
msg += f"Check this: https://transfercamp.com/foothill-college-grade-distribution-{year}"
176+
page = Page(title, msg, 0, len(pages) + 1)
177+
178+
if pages:
179+
page.prev = pages[-1]
180+
pages[-1].next = page
181+
182+
pages.append(page)
183+
184+
return pages
185+
186+
187+
def ifEmpty(data):
188+
empty = True
189+
190+
for year in data:
191+
if data[year]:
192+
empty = False
193+
194+
return empty
195+
196+
197+
async def showReactions(msg, page):
198+
await msg.clear_reactions()
199+
200+
if page.hasPrev():
201+
await msg.add_reaction(emojis["back"])
202+
203+
for emoji in list(numeric_emojis.values())[0 : page.count]:
204+
await msg.add_reaction(emoji)
205+
206+
if page.hasNext():
207+
await msg.add_reaction(emojis["next"])
208+
209+
await msg.add_reaction(emojis["exit"])
210+
211+
212+
def setup(client):
213+
client.add_command(grade)

bots/py/commands/gradescrape.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import json
2+
import requests
3+
from discord.ext import commands
4+
from bs4 import BeautifulSoup
5+
6+
years = ["2015-2016", "2016-2017", "2017-2018", "2018-2019", "2019-2020"]
7+
8+
9+
@commands.command()
10+
async def gradescrape(ctx):
11+
await ctx.send("Scraping grade distributions...")
12+
13+
all_data = {}
14+
15+
for year in years:
16+
url = "https://transfercamp.com/foothill-college-grade-distribution-" + year
17+
18+
# Let's GET the HTML page
19+
res = requests.get(url)
20+
# Throw an error and stop the script if the status code is not 200 OK
21+
res.raise_for_status()
22+
23+
# Make a soup from our HTML string
24+
soup = BeautifulSoup(res.text, "html5lib")
25+
26+
# Find all the 'tr' tags that contain instructor data
27+
rows = soup.select("tbody tr")
28+
29+
# Table headers, used for convenience
30+
table_headers = [
31+
"year",
32+
"quarter",
33+
"professor",
34+
"subject",
35+
"number",
36+
"course ID",
37+
"A",
38+
"B",
39+
"C",
40+
"D",
41+
"F",
42+
"W",
43+
]
44+
45+
data_of_a_year = []
46+
47+
for row in rows:
48+
49+
column = row.find_all("td")
50+
51+
text_data = [td.get_text().strip() for td in column]
52+
53+
instructor_data = dict(zip(table_headers, text_data))
54+
55+
instructor_data["subject"] = instructor_data["subject"].replace(" ", "")
56+
57+
data_of_a_year.append(instructor_data)
58+
59+
all_data[year] = data_of_a_year
60+
print(year)
61+
62+
with open("data/grades.json", "w") as outfile:
63+
json.dump(all_data, outfile)
64+
65+
await ctx.send("Finished scraping grade distributions!")
66+
67+
68+
def setup(client):
69+
client.add_command(gradescrape)

0 commit comments

Comments
 (0)