sanjay7178 commited on
Commit
5161c7a
·
verified ·
1 Parent(s): 830b9fc

Upload 31 files

Browse files
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ tests/
2
+ constants/priv_constants.py
3
+ __pycache__/
4
+ venv/
5
+ test.py
6
+ .vscode
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+
3
+ RUN useradd -m -u 1000 user
4
+ USER user
5
+ ENV PATH="/home/user/.local/bin:$PATH"
6
+
7
+ WORKDIR /app
8
+
9
+ COPY --chown=user ./requirements.txt requirements.txt
10
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
11
+
12
+ COPY --chown=user . /app
13
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uvicorn
2
+ from aiohttp import ClientSession
3
+ from gen_session import gen_session
4
+ from get_marks import get_marks_data
5
+ from get_grades import get_grades_data
6
+ from get_profile import get_profile_data
7
+ from fastapi.responses import JSONResponse
8
+ from get_timetable import get_timetable_data
9
+ from get_attendance import get_attendance_data
10
+ from get_exam_schedule import get_examSchedule_data
11
+ from get_sem_id import _get_all_sem_ids, _get_sem_id
12
+ from fastapi import FastAPI, Request, HTTPException, status, Form
13
+
14
+ app = FastAPI()
15
+
16
+
17
+ def basic_creds_check(username: str | None, password: str | None):
18
+ if not username or not password:
19
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
20
+
21
+
22
+ @app.get("/")
23
+ async def root():
24
+ return {"message": "VTOP-AP API"}
25
+
26
+
27
+ async def handle_request(data_func, num_parameters, username, password):
28
+ basic_creds_check(username, password)
29
+ async with ClientSession() as sess:
30
+ session_result = await gen_session(sess, username, password)
31
+ if session_result == 0:
32
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
33
+ if num_parameters == 3:
34
+ return await data_func(sess, username, session_result)
35
+ else:
36
+ return await data_func(
37
+ sess,
38
+ username,
39
+ await _get_sem_id(sess, username, session_result),
40
+ session_result,
41
+ )
42
+
43
+
44
+ @app.post("/api/attendance")
45
+ @app.post("/api/timetable")
46
+ @app.post("/api/examSchedule")
47
+ async def handle_4param_data_functions(
48
+ request: Request, username: str = Form(...), password: str = Form(...)
49
+ ):
50
+ data_func = {
51
+ "/api/attendance": get_attendance_data,
52
+ "/api/timetable": get_timetable_data,
53
+ "/api/examSchedule": get_examSchedule_data,
54
+ }
55
+ return await handle_request(data_func[request.url.path], 4, username, password)
56
+
57
+
58
+ @app.post("/api/grades")
59
+ @app.post("/api/profile")
60
+ @app.post("/api/semIDs")
61
+ async def handle_3param_data_functions(
62
+ request: Request, username: str = Form(...), password: str = Form(...)
63
+ ):
64
+ data_func = {
65
+ "/api/grades": get_grades_data,
66
+ "/api/profile": get_profile_data,
67
+ "/api/semIDs": _get_all_sem_ids,
68
+ }
69
+ return await handle_request(data_func[request.url.path], 3, username, password)
70
+
71
+
72
+ @app.post("/api/verify")
73
+ async def verify_creds(username: str = Form(...), password: str = Form(...)):
74
+ basic_creds_check(username, password)
75
+
76
+ async with ClientSession() as sess:
77
+ session_result = await gen_session(sess, username, password)
78
+ if session_result == 0:
79
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
80
+ else:
81
+ return JSONResponse(
82
+ content={"csrf_token": session_result}, status_code=status.HTTP_200_OK
83
+ )
84
+
85
+
86
+ @app.post("/api/all")
87
+ async def all_data(username: str = Form(...), password: str = Form(...)):
88
+ basic_creds_check(username, password)
89
+
90
+ async with ClientSession() as sess:
91
+ session_result = await gen_session(sess, username, password)
92
+ if session_result == 0:
93
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
94
+ semID = await _get_sem_id(sess, username, session_result)
95
+ data = {
96
+ "profile": await get_profile_data(sess, username, session_result),
97
+ "attendance": await get_attendance_data(
98
+ sess, username, semID, session_result
99
+ ),
100
+ "semIDs": await _get_all_sem_ids(sess, username, session_result),
101
+ "grades": await get_grades_data(sess, username, session_result),
102
+ "examSchedule": await get_examSchedule_data(
103
+ sess, username, semID, session_result
104
+ ),
105
+ "timetable": await get_timetable_data(
106
+ sess, username, semID, session_result
107
+ ),
108
+ }
109
+
110
+ return data
111
+
112
+
113
+ @app.post("/api/marks")
114
+ async def marks(
115
+ username: str = Form(...), password: str = Form(...), semID: str = Form(...)
116
+ ):
117
+ basic_creds_check(username, password)
118
+
119
+ async with ClientSession() as sess:
120
+ session_result = await gen_session(sess, username, password)
121
+ if session_result == 0:
122
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
123
+
124
+ return await get_marks_data(sess, username, semID, session_result)
125
+
126
+
127
+ if __name__ == "__main__":
128
+ uvicorn.run(app, host="localhost", port=7860)
constants/__pycache__/bitmaps.cpython-310.pyc ADDED
Binary file (98.3 kB). View file
 
