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
class ExerciseGroup(): from time import sleep
def __init__(self, url:str, soup, session, parent):
self.url = url
self.name = soup.text
self.__raw = soup
self.session = session
self.parent = parent # This is unnecessary, but I'll keep it for now
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}"
@property
def amExercise(self):
return "ass-submitable" in self.__raw['class']
def submit(self):
if not self.amExercise:
raise IllegalAction(message="You are submitting to a folder.")
# Logic for submitting
# Test cases
@property
def testCases(self):
section = self.soup.find_all('div', class_="subsec round shade")
tcs = []
for div in section:
res = div.find("h4", class_="info")
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
class ExerciseGroup:
def __init__(self, url: str, soup, session, parent):
self.url = url
self.name = soup.text
self.__raw = soup
self.session = session
self.parent = parent # This is unnecessary, but I'll keep it for now
self.request = self.session.get(self.url)
self.soup = BeautifulSoup(self.request.text, "lxml")
def downloadFiles(self, path="."): def __str__(self):
for file in self.files: return f"ExerciseGroup {self.name} in folder {self.parent.name}"
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
# idea exercises and folders are identical, maybe merge them? @property
@property def amExercise(self):
def exercises(self) -> list: return "ass-submitable" in self.__raw["class"]
if self.amExercise:
return self
section = self.soup.find('div', class_="ass-children") def submit(self):
try: if not self.amExercise:
submittables = section.find_all('a', class_="ass-submitable") raise IllegalAction(message="You are submitting to a folder.")
except AttributeError:
return None
return [ # Logic for submitting
ExerciseGroup(f"https://themis.housing.rug.nl{x['href']}",
x,
self.session,
self)
for x in submittables]
@property # Test cases
def folders(self) -> list: @property
section = self.soup.find('div', class_="ass-children") def testCases(self):
try: section = self.soup.find_all("div", class_="subsec round shade")
folders = section.find_all('a', class_="ass-group") tcs = []
except AttributeError: for div in section:
return None res = div.find("h4", class_="info")
if not res:
continue
return [ if "Test cases" in res.text:
ExerciseGroup(f"https://themis.housing.rug.nl{x['href']}", for case in div.find_all("div", class_="cfg-line"):
x, if link := case.find("a"):
session, tcs.append(link)
self) return tcs
for x in folders] 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
def __waitForResult(self, url):
# This waits for result and returns a bundled info package
r = self.session.get(url)
soup = BeautifulSoup(r.text, "lxml")
return self.__parseTable(soup, url)
# Submit
def submit(self, files: list, judge=True, wait=True):
# 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")
if not form:
raise IllegalAction(message="You cannot submit to this assignment.")
url = "https://themis.housing.rug.nl" + form["action"]
file_types = loads(form["data-suffixes"])
if isinstance(files, str):
temp = []
temp.append(files)
files = temp
# Package the files up into files[]
packaged_files = []
data = {}
found_type = ""
for file in files:
for t in file_types:
if t in file:
found_type = file_types[t]
break
if not found_type:
raise IllegalAction(message="Illegal filetype for this assignment.")
packaged_files.append((f"files[]", (file, open(file, "rb"), "text/x-csrc")))
data = {"judgenow": "true" if judge else "false", "judgeLanguage": found_type}
resp = self.session.post(url, files=packaged_files, data=data)
# Close each file
i = 0
for f in packaged_files:
if i == 1:
f[1].close()
i = 0
else:
i += 1
if not wait:
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)