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,8 +1,11 @@
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():
class ExerciseGroup:
def __init__(self, url: str, soup, session, parent): def __init__(self, url: str, soup, session, parent):
self.url = url self.url = url
self.name = soup.text self.name = soup.text
@ -10,14 +13,14 @@ class ExerciseGroup():
self.session = session self.session = session
self.parent = parent # This is unnecessary, but I'll keep it for now self.parent = parent # This is unnecessary, but I'll keep it for now
self.request = self.session.get(self.url) self.request = self.session.get(self.url)
self.soup = BeautifulSoup(self.request.text, 'lxml') self.soup = BeautifulSoup(self.request.text, "lxml")
def __str__(self): def __str__(self):
return f"ExerciseGroup {self.name} in folder {self.parent.name}" return f"ExerciseGroup {self.name} in folder {self.parent.name}"
@property @property
def amExercise(self): def amExercise(self):
return "ass-submitable" in self.__raw['class'] return "ass-submitable" in self.__raw["class"]
def submit(self): def submit(self):
if not self.amExercise: if not self.amExercise:
@ -28,7 +31,7 @@ class ExerciseGroup():
# Test cases # Test cases
@property @property
def testCases(self): def testCases(self):
section = self.soup.find_all('div', class_="subsec round shade") section = self.soup.find_all("div", class_="subsec round shade")
tcs = [] tcs = []
for div in section: for div in section:
res = div.find("h4", class_="info") res = div.find("h4", class_="info")
@ -61,27 +64,25 @@ class ExerciseGroup():
# Files # Files
@property @property
def files(self): def files(self):
details = self.soup.find('div', id=lambda x: x and x.startswith('details')) details = self.soup.find("div", id=lambda x: x and x.startswith("details"))
cfg_lines = details.find_all('div', class_='cfg-line') cfg_lines = details.find_all("div", class_="cfg-line")
link_list = [] link_list = []
for line in cfg_lines: for line in cfg_lines:
key = line.find('span', class_='cfg-key') key = line.find("span", class_="cfg-key")
if key and "Downloads" in key.text.strip(): if key and "Downloads" in key.text.strip():
# Extract all links in the cfg-val span # Extract all links in the cfg-val span
links = line.find_all('span', class_='cfg-val') links = line.find_all("span", class_="cfg-val")
for link in links: for link in links:
a = link.find_all('a') a = link.find_all("a")
for a in a: for a in a:
link_list.append(a) link_list.append(a)
return link_list if link_list else None return link_list if link_list else None
def downloadFiles(self, path="."): def downloadFiles(self, path="."):
for file in self.files: for file in self.files:
print(f"Downloading file {file.text}") print(f"Downloading file {file.text}")
@ -90,37 +91,110 @@ class ExerciseGroup():
f.write(self.session.get(url).content) f.write(self.session.get(url).content)
return self.files return self.files
# idea exercises and folders are identical, maybe merge them?
@property @property
def exercises(self) -> list: def exercises(self) -> list:
if self.amExercise: if self.amExercise:
return self return self
section = self.soup.find('div', class_="ass-children") section = self.soup.find("div", class_="ass-children")
try: try:
submittables = section.find_all('a', class_="ass-submitable") submittables = section.find_all("a", class_="ass-submitable")
except AttributeError: except AttributeError:
return None return None
return [ return [
ExerciseGroup(f"https://themis.housing.rug.nl{x['href']}", ExerciseGroup(
x, f"https://themis.housing.rug.nl{x['href']}", x, self.session, self
self.session, )
self) for x in submittables
for x in submittables] ]
@property @property
def folders(self) -> list: def folders(self) -> list:
section = self.soup.find('div', class_="ass-children") section = self.soup.find("div", class_="ass-children")
try: try:
folders = section.find_all('a', class_="ass-group") folders = section.find_all("a", class_="ass-group")
except AttributeError: except AttributeError:
return None return None
return [ return [
ExerciseGroup(f"https://themis.housing.rug.nl{x['href']}", ExerciseGroup(f"https://themis.housing.rug.nl{x['href']}", x, session, self)
x, for x in folders
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)