Implemented submissions. Works, although in a pretty nasty manner.

This commit is contained in:
Boyan 2024-04-08 21:40:52 +02:00
parent b819305704
commit 3c63a64eac
3 changed files with 195 additions and 120 deletions

2
.gitignore vendored
View File

@ -1,6 +1,6 @@
# Config - Testing # Config - Testing
config.py config.py
baller.py tests/
#Doc env #Doc env
.docs_env .docs_env

View File

@ -1,126 +1,200 @@
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from exceptions.IllegalAction import IllegalAction from exceptions.IllegalAction import IllegalAction
import re import re
from json import loads
from time import sleep
class ExerciseGroup():
def __init__(self, url:str, soup, session, parent): class ExerciseGroup:
self.url = url def __init__(self, url: str, soup, session, parent):
self.name = soup.text self.url = url
self.__raw = soup self.name = soup.text
self.session = session self.__raw = soup
self.parent = parent # This is unnecessary, but I'll keep it for now self.session = session
self.request = self.session.get(self.url) self.parent = parent # This is unnecessary, but I'll keep it for now
self.soup = BeautifulSoup(self.request.text, 'lxml') self.request = self.session.get(self.url)
self.soup = BeautifulSoup(self.request.text, "lxml")
def __str__(self):
return f"ExerciseGroup {self.name} in folder {self.parent.name}" def __str__(self):
return f"ExerciseGroup {self.name} in folder {self.parent.name}"
@property
def amExercise(self): @property
return "ass-submitable" in self.__raw['class'] def amExercise(self):
return "ass-submitable" in self.__raw["class"]
def submit(self):
if not self.amExercise: def submit(self):
raise IllegalAction(message="You are submitting to a folder.") if not self.amExercise:
raise IllegalAction(message="You are submitting to a folder.")
# Logic for submitting
# Logic for submitting
# Test cases
@property # Test cases
def testCases(self): @property
section = self.soup.find_all('div', class_="subsec round shade") def testCases(self):
tcs = [] section = self.soup.find_all("div", class_="subsec round shade")
for div in section: tcs = []
res = div.find("h4", class_="info") for div in section:
if not res: res = div.find("h4", class_="info")
continue if not res:
continue
if "Test cases" in res.text:
for case in div.find_all("div", class_="cfg-line"):
if link := case.find("a"):
tcs.append(link)
return tcs
return None
def downloadTCs(self, path="."):
# Logic for downloading test cases(if any)
# In a div with class "subsec round shade", where there is an h4 with text "Test cases"
if not self.amExercise:
raise IllegalAction(message="You are downloading test cases from a folder.")
for tc in self.testCases:
url = f"https://themis.housing.rug.nl{tc['href']}"
print(f"Downloading {tc.text}")
# download the files
with open(f"{path}/{tc.text}", "wb") as f:
f.write(self.session.get(url).content)
return self.testCases
# Files
@property
def files(self):
details = self.soup.find("div", id=lambda x: x and x.startswith("details"))
cfg_lines = details.find_all("div", class_="cfg-line")
link_list = []
for line in cfg_lines:
key = line.find("span", class_="cfg-key")
if key and "Downloads" in key.text.strip():
# Extract all links in the cfg-val span
links = line.find_all("span", class_="cfg-val")
for link in links:
a = link.find_all("a")
for a in a:
link_list.append(a)
return link_list if link_list else None
def downloadFiles(self, path="."):
for file in self.files:
print(f"Downloading file {file.text}")
url = f"https://themis.housing.rug.nl{file['href']}"
with open(f"{path}/{file.text}", "wb") as f:
f.write(self.session.get(url).content)
return self.files
@property
def exercises(self) -> list:
if self.amExercise:
return self
section = self.soup.find("div", class_="ass-children")
try:
submittables = section.find_all("a", class_="ass-submitable")
except AttributeError:
return None
return [
ExerciseGroup(
f"https://themis.housing.rug.nl{x['href']}", x, self.session, self
)
for x in submittables
]
@property
def folders(self) -> list:
section = self.soup.find("div", class_="ass-children")
try:
folders = section.find_all("a", class_="ass-group")
except AttributeError:
return None
return [
ExerciseGroup(f"https://themis.housing.rug.nl{x['href']}", x, session, self)
for x in folders
]
def __parseTable(self, soup, url):
cases = soup.find_all('tr', class_='sub-casetop')
fail_pass = {}
i = 1
for case in cases:
status = case.find('td', class_='status-icon')
# queued status-icon
if "queued" in status.get("class"):
sleep(1) # <- 🗿
return self.__waitForResult(url)
if "Passed" in status.text:
fail_pass[i] = True
elif "Wrong output" in status.text:
fail_pass[i] = False
elif ("No status" or "error") in status.text:
fail_pass[i] = None
i += 1
return fail_pass
if "Test cases" in res.text: def __waitForResult(self, url):
for case in div.find_all("div", class_="cfg-line"): # This waits for result and returns a bundled info package
if link := case.find("a"): r = self.session.get(url)
tcs.append(link) soup = BeautifulSoup(r.text, "lxml")
return tcs return self.__parseTable(soup, url)
return None
def downloadTCs(self, path="."):
# Logic for downloading test cases(if any)
# In a div with class "subsec round shade", where there is an h4 with text "Test cases"
if not self.amExercise:
raise IllegalAction(message="You are downloading test cases from a folder.")
for tc in self.testCases:
url= f"https://themis.housing.rug.nl{tc['href']}"
print(f"Downloading {tc.text}")
# download the files
with open(f"{path}/{tc.text}", "wb") as f:
f.write(self.session.get(url).content)
return self.testCases
# Files
@property
def files(self):
details = self.soup.find('div', id=lambda x: x and x.startswith('details'))
cfg_lines = details.find_all('div', class_='cfg-line')
# Submit
link_list = [] def submit(self, files: list, judge=True, wait=True):
for line in cfg_lines:
key = line.find('span', class_='cfg-key')
if key and "Downloads" in key.text.strip():
# Extract all links in the cfg-val span
links = line.find_all('span', class_='cfg-val')
for link in links:
a = link.find_all('a')
for a in a:
link_list.append(a)
return link_list if link_list else None
# Find the form with submit and store the action as url
# Store then the data-suffixes as file_types - dictionary
form = self.soup.find("form")
def downloadFiles(self, path="."): if not form:
for file in self.files: raise IllegalAction(message="You cannot submit to this assignment.")
print(f"Downloading file {file.text}")
url = f"https://themis.housing.rug.nl{file['href']}" url = "https://themis.housing.rug.nl" + form["action"]
with open(f"{path}/{file.text}", "wb") as f: file_types = loads(form["data-suffixes"])
f.write(self.session.get(url).content)
return self.files if isinstance(files, str):
temp = []
# idea exercises and folders are identical, maybe merge them? temp.append(files)
@property files = temp
def exercises(self) -> list:
if self.amExercise: # Package the files up into files[]
return self
packaged_files = []
section = self.soup.find('div', class_="ass-children") data = {}
try: found_type = ""
submittables = section.find_all('a', class_="ass-submitable") for file in files:
except AttributeError: for t in file_types:
return None if t in file:
found_type = file_types[t]
return [ break
ExerciseGroup(f"https://themis.housing.rug.nl{x['href']}", if not found_type:
x, raise IllegalAction(message="Illegal filetype for this assignment.")
self.session,
self) packaged_files.append((f"files[]", (file, open(file, "rb"), "text/x-csrc")))
for x in submittables]
data = {"judgenow": "true" if judge else "false", "judgeLanguage": found_type}
@property
def folders(self) -> list: resp = self.session.post(url, files=packaged_files, data=data)
section = self.soup.find('div', class_="ass-children")
try: # Close each file
folders = section.find_all('a', class_="ass-group") i = 0
except AttributeError: for f in packaged_files:
return None if i == 1:
f[1].close()
return [ i = 0
ExerciseGroup(f"https://themis.housing.rug.nl{x['href']}", else:
x, i += 1
session,
self) if not wait:
for x in folders] return resp.url if "@submissions" in resp.url else None
return self.__waitForResult(resp.url)

View File

@ -31,6 +31,7 @@ class Themis:
# get the csrf token and add it to payload # get the csrf token and add it to payload
csrfToken = soup.find('input',attrs = {'name':'_csrf'})['value'] csrfToken = soup.find('input',attrs = {'name':'_csrf'})['value']
data['_csrf'] = csrfToken data['_csrf'] = csrfToken
data['sudo'] = user.lower()
# Login # Login
r = s.post(url,data=data,headers = headers) r = s.post(url,data=data,headers = headers)