constants/__pycache__/bitmaps.cpython-311.pyc ADDED
Binary file (117 kB). View file
 
constants/__pycache__/constants.cpython-310.pyc ADDED
Binary file (951 Bytes). View file
 
constants/__pycache__/constants.cpython-311.pyc ADDED
Binary file (1.08 kB). View file
 
constants/bitmaps.py ADDED
The diff for this file is too large to render. See raw diff
 
constants/constants.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ user_agent_header = {
2
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.67"
3
+ }
4
+
5
+ vtop_base_url = "https://vtop.vitap.ac.in/vtop/"
6
+
7
+ vtop_profile_url = f"{vtop_base_url}studentsRecord/StudentProfileAllView"
8
+ vtop_process_timetable_url = f"{vtop_base_url}processViewTimeTable"
9
+ vtop_semID_list_url = f"{vtop_base_url}academics/common/StudentAttendance"
10
+ vtop_process_attendance_url = f"{vtop_base_url}processViewStudentAttendance"
11
+ vtop_process_attendance_detail_url = f"{vtop_base_url}processViewAttendanceDetail"
12
+ vtop_doMarks_view_url = f"{vtop_base_url}examinations/doStudentMarkView"
13
+ vtop_gradeHistory_url = f"{vtop_base_url}examinations/examGradeView/StudentGradeHistory"
14
+ vtop_doExamSchedule_url = f"{vtop_base_url}examinations/doSearchExamScheduleForStudent"
constants/weights.json ADDED
The diff for this file is too large to render. See raw diff
 
gen_session.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import aiohttp
2
+ from constants.constants import user_agent_header
3
+ from utils.captcha_solver import solve_base64
4
+ from utils.payloads import get_login_payload
5
+ import re
6
+
7
+
8
+ async def gen_session(sess: aiohttp.ClientSession, username: str, password: str):
9
+ try:
10
+ async with sess.get(
11
+ "https://vtop.vitap.ac.in/vtop/", headers=user_agent_header
12
+ ):
13
+ async with sess.get("https://vtop.vitap.ac.in/vtop/open/page") as csrf:
14
+ csrf_token = re.search(
15
+ r'name="_csrf" value="(.*)"', await csrf.text()
16
+ ).group(1)
17
+ await sess.post(
18
+ "https://vtop.vitap.ac.in/vtop/prelogin/setup",
19
+ data={"_csrf": csrf_token, "flag": "VTOP"},
20
+ )
21
+ await sess.get("https://vtop.vitap.ac.in/vtop/init/page")
22
+ async with sess.get("https://vtop.vitap.ac.in/vtop/login") as req:
23
+ captcha = solve_base64(
24
+ re.search(r';base64,(.+)"', await req.text()).group(1)
25
+ )
26
+ async with sess.post(
27
+ "https://vtop.vitap.ac.in/vtop/login",
28
+ data=get_login_payload(
29
+ csrf_token,
30
+ username,
31
+ password,
32
+ captcha,
33
+ ),
34
+ ) as final:
35
+ csrf = re.search(
36
+ r'var csrfValue = "(.*)";', await final.text()
37
+ ).group(1)
38
+ if "Invalid" in await final.text():
39
+ return 0
40
+ elif len(csrf) == 36:
41
+ return csrf
42
+ else:
43
+ return await gen_session(sess, username, password)
44
+
45
+ except Exception:
46
+ return await gen_session(sess, username, password)
get_attendance.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import aiohttp
3
+ import pandas as pd
4
+ from io import StringIO
5
+
6
+ from constants.constants import (
7
+ vtop_process_attendance_url,
8
+ vtop_process_attendance_detail_url,
9
+ )
10
+ from utils.payloads import (
11
+ get_attendance_payload,
12
+ get_attendance_detail_payload,
13
+ )
14
+
15
+
16
+ async def _get_attendance_page(
17
+ sess: aiohttp.ClientSession, username: str, semID: str, csrf: str
18
+ ):
19
+ async with sess.post(
20
+ vtop_process_attendance_url, data=get_attendance_payload(username, semID, csrf)
21
+ ) as req:
22
+ return await req.text()
23
+
24
+
25
+ async def _get_attendance_detail_page(
26
+ sess: aiohttp.ClientSession, csrf, semID, username, courseID, courseType
27
+ ):
28
+ async with sess.post(
29
+ vtop_process_attendance_detail_url,
30
+ data=get_attendance_detail_payload(csrf, semID, username, courseID, courseType),
31
+ ) as req:
32
+ return await req.text()
33
+
34
+
35
+ def _get_class_type(classType: str):
36
+ if classType == "Embedded Theory":
37
+ return "ETH"
38
+ elif classType == "Embedded Lab":
39
+ return "ELA"
40
+ elif classType == "Theory Only":
41
+ return "TH"
42
+ elif classType == "Lab Only":
43
+ return "LO"
44
+
45
+
46
+ def _parse_attendance_detail(attendance_detail_page: str):
47
+ attendance_detail_table = pd.read_html(StringIO(attendance_detail_page))
48
+ if len(attendance_detail_table) < 2:
49
+ return {}
50
+ attendance_detail_table = attendance_detail_table[1]
51
+ attendance_detail = {}
52
+
53
+ for index, row in attendance_detail_table.iterrows():
54
+ attendance_detail[str(row["Sl.No."])] = {
55
+ "status": row["Status"],
56
+ "date": row["Date"],
57
+ "time": row["Day / Time"],
58
+ }
59
+
60
+ return attendance_detail
61
+
62
+
63
+ async def _parse_attendance(
64
+ attendance_page: str,
65
+ sess: aiohttp.ClientSession,
66
+ username: str,
67
+ csrf: str,
68
+ semID: str,
69
+ ):
70
+ table_df = pd.read_html(StringIO(attendance_page))[0]
71
+
72
+ attendance = []
73
+
74
+ for index, row in table_df.iterrows():
75
+ code = row["Course Detail"].split("-")[0].strip()
76
+ slot = row["Class Detail"].split("-")[1].strip()
77
+ if "Total Number Of Credits" in code:
78
+ if "0" in code:
79
+ raise Exception
80
+ continue
81
+
82
+ attendance.append(
83
+ {
84
+ "classID": row["Class Detail"].split("-")[0].strip(),
85
+ "name": row["Course Detail"].split("-")[1].strip(),
86
+ "courseType": row["Course Detail"].split("-")[2].strip(),
87
+ "slot": slot,
88
+ "totalClasses": str(row["Total Classes"]),
89
+ "attendedClasses": str(row["Attended Classes"]),
90
+ "attendancePercentage": row["Attendance Percentage"][:-1],
91
+ "attendanceDetail": _parse_attendance_detail(
92
+ await _get_attendance_detail_page(
93
+ sess,
94
+ csrf,
95
+ semID,
96
+ username,
97
+ re.search(f";(\w*_{code}_\d*)&", attendance_page).group(1),
98
+ _get_class_type(row["Course Detail"].split("-")[2].strip()),
99
+ )
100
+ ),
101
+ }
102
+ )
103
+
104
+ return attendance
105
+
106
+
107
+ async def get_attendance_data(
108
+ sess: aiohttp.ClientSession, username: str, semID: str, csrf: str
109
+ ):
110
+ return await _parse_attendance(
111
+ await _get_attendance_page(sess, username, semID, csrf),
112
+ sess,
113
+ username,
114
+ csrf,
115
+ semID,
116
+ )
get_exam_schedule.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import aiohttp
3
+ import pandas as pd
4
+ from io import StringIO
5
+
6
+ from utils.payloads import get_examSchedule_payload
7
+ from constants.constants import vtop_doExamSchedule_url
8
+
9
+
10
+ async def _get_examSchedule_page(
11
+ sess: aiohttp.ClientSession, username: str, semID: str, csrf: str
12
+ ) -> str:
13
+ async with sess.post(
14
+ vtop_doExamSchedule_url, data=get_examSchedule_payload(username, semID, csrf)
15
+ ) as req:
16
+ return await req.text()
17
+
18
+
19
+ def _return_dash_if_not_str(value):
20
+ if isinstance(value, str):
21
+ return value
22
+ else:
23
+ return "-"
24
+
25
+
26
+ async def _parse_examSchedule(examSchedule_page: str):
27
+ try:
28
+ examSchedule_table = pd.read_html(StringIO(examSchedule_page))[0]
29
+ except ValueError:
30
+ return {}
31
+ examSchedule_data = {}
32
+
33
+ current_exam = ""
34
+ for index, row in examSchedule_table.iterrows():
35
+ if index == 0:
36
+ continue
37
+ if re.search("\D", row[0]):
38
+ current_exam = row[0]
39
+ examSchedule_data[current_exam] = {}
40
+ else:
41
+ examSchedule_data[current_exam][row[1]] = {
42
+ "name": row[2],
43
+ "type": row[3],
44
+ "classID": row[4],
45
+ "slot": row[5],
46
+ "date": _return_dash_if_not_str(row[6]),
47
+ "session": _return_dash_if_not_str(row[7]),
48
+ "reportingTime": _return_dash_if_not_str(row[8]),
49
+ "duration": _return_dash_if_not_str(row[9]),
50
+ "venue": row[10].split("-")[0],
51
+ "roomNo": row[10].split("-")[1],
52
+ "seatLocation": row[11],
53
+ "seatNo": row[12],
54
+ }
55
+
56
+ examSchedule_data.pop("S.No.")
57
+ return examSchedule_data
58
+
59
+
60
+ async def get_examSchedule_data(
61
+ sess: aiohttp.ClientSession, username: str, semID: str, csrf: str
62
+ ) -> dict:
63
+ examSchedule_page = await _get_examSchedule_page(sess, username, semID, csrf)
64
+ examSchedule_data = await _parse_examSchedule(examSchedule_page)
65
+ return examSchedule_data
get_grades.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import aiohttp
2
+ import pandas as pd
3
+ from io import StringIO
4
+ from constants.constants import vtop_gradeHistory_url
5
+ from utils.payloads import get_gradeHistory_payload
6
+
7
+
8
+ async def _get_grades_page(
9
+ sess: aiohttp.ClientSession, username: str, csrf: str
10
+ ) -> str:
11
+ async with sess.post(
12
+ vtop_gradeHistory_url, data=get_gradeHistory_payload(username, csrf)
13
+ ) as req:
14
+ return await req.text()
15
+
16
+
17
+ async def get_grades_data(sess: aiohttp.ClientSession, username: str, csrf: str):
18
+ grades_page = await _get_grades_page(sess, username, csrf)
19
+
20
+ try:
21
+ tables = pd.read_html(StringIO(grades_page))
22
+ data_table = tables[1]
23
+ data_summary_table = tables[-1]
24
+ except Exception:
25
+ return {}
26
+
27
+ grade_data = {}
28
+
29
+ grade_data["creditsEarned"] = str(data_summary_table.iloc[0, 1])
30
+ grade_data["cgpa"] = str(data_summary_table.iloc[0, 2])
31
+
32
+ grade_data["numOfEachGrade"] = {}
33
+ grade_data["subjects"] = {}
34
+ grade_data["numOfEachGrade"]["S"] = str(data_summary_table.iloc[0, 3])
35
+ for i in range(0, 6):
36
+ grade_data["numOfEachGrade"][chr(65 + i)] = str(
37
+ data_summary_table.iloc[0, 4 + i]
38
+ )
39
+
40
+ for i in range(2, len(data_table)):
41
+ grade_data["subjects"][data_table.iloc[i, 1]] = {
42
+ "name": data_table.iloc[i, 2],
43
+ "type": data_table.iloc[i, 3],
44
+ "credits": data_table.iloc[i, 4],
45
+ "grade": data_table.iloc[i, 5],
46
+ }
47
+
48
+ return grade_data
get_marks.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import aiohttp
2
+ import pandas as pd
3
+ from io import StringIO
4
+
5
+ from constants.constants import vtop_doMarks_view_url
6
+ from utils.payloads import get_doMarks_view_payload
7
+
8
+
9
+ async def _get_doMarks_view_page(
10
+ sess: aiohttp.ClientSession, username: str, semID: str, csrf: str
11
+ ) -> str:
12
+ async with sess.post(
13
+ vtop_doMarks_view_url, data=get_doMarks_view_payload(username, semID, csrf)
14
+ ) as req:
15
+ return await req.text()
16
+
17
+
18
+ def _parse_marks(marks_page):
19
+ try:
20
+ tables = pd.read_html(StringIO(marks_page))
21
+ except ValueError:
22
+ return {}
23
+
24
+ course_details = tables[0].iloc[1::2, :]
25
+ marks_data = {}
26
+
27
+ for i in range(course_details.shape[0]):
28
+ course = course_details.iloc[i]
29
+ marks_data[course[1]] = {
30
+ "courseName": course[3],
31
+ "courseType": course[4],
32
+ "professor": course[6],
33
+ "courseSlot": course[7],
34
+ "marks": {},
35
+ }
36
+
37
+ current_course_table = tables[i + 1]
38
+ for j in range(1, current_course_table.shape[0]):
39
+ entry = current_course_table.iloc[j]
40
+ marks_data[course[1]]["marks"][entry[1]] = {
41
+ "maxMarks": entry[2],
42
+ "maxWeightageMarks": entry[3],
43
+ "scoredMarks": entry[5],
44
+ "scoredWeightageMarks": entry[6],
45
+ }
46
+
47
+ return marks_data
48
+
49
+
50
+ async def get_marks_data(
51
+ sess: aiohttp.ClientSession, username: str, semID: str, csrf: str
52
+ ):
53
+ return _parse_marks(await _get_doMarks_view_page(sess, username, semID, csrf))
get_profile.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import string
3
+ import aiohttp
4
+ import pandas as pd
5
+ from io import StringIO
6
+
7
+ from constants.constants import vtop_profile_url
8
+ from utils.payloads import get_profile_payload
9
+
10
+
11
+ def _get_value_from_column1(text: str, df: pd.DataFrame):
12
+ row = df[df[0] == text]
13
+ if not row.empty:
14
+ return str(row.iloc[0, 1])
15
+ else:
16
+ return str(None)
17
+
18
+
19
+ async def _get_profile_page(
20
+ sess: aiohttp.ClientSession, username: str, csrf: str
21
+ ) -> str:
22
+ async with sess.post(
23
+ vtop_profile_url, data=get_profile_payload(username, csrf)
24
+ ) as req:
25
+ return await req.text()
26
+
27
+
28
+ async def get_profile_data(
29
+ sess: aiohttp.ClientSession, username: str, csrf: str
30
+ ) -> dict:
31
+ profile_page = await _get_profile_page(sess, username, csrf)
32
+ data = {}
33
+ tables = pd.read_html(StringIO(profile_page))
34
+
35
+ desired_fields_table_0 = {
36
+ "Student Name": "STUDENT NAME",
37
+ "Application Number": "APPLICATION NUMBER",
38
+ }
39
+
40
+ desired_fields_table_3 = {
41
+ "Mentor Name": "FACULTY NAME",
42
+ "Mentor Cabin": "CABIN",
43
+ "Mentor Email": "FACULTY EMAIL",
44
+ "Mentor intercom": "FACULTY INTERCOM",
45
+ "Mentor Mobile Number": "FACULTY MOBILE NUMBER",
46
+ }
47
+
48
+ data["image"] = re.findall(r'src="data:null;base64,(.*)"', profile_page)[0]
49
+ data["VIT Registration Number"] = username
50
+ for key, field in desired_fields_table_0.items():
51
+ data[key] = string.capwords(_get_value_from_column1(field, tables[0]))
52
+ for key, field in desired_fields_table_3.items():
53
+ data[key] = _get_value_from_column1(field, tables[3])
54
+
55
+ return data
get_sem_id.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import aiohttp
3
+ from bs4 import BeautifulSoup
4
+ from constants.constants import vtop_semID_list_url
5
+ from utils.payloads import get_attendance_semID_list_payload
6
+
7
+
8
+ async def _get_sem_id(sess: aiohttp.ClientSession, username: str, csrf: str):
9
+ async with sess.post(
10
+ vtop_semID_list_url,
11
+ data=get_attendance_semID_list_payload(username, csrf),
12
+ ) as req:
13
+ return re.search('<option value="(A.*)"', await req.text()).group(1)
14
+
15
+
16
+ async def _get_all_sem_ids(sess: aiohttp.ClientSession, username: str, csrf: str):
17
+ async with sess.post(
18
+ vtop_semID_list_url,
19
+ data=get_attendance_semID_list_payload(username, csrf),
20
+ ) as req:
21
+ # sem as list
22
+ # return re.findall('<option value="(A.*)"', await req.text())
23
+
24
+ soup = BeautifulSoup(await req.text(), "lxml")
25
+
26
+ sem_ids = {}
27
+ sem_ids_soup = soup.findAll("option")
28
+ for elem in sem_ids_soup:
29
+ id = elem["value"]
30
+ if id != "":
31
+ sem_ids[id] = elem.text.strip()
32
+ return sem_ids
get_timetable.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import aiohttp
2
+ import pandas as pd
3
+ from io import StringIO
4
+ from bs4 import BeautifulSoup
5
+
6
+ from constants.constants import vtop_process_timetable_url
7
+ from models.period import Period
8
+ from utils.payloads import get_timetable_payload
9
+
10
+
11
+ DAYS_MAP = {
12
+ "MON": "Monday",
13
+ "TUE": "Tuesday",
14
+ "WED": "Wednesday",
15
+ "THU": "Thursday",
16
+ "FRI": "Friday",
17
+ "SAT": "Saturday",
18
+ "SUN": "Sunday",
19
+ }
20
+
21
+ VALID_DAYS = {"Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}
22
+
23
+
24
+ async def _get_timetable_page(
25
+ sess: aiohttp.ClientSession, username: str, semID: str, csrf: str
26
+ ) -> str:
27
+ async with sess.post(
28
+ vtop_process_timetable_url, data=get_timetable_payload(username, semID, csrf)
29
+ ) as req:
30
+ return await req.text()
31
+
32
+
33
+ def _get_course_code_name_dict(soup: BeautifulSoup) -> dict:
34
+ course_data = soup.find("div", attrs={"id": "studentDetailsList"}).find_all(
35
+ "td",
36
+ attrs={
37
+ "style": "padding: 3px; font-size: 12px; border-color: #b2b2b2;vertical-align: middle;"
38
+ },
39
+ )
40
+ return {
41
+ data.text.split("-")[0].strip(): data.text.split("-")[1].split("\n")[0].strip()
42
+ for data in course_data
43
+ }
44
+
45
+
46
+ def _parse_course_vals(cell_str: str):
47
+ temp_arr = str(cell_str).strip().split("-")
48
+ course_code = temp_arr[1]
49
+ cls = "-".join(temp_arr[3 : len(temp_arr) - 1])
50
+
51
+ return course_code, cls
52
+
53
+
54
+ def _get_end_time(start_time: str, is_theory: bool = True):
55
+ if is_theory:
56
+ return f'{start_time.split(":")[0]}:50'
57
+ else:
58
+ return f'{int(start_time.split(":")[0]) + 1}:40'
59
+
60
+
61
+ def _parse_timetable(timetable_page: str):
62
+ timetable = {day: [] for day in VALID_DAYS}
63
+ soup = BeautifulSoup(timetable_page, "lxml")
64
+ course_code_dict = _get_course_code_name_dict(soup)
65
+ dataframes = pd.read_html(StringIO(timetable_page))
66
+ course_details, timetable_df = dataframes[0], dataframes[1]
67
+
68
+ for row in timetable_df.itertuples(index=False):
69
+ if len(row) < 2 or row[1].lower() not in {"theory", "lab"}:
70
+ continue
71
+ day = DAYS_MAP.get(row[0], "Sunday")
72
+ is_theory = row[1].lower() == "theory"
73
+ if day not in timetable:
74
+ continue
75
+ for col_idx, cell in enumerate(row[2:], start=2):
76
+ cell_str = str(cell).strip()
77
+ if len(cell_str) > 3 and cell_str.count("-") >= 3:
78
+ code, location = _parse_course_vals(cell_str)
79
+ class_id = course_details.loc[
80
+ course_details["Slot - Venue"].str.contains(
81
+ cell_str.split("-")[0], na=False
82
+ ),
83
+ "Class Nbr",
84
+ ].iloc[0]
85
+ start_time = timetable_df.iloc[0, col_idx]
86
+ period = Period(
87
+ class_id=class_id,
88
+ slot=course_details.loc[
89
+ course_details["Class Nbr"] == class_id, "Slot - Venue"
90
+ ]
91
+ .iloc[0]
92
+ .split(" - ")[0],
93
+ courseName=course_code_dict[code],
94
+ code=code,
95
+ location=location,
96
+ startTime=start_time,
97
+ endTime=_get_end_time(start_time, is_theory),
98
+ )
99
+ if period not in timetable[day]:
100
+ timetable[day].append(period)
101
+ return timetable
102
+
103
+
104
+ async def get_timetable_data(
105
+ sess: aiohttp.ClientSession, username: str, semID: str, csrf: str
106
+ ):
107
+ timetable = _parse_timetable(await _get_timetable_page(sess, username, semID, csrf))
108
+ for key in timetable:
109
+ timetable[key].sort()
110
+ return {
111
+ key: [period.to_dict() for period in period_list]
112
+ for key, period_list in timetable.items()
113
+ }
models/__pycache__/period.cpython-310.pyc ADDED
Binary file (1.23 kB). View file
 
models/__pycache__/period.cpython-311.pyc ADDED
Binary file (1.84 kB). View file
 
models/period.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Period:
2
+ def __init__(self, class_id, slot, courseName, code, location, startTime, endTime):
3
+ self.class_id = class_id
4
+ self.slot = slot
5
+ self.courseName = courseName
6
+ self.code = code
7
+ self.location = location
8
+ self.startTime = startTime
9
+ self.endTime = endTime
10
+
11
+ def __eq__(self, other):
12
+ return (
13
+ self.slot == other.slot or self.slot[0] == other.slot[0] == "L"
14
+ ) and self.code == other.code
15
+
16
+ def __lt__(self, other):
17
+ return self.startTime < other.startTime
18
+
19
+ def to_dict(self):
20
+ return {
21
+ "classId": self.class_id,
22
+ "slot": self.slot,
23
+ "courseName": self.courseName,
24
+ "code": self.code,
25
+ "location": self.location,
26
+ "startTime": self.startTime,
27
+ "endTime": self.endTime,
28
+ }
29
+
30
+ def __repr__(self) -> str:
31
+ return str(self.to_dict())
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ aiohttp==3.10.5
2
+ beautifulsoup4==4.12.3
3
+ fastapi==0.112.2
4
+ numpy==2.1.0
5
+ pandas==2.2.2
6
+ Pillow==10.4.0
7
+ uvicorn==0.30.6
8
+ python-multipart
source/conf.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Configuration file for the Sphinx documentation builder.
2
+ #
3
+ # For the full list of built-in configuration values, see the documentation:
4
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html
5
+
6
+ # -- Project information -----------------------------------------------------
7
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
8
+
9
+ project = 'sanjay7178'
10
+ copyright = '2024, sanjay'
11
+ author = 'sanjay'
12
+ release = 'v1'
13
+
14
+ # -- General configuration ---------------------------------------------------
15
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
16
+
17
+ extensions = []
18
+
19
+ templates_path = ['_templates']
20
+ exclude_patterns = []
21
+
22
+
23
+
24
+ # -- Options for HTML output -------------------------------------------------
25
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
26
+
27
+ html_theme = 'alabaster'
28
+ html_static_path = ['_static']
source/index.rst ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .. sanjay7178 documentation master file, created by
2
+ sphinx-quickstart on Tue Aug 27 20:15:42 2024.
3
+ You can adapt this file completely to your liking, but it should at least
4
+ contain the root `toctree` directive.
5
+
6
+ sanjay7178 documentation
7
+ ========================
8
+
9
+ Add your content using ``reStructuredText`` syntax. See the
10
+ `reStructuredText <https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html>`_
11
+ documentation for details.
12
+
13
+
14
+ .. toctree::
15
+ :maxdepth: 2
16
+ :caption: Contents:
17
+
utils/__pycache__/captcha_solver.cpython-310.pyc ADDED
Binary file (3.77 kB). View file
 
utils/__pycache__/captcha_solver.cpython-311.pyc ADDED
Binary file (6.77 kB). View file
 
utils/__pycache__/payloads.cpython-310.pyc ADDED
Binary file (2.21 kB). View file
 
utils/__pycache__/payloads.cpython-311.pyc ADDED
Binary file (3.17 kB). View file
 
utils/captcha_solver.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from PIL import Image
3
+ import json
4
+ from constants.bitmaps import bitmaps
5
+ import base64
6
+ from io import BytesIO
7
+
8
+ def pre_img(img):
9
+ avg = sum(sum(e) for e in img) / (24 * 22)
10
+ bits = [[1 if val > avg else 0 for val in row] for row in img]
11
+ return bits
12
+
13
+
14
+ def saturation(d):
15
+ saturate = np.round(((np.max(d, axis=1) - np.min(d, axis=1)) * 255) / np.max(d, axis=1))
16
+ img = saturate.reshape((40, 200))
17
+ bls = [img[7 + 5 * (i % 2) + 1:35 - 5 * ((i + 1) % 2), (i + 1) * 25 + 2:(i + 2) * 25 + 1] for i in range(6)]
18
+ return bls
19
+
20
+
21
+ def flatten(arr):
22
+ return [val for sublist in arr for val in sublist]
23
+
24
+
25
+ def mat_mul(a, b):
26
+ x, z, y = len(a), len(a[0]), len(b[0])
27
+ product_row = [0] * y
28
+ product = [[0] * y for _ in range(x)]
29
+
30
+ for i in range(x):
31
+ for j in range(y):
32
+ for k in range(z):
33
+ product[i][j] += a[i][k] * b[k][j]
34
+
35
+ return product
36
+
37
+
38
+ def mat_add(a, b):
39
+ return [a[i] + b[i] for i in range(len(a))]
40
+
41
+
42
+ def max_soft(a):
43
+ n = list(a)
44
+ s = sum(np.exp(f) for f in n)
45
+ n = [np.exp(f) / s for f in n]
46
+ return n
47
+
48
+
49
+ HEIGHT = 40
50
+ WIDTH = 200
51
+
52
+ def solve(img):
53
+ weights = None
54
+ biases = None
55
+ label_txt = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
56
+
57
+ with open("constants/weights.json", "r") as f:
58
+ data = json.load(f)
59
+ weights = data["weights"]
60
+ biases = data["biases"]
61
+
62
+ img_data = img.convert("RGB").getdata()
63
+ img_array = np.array(list(img_data))
64
+
65
+ bls = saturation(img_array)
66
+ out = ""
67
+
68
+ for i in range(6):
69
+ bls[i] = pre_img(bls[i])
70
+ bls[i] = [flatten(bls[i])]
71
+ bls[i] = mat_mul(bls[i], weights)
72
+ bls[i] = mat_add(*bls[i], biases)
73
+ bls[i] = max_soft(bls[i])
74
+ index = bls[i].index(max(bls[i]))
75
+ out += label_txt[index]
76
+
77
+ return out
78
+
79
+ def solve_base64(img_base64: str) -> str:
80
+ return solve(Image.open(BytesIO(base64.b64decode(img_base64))))
utils/payloads.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import datetime
2
+
3
+
4
+ def get_current_time() -> str:
5
+ return datetime.datetime.now(datetime.timezone.utc).strftime("%c GMT")
6
+
7
+
8
+ def get_login_payload(
9
+ csrf_token: str, username: str, password: str, captcha: str
10
+ ) -> dict:
11
+ return {
12
+ "_csrf": csrf_token,
13
+ "username": username,
14
+ "password": password,
15
+ "captchaStr": captcha,
16
+ }
17
+
18
+
19
+ def get_profile_payload(username: str, csrf: str) -> dict:
20
+ return {
21
+ "verifyMenu": "true",
22
+ "authorizedID": username,
23
+ "_csrf": csrf,
24
+ "nocache": "@(new Date().getTime()",
25
+ }
26
+
27
+
28
+ def get_timetable_payload(username: str, semID: str, csrf: str) -> dict:
29
+ return {
30
+ "_csrf": csrf,
31
+ "semesterSubId": semID,
32
+ "authorizedID": username,
33
+ "x": get_current_time(),
34
+ }
35
+
36
+
37
+ def get_attendance_payload(username: str, semID: str, csrf: str) -> dict:
38
+ return get_timetable_payload(username, semID, csrf)
39
+
40
+
41
+ def get_attendance_semID_list_payload(username: str, csrf: str) -> dict:
42
+ return get_profile_payload(username, csrf)
43
+
44
+
45
+ def get_attendance_detail_payload(
46
+ csrf: str, semID: str, username: str, courseID: str, courseType: str
47
+ ) -> dict:
48
+ return {
49
+ "_csrf": csrf,
50
+ "semesterSubId": semID,
51
+ "registerNumber": username,
52
+ "courseId": courseID,
53
+ "courseType": courseType,
54
+ "authorizedID": username,
55
+ "x": get_current_time(),
56
+ }
57
+
58
+
59
+ def get_doMarks_view_payload(username: str, semID: str, csrf: str) -> dict:
60
+ return {"authorizedID": username, "semesterSubId": semID, "_csrf": csrf}
61
+
62
+
63
+ def get_gradeHistory_payload(username: str, csrf: str) -> dict:
64
+ return get_profile_payload(username, csrf)
65
+
66
+
67
+ def get_examSchedule_payload(username: str, semID: str, csrf: str) -> dict:
68
+ return get_doMarks_view_payload(username, semID, csrf)
69
+
70
+
71
+ def get_goto_page_payload(username: str, csrf: str) -> dict:
72
+ return get_profile_payload(username, csrf)
utils/sem_ids.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ import json
2
+ import os
3
+
4
+ semIDs = json.load(open(os.path.join(os.path.dirname(__file__), '../constants/sem_ids.json'))).keys()