-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
288 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,4 +4,5 @@ __pycache__/ | |
build/ | ||
dist/ | ||
*.egg-info/ | ||
test* | ||
test* | ||
.venv |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import datetime | ||
from typing import Optional | ||
|
||
|
||
class AnnouncementFile: | ||
def __init__(self, name: str, content: bytes): | ||
self.name = name | ||
self.content = content | ||
|
||
|
||
class Announcement: | ||
def __init__( | ||
self, | ||
title: str, | ||
date: datetime.date, | ||
content: str, | ||
img: str = None, | ||
files: Optional[list[AnnouncementFile]] = None, | ||
): | ||
self.title = title | ||
self.date = date | ||
self.content = content | ||
self.img = img | ||
self.files = files | ||
|
||
def __str__(self): | ||
return f"{self.__dict__}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
class SeatingInformation: | ||
def __init__( | ||
self, | ||
name: str, | ||
course_code: str, | ||
date: str, | ||
time: str, | ||
terminal: str, | ||
block: str, | ||
): | ||
self.name = name | ||
self.course_code = course_code | ||
self.date = date | ||
self.time = time | ||
self.terminal = terminal | ||
self.block = block | ||
|
||
def __str__(self): | ||
return f"{self.__dict__}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
from .announcements import AnnouncementPageHandler | ||
from .attendance import AttendancePageHandler | ||
from .courses import CoursesPageHandler | ||
from .profile import ProfilePageHandler | ||
from .faculty import FacultyPageHandler | ||
from .seating_information import SeatingInformationHandler |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import datetime | ||
import re | ||
from typing import Optional | ||
|
||
import urllib3 | ||
|
||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) | ||
import requests_html | ||
from bs4 import BeautifulSoup | ||
|
||
from pesuacademy.models.announcement import Announcement, AnnouncementFile | ||
|
||
|
||
class AnnouncementPageHandler: | ||
@staticmethod | ||
def get_announcement_by_id( | ||
session: requests_html.HTMLSession, csrf_token: str, announcement_id: str | ||
) -> Announcement: | ||
url = "https://www.pesuacademy.com/Academy/s/studentProfilePESUAdmin" | ||
data = { | ||
"controllerMode": "6411", | ||
"actionType": "4", | ||
"AnnouncementId": announcement_id, | ||
"menuId": "667", | ||
} | ||
headers = { | ||
"accept": "*/*", | ||
"accept-language": "en-IN,en-US;q=0.9,en-GB;q=0.8,en;q=0.7", | ||
"content-type": "application/x-www-form-urlencoded", | ||
"origin": "https://www.pesuacademy.com", | ||
"priority": "u=1, i", | ||
"referer": "https://www.pesuacademy.com/Academy/s/studentProfilePESU", | ||
"sec-ch-ua": '"Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"', | ||
"sec-ch-ua-mobile": "?0", | ||
"sec-ch-ua-platform": '"Windows"', | ||
"sec-fetch-dest": "empty", | ||
"sec-fetch-mode": "cors", | ||
"sec-fetch-site": "same-origin", | ||
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", | ||
"x-csrf-token": csrf_token, | ||
"x-requested-with": "XMLHttpRequest", | ||
} | ||
|
||
response = session.post(url, data=data, headers=headers) | ||
if response.status_code != 200: | ||
data["actionType"] = "6" | ||
response = session.post(url, data=data, headers=headers) | ||
|
||
soup = BeautifulSoup(response.text, "lxml") | ||
|
||
title = soup.find("h4", class_="text-info").text.strip() | ||
date = soup.find("span", class_="text-muted text-date pull-right").text.strip() | ||
date = datetime.datetime.strptime(date, "%d-%B-%Y").date() | ||
|
||
content_tag = soup.find("div", class_="col-md-12") | ||
if content_tag is None: | ||
content_tag = soup.find("div", class_="col-md-8") | ||
paragraph_or_list_tags = content_tag.find_all(["p", "li"]) | ||
content = "\n".join([tag.text.strip() for tag in paragraph_or_list_tags]) | ||
|
||
img_tag = soup.find("img", class_="img-responsive") | ||
img = img_tag.attrs["src"] if img_tag else None | ||
|
||
attachment_tags = [ | ||
tag | ||
for tag in content_tag.find_all("a") | ||
if tag.text.strip().endswith(".pdf") | ||
] | ||
attachments = list() | ||
for attachment_tag in attachment_tags: | ||
attachment_name = attachment_tag.text.strip() | ||
pattern = re.compile(r"handleDownloadAnoncemntdoc\('(\d+)'\)") | ||
attachment_id = re.findall(pattern, attachment_tag.attrs["href"])[0] | ||
response = session.get( | ||
f"https://pesuacademy.com/Academy/s/studentProfilePESUAdmin/downloadAnoncemntdoc/{attachment_id}", | ||
headers={"x-csrf-token": csrf_token}, | ||
verify=False, | ||
) | ||
attachment_bytes = response.content | ||
attachments.append( | ||
AnnouncementFile(name=attachment_name, content=attachment_bytes) | ||
) | ||
|
||
return Announcement( | ||
title=title, date=date, content=content, img=img, files=attachments | ||
) | ||
|
||
def get_page( | ||
self, | ||
session: requests_html.HTMLSession, | ||
csrf_token: str, | ||
start_date: Optional[datetime.date] = None, | ||
end_date: Optional[datetime.date] = None, | ||
) -> list[Announcement]: | ||
url = "https://www.pesuacademy.com/Academy/s/studentProfilePESUAdmin" | ||
query = { | ||
"menuId": "667", | ||
"controllerMode": "6411", | ||
"actionType": "5", | ||
"_": str(int(datetime.datetime.now().timestamp() * 1000)), | ||
} | ||
response = session.get(url, allow_redirects=False, params=query) | ||
if response.status_code != 200: | ||
raise ConnectionError("Unable to fetch announcement data.") | ||
soup = BeautifulSoup(response.text, "lxml") | ||
|
||
announcement_ids = soup.find_all("a", class_="pull-right readmorelink") | ||
pattern = re.compile(r"handleShowMoreAnnouncement\(\d+, \d+,(\d+)\)") | ||
announcement_ids = [ | ||
pattern.match(ann.attrs.get("onclick")).group(1) for ann in announcement_ids | ||
] | ||
|
||
announcements = list() | ||
for announcement_id in announcement_ids: | ||
announcement = self.get_announcement_by_id( | ||
session, csrf_token, announcement_id | ||
) | ||
if start_date and announcement.date < start_date: | ||
continue | ||
if end_date and announcement.date > end_date: | ||
continue | ||
announcements.append(announcement) | ||
announcements.sort(key=lambda x: x.date, reverse=True) | ||
return announcements |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import datetime | ||
|
||
import requests_html | ||
from bs4 import BeautifulSoup | ||
|
||
from pesuacademy.models import SeatingInformation | ||
|
||
|
||
class SeatingInformationHandler: | ||
@staticmethod | ||
def get_seating_information_from_page( | ||
soup: BeautifulSoup, | ||
) -> list[SeatingInformation]: | ||
info_table = soup.find("table", attrs={"id": "seatinginfo"}) | ||
tablebody = info_table.find("tbody") | ||
tablerows = tablebody.find_all("tr") | ||
seating_info = list() | ||
for row in tablerows: | ||
columns = row.find_all("td") | ||
assn_name = columns[0].text.strip() | ||
course_code = columns[1].text.strip() | ||
date = columns[2].text.strip() | ||
time = columns[3].text.strip() | ||
terminal = columns[4].text.strip() | ||
block = columns[5].text.strip() | ||
seating_info.append( | ||
SeatingInformation(assn_name, course_code, date, time, terminal, block) | ||
) | ||
return seating_info | ||
|
||
@staticmethod | ||
def get_page(session: requests_html.HTMLSession) -> list[SeatingInformation]: | ||
try: | ||
profile_url = ( | ||
"https://www.pesuacademy.com/Academy/s/studentProfilePESUAdmin" | ||
) | ||
query = { | ||
"menuId": "655", | ||
"url": "studentProfilePESUAdmin", | ||
"controllerMode": "6404", | ||
"actionType": "5", | ||
"id": "0", | ||
"selectedData": "0", | ||
"_": str(int(datetime.datetime.now().timestamp() * 1000)), | ||
} | ||
response = session.get(profile_url, allow_redirects=False, params=query) | ||
if response.status_code != 200: | ||
raise ConnectionError("Unable to fetch seating info.") | ||
soup = BeautifulSoup(response.text, "lxml") | ||
if ( | ||
(no_seating_tag := soup.find("h5")) is not None | ||
and no_seating_tag.text == "No Test Seating Info is available" | ||
): | ||
return [] | ||
else: | ||
return SeatingInformationHandler.get_seating_information_from_page(soup) | ||
except Exception: | ||
raise ConnectionError("Unable to fetch seating info.") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters