Used the API for year logic

This commit is contained in:
Boyan 2024-11-18 20:04:02 +01:00
parent f6e6bc28d2
commit da4705b56a

View File

@ -1,9 +1,7 @@
""" """
Main class for the Themis API Main class for the Themis API using the new JSON endpoints.
""" """
import urllib3
import keyring import keyring
import getpass import getpass
from requests import Session from requests import Session
@ -11,41 +9,49 @@ from bs4 import BeautifulSoup
from .year import Year from .year import Year
from .exceptions.illegal_action import IllegalAction from .exceptions.illegal_action import IllegalAction
# Disable warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class Themis: class Themis:
""" """
login: Login to Themis Main class for interacting with Themis.
get_year: Get a year object - login: Login to Themis
all_years: Get all years - get_year: Get a year object
- all_years: Get all years
""" """
def __init__(self, user: str): def __init__(self, user: str):
"""
Initialize Themis object, logging in with the given user.
Args:
user (str): Username to login with.
Attributes:
user (str): Username.
password (str): Password, retrieved from keyring.
base_url (str): Base URL of the Themis website.
session (requests.Session): Authenticated session.
"""
self.user = user self.user = user
self.password = self.__get_password() self.password = self.__get_password()
self.session = self.login(user, self.password) self.base_url = "https://themis.housing.rug.nl"
self.session = self.login(self.user, self.password)
def __get_password(self) -> str: def __get_password(self) -> str:
""" """
Retrieve the password from the keyring, prompting the user if not found. Retrieve the password from the keyring, prompting the user if not found.
""" """
password = keyring.get_password(f'{self.user}-temmies', self.user) password = keyring.get_password(f"{self.user}-temmies", self.user)
if not password: if not password:
print(f"Password for user '{self.user}' not found in keyring.") print(f"Password for user '{self.user}' not found in keyring.")
password = getpass.getpass(prompt=f"Enter password for {self.user}: ") password = getpass.getpass(prompt=f"Enter password for {self.user}: ")
keyring.set_password(f'{self.user}-temmies', self.user, password) keyring.set_password(f"{self.user}-temmies", self.user, password)
print("Password saved securely in keyring.") print("Password saved securely in keyring.")
return password return password
def login(self, user: str, passwd: str) -> Session: def login(self, user: str, passwd: str) -> Session:
""" """
login(self, user: str, passwd: str) -> Session Login to Themis using the original method, parsing CSRF token from the login page.
Login to Themis
Set user to your student number and passwd to your password
""" """
session = Session()
login_url = f"{self.base_url}/log/in"
user_agent = ( user_agent = (
"Mozilla/5.0 (X11; Linux x86_64) " "Mozilla/5.0 (X11; Linux x86_64) "
@ -57,52 +63,52 @@ class Themis:
data = {"user": user, "password": passwd, "null": None} data = {"user": user, "password": passwd, "null": None}
with Session() as s: # Get login page to retrieve CSRF token
url = "https://themis.housing.rug.nl/log/in" response = session.get(login_url, headers=headers, verify=False)
r = s.get(url, headers=headers, verify=False) if response.status_code != 200:
soup = BeautifulSoup(r.text, "lxml") raise ConnectionError("Failed to connect to Themis login page.")
# get the csrf token and add it to payload # Parse CSRF token from login page
csrf_token = soup.find("input", attrs={"name": "_csrf"})["value"] soup = BeautifulSoup(response.text, "lxml")
csrf_input = soup.find("input", attrs={"name": "_csrf"})
if not csrf_input or not csrf_input.get("value"):
raise ValueError("Unable to retrieve CSRF token.")
csrf_token = csrf_input["value"]
data["_csrf"] = csrf_token data["_csrf"] = csrf_token
data["sudo"] = user.lower() data["sudo"] = user.lower()
# Login # Attempt login
r = s.post(url, data=data, headers=headers) response = session.post(login_url, data=data, headers=headers)
if "Invalid credentials" in response.text:
# check if login was successful
log_out = "Welcome, logged in as" in r.text
if "Invalid credentials" in r.text:
# Prompt for password again # Prompt for password again
print("Invalid credentials. Please try again.") print("Invalid credentials. Please try again.")
passwd = getpass.getpass(prompt="Enter password: ") passwd = getpass.getpass(prompt="Enter password: ")
keyring.set_password(f'{self.user}-temmies', self.user, passwd) keyring.set_password(f'{self.user}-temmies', self.user, passwd)
return self.login(user, passwd) return self.login(user, passwd)
elif "Welcome, logged in as" not in response.text:
raise ValueError("Login failed for an unknown reason.")
return s return session
def get_year(self, start: int, end: int) -> Year: def get_year(self, year_path: str) -> Year:
""" """
get_year(self, start: int, end: int) -> Year Gets a Year object using the year path (e.g., '2023-2024').
Gets a year object
Set start to the start year and end to the end year (e.g. 2023-2024)
""" """
return Year(self.session, start, end) return Year(self.session, year_path)
def all_years(self) -> list[Year]: def all_years(self) -> list:
""" """
get_years(self, start: int, end: int) -> list[Year] Gets all visible years as Year objects.
Gets all visible years
""" """
# All of them are in a big ul at the beginning of the page navigation_url = f"{self.base_url}/api/navigation/"
r = self.session.get(self.url) response = self.session.get(navigation_url)
soup = BeautifulSoup(r.text, "lxml") if response.status_code != 200:
ul = soup.find("ul", class_="round") raise ConnectionError("Failed to retrieve years from Themis API.")
lis = ul.find_all("li", class_="large")
years_data = response.json()
years = [] years = []
for li in lis: for year_info in years_data:
# format: 2019-2020 if year_info.get("visible", False):
year = li.a.text.split("-") year_path = year_info["path"].strip("/")
years.append(Year(self.session, int(year[0]), int(year[1]))) years.append(Year(self.session, year_path))
return years
return years # Return a list of year objects