├── Procfile ├── Pipfile ├── requirements ├── LICENSE ├── README.md ├── token_srm.py ├── main.py ├── timetable.py ├── attendence_marks.py └── course_personal_details.py /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn main:app --log-file=- 2 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [requires] 2 | python_version = "2.7" 3 | [packages] 4 | whitenoise = "*" 5 | gunicorn = "*" 6 | Flask = "*" 7 | crayons = "*" 8 | maya = "*" 9 | Flask-Compress = "*" 10 | Flask-Cache = "*" 11 | meinheld = "*" 12 | requests = "*" 13 | pyquery = "*" 14 | -------------------------------------------------------------------------------- /requirements: -------------------------------------------------------------------------------- 1 | $ pip Freeze 2 | Flask==0.10.1 3 | Flask-Mail==0.9.1 4 | Flask-SQLAlchemy==2.1 5 | Flask-WTF==0.12 6 | Jinja2==2.8 7 | MarkupSafe==0.23 8 | SQLAlchemy==1.0.11 9 | WTForms==2.1 10 | Werkzeug==0.11.3 11 | blinker==1.4 12 | gunicorn==19.4.5 13 | itsdangerous==0.24 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Yogesh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SRM Academia API - Python 2 | This Repository is an unofficial API for Academia which is Online Portal developed by ZOHO for SRM University Katankulathur in order to show students their Attendance, Marks, TimeTable and other useful details. There was no official API of Academia when I wrote this piece of code but as of now WISOPT approached ZOHO and got their API. 3 | 4 | ## DEMO 5 | ``` 6 | https://academia-yogesh.herokuapp.com 7 | ``` 8 | 9 | ## Getting Access Token Of User 10 | ``` 11 | https://academia-yogesh.herokuapp.com/token?email=****@srmuniv.edu.in&pass=*** 12 | ``` 13 | Here I am using GET request but you may change the code to use POST requests. 14 | The above url would the token if the request was successful. 15 | Store the token to use it in the future. 16 | 17 | ## Getting Attendance and Marks Of User 18 | ``` 19 | https://academia-yogesh.herokuapp.com/AttAndMarks?token={{token}} 20 | ``` 21 | This would give you the Attendance and the Marks of the User. 22 | 23 | ## Getting Personal Details Of User 24 | ``` 25 | https://academia-yogesh.herokuapp.com/PersonalDetails?token={{token}} 26 | ``` 27 | This would give you the Personal Details of the User. 28 | 29 | ## Getting TimeTable Of User 30 | ``` 31 | https://academia-yogesh.herokuapp.com/TimeTable?token={{token}}&batch={{1 or 2}} 32 | ``` 33 | This would give you the TimeTable of the User based on the batch. 34 | 35 | #### The code might need to be changed if any changed have been made in the Academia website. If you find any bug please write to me about it. 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /token_srm.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | from urllib.parse import parse_qs 4 | import base64 5 | 6 | 7 | url = "https://academia.srmuniv.ac.in/accounts/signin.ac" 8 | 9 | headers = {'Origin': 'https://academia.srmuniv.ac.in', 10 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36' } 11 | 12 | 13 | 14 | 15 | def getToken(username, password): 16 | payload = {'username': username, 17 | 'password': password, 18 | 'client_portal': 'true', 19 | 'portal': '10002227248', 20 | 'servicename': 'ZohoCreator', 21 | 'serviceurl': 'https://academia.srmuniv.ac.in/', 22 | 'is_ajax': 'true', 23 | 'grant_type': 'password', 24 | 'service_language': 'en'} 25 | 26 | r = requests.post(url, data=payload, headers=headers) 27 | json_data = json.loads(r.text) 28 | 29 | if "error" in json_data: 30 | error_m = json_data['error']['msg'] 31 | json_o = {"status":"error", "msg":error_m} 32 | return json.dumps(json_o) 33 | else: 34 | params = parse_qs(json_data['data']['token_params']) 35 | params['state'] = 'https://academia.srmuniv.ac.in/' 36 | r = requests.get(json_data['data']['oauthorize_uri'], data=params, headers=headers) 37 | token = json.dumps(r.history[0].cookies.get_dict()) 38 | token = str(base64.encodestring(str.encode(token)),'utf-8') 39 | 40 | 41 | json_o = {"status":"success", "token": token} 42 | return json.dumps(json_o) 43 | 44 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask import request as rq 3 | import token_srm 4 | import attendence_marks 5 | import timetable 6 | import course_personal_details 7 | import json 8 | from flask import Response 9 | 10 | 11 | app = Flask(__name__) 12 | 13 | 14 | @app.route('/') 15 | def home(): 16 | json_o = {"status": "success", "msg": "*** ACADEMIA API WITH PYTHON *** By Yogesh Kumawat"} 17 | json_o = json.dumps(json_o) 18 | return json_o 19 | 20 | 21 | 22 | @app.route('/token', methods=['GET', 'POST']) 23 | def request(): 24 | if 'email' in rq.args and 'pass' in rq.args: 25 | response = token_srm.getToken(rq.args.get('email'), rq.args.get('pass')) 26 | response = Response(response, status=200, mimetype='application/json') 27 | return response 28 | else: 29 | response = {"status":"error", "msg":"Error in Input Parameters"} 30 | response = json.dumps(response) 31 | response = Response(response, status=200, mimetype='application/json') 32 | return response 33 | 34 | 35 | 36 | @app.route('/AttAndMarks', methods=['GET', 'POST']) 37 | def AttAndMarks(): 38 | if 'token' in rq.args: 39 | token = str(rq.args.get('token')) 40 | att_marks = attendence_marks.getAttendenceAndMarks(token) 41 | response = Response(att_marks, status=200, mimetype='application/json') 42 | return response 43 | else: 44 | response = {"status": "error", "msg": "Error in Input Parameters"} 45 | response = json.dumps(response) 46 | response = Response(response, status=200, mimetype='application/json') 47 | return response 48 | 49 | 50 | 51 | @app.route('/TimeTable', methods=['GET', 'POST']) 52 | def TimeTable(): 53 | if 'batch' in rq.args and 'token' in rq.args: 54 | batchNo = rq.args.get('batch') 55 | token = rq.args.get('token') 56 | timeTable = timetable.getTimeTable(token, batchNo) 57 | response = Response(timeTable, status=200, mimetype='application/json') 58 | return response 59 | else: 60 | response = {"status": "error", "msg": "Error in Input Parameters"} 61 | response = json.dumps(response) 62 | response = Response(response, status=200, mimetype='application/json') 63 | return response 64 | 65 | 66 | 67 | @app.route('/PersonalDetails', methods=['GET', 'POST']) 68 | def getPersonalDetails(): 69 | if 'token' in rq.args: 70 | token = rq.args.get('token') 71 | details = course_personal_details.getCoursePersonalDetails(token) 72 | response = Response(details, status=200, mimetype='application/json') 73 | return response 74 | else: 75 | response = {"status": "error", "msg": "Error in Input Parameters"} 76 | response = json.dumps(response) 77 | response = Response(response, status=200, mimetype='application/json') 78 | return response 79 | 80 | 81 | 82 | 83 | 84 | if __name__ == '__main__': 85 | app.run(debug=True) 86 | -------------------------------------------------------------------------------- /timetable.py: -------------------------------------------------------------------------------- 1 | from pyquery import PyQuery as pq 2 | import json 3 | import requests 4 | import base64 5 | 6 | 7 | TimeTable = {} 8 | 9 | Slots = [] 10 | 11 | 12 | def getCookieFromToken(token): 13 | try: 14 | token = token.replace('\\n', '\n') 15 | token = base64.decodestring(str.encode(token)) 16 | cookie = json.loads(token) 17 | return cookie 18 | except: 19 | return "error" 20 | 21 | 22 | 23 | 24 | 25 | 26 | def get_timetable(index, element): 27 | DayName = "Day-" + str(index + 1) 28 | timetable_eachDay = {} 29 | 30 | for index, value in enumerate(pq(element).find('td:nth-child(n + 2)')): 31 | timetable_eachDay[Slots[index]] = pq(value).text() 32 | 33 | TimeTable[DayName] = timetable_eachDay 34 | 35 | 36 | 37 | 38 | url = "https://academia.srmuniv.ac.in/liveViewHeader.do" 39 | 40 | 41 | def getTimeTable(token, batch): 42 | batch = str(batch) 43 | 44 | if(batch == "1"): 45 | viewLinkName = "Common_Time_Table_Batch_1" 46 | elif(batch == "2"): 47 | viewLinkName = "Common_Time_Table_Batch_2" 48 | else: 49 | json_o = {"status":"error", "msg":"Error in batch name."} 50 | json_o = json.dumps(json_o) 51 | return json_o 52 | 53 | Cookies = getCookieFromToken(token) 54 | if (Cookies == "error"): 55 | json_o = {"status": "error", "msg": "Error in token"} 56 | json_o = json.dumps(json_o) 57 | return json_o 58 | else: 59 | 60 | headers = {'Origin': 'https://academia.srmuniv.ac.in', 61 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36' 62 | } 63 | data = {"sharedBy": "srm_university", 64 | "appLinkName": "academia-academic-services", 65 | "viewLinkName": viewLinkName, 66 | "urlParams": {}, 67 | "isPageLoad": "true"} 68 | 69 | dom = requests.post(url, data=data, headers=headers,cookies=Cookies).text 70 | 71 | s1 = '$("#zc-viewcontainer_' + viewLinkName + '").prepend(pageSanitizer.sanitize(' 72 | s2 = '});' 73 | 74 | a, b = dom.find(s1), dom.find(s2) 75 | dom = pq(dom[a + 56 + len(viewLinkName):b - 5]) 76 | 77 | 78 | 79 | 80 | for value in dom('table[width="400"]').find('tr').eq(0).find('td:nth-child(n + 2)'): 81 | Slots.append(pq(value).text().replace(" ", "")) 82 | 83 | dom('table[width="400"]').find('tr:nth-child(n + 5)').each(get_timetable) 84 | 85 | 86 | if len(TimeTable) > 3: 87 | json_o = {"status": "success", "data": TimeTable} 88 | json_o = json.dumps(json_o) 89 | return json_o 90 | else: 91 | json_o = {"status": "error", "msg": "Error occured"} 92 | json_o = json.dumps(json_o) 93 | return json_o 94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /attendence_marks.py: -------------------------------------------------------------------------------- 1 | from pyquery import PyQuery as pq 2 | import json 3 | import requests 4 | import base64 5 | import re 6 | 7 | 8 | AttendanceDetails = [] 9 | 10 | def getCookieFromToken(token): 11 | try: 12 | token = token.replace('\\n', '\n') 13 | token = base64.decodestring(str.encode(token)) 14 | cookie = json.loads(token) 15 | return cookie 16 | except: 17 | return "error" 18 | 19 | 20 | 21 | 22 | 23 | def get_attendancedata(index, element): 24 | global AttendanceDetails 25 | 26 | if index == 0: 27 | AttendanceDetails = [] 28 | print(AttendanceDetails) 29 | CourseCode = pq(element).find('td').eq(0).text() 30 | 31 | if CourseCode.find("Regular") == -1: 32 | pass 33 | else: 34 | CourseCode = CourseCode[:-8] 35 | 36 | 37 | AttendanceDetails.append({ 38 | "CourseCode": CourseCode, 39 | "CourseTitle": pq(element).find('td').eq(1).text(), 40 | "Category": pq(element).find('td').eq(2).text(), 41 | "FacultyName": pq(element).find('td').eq(3).text(), 42 | "Slot": pq(element).find('td').eq(4).text(), 43 | "RoomNo": pq(element).find('td').eq(5).text(), 44 | "HoursConducted": pq(element).find('td').eq(6).text(), 45 | "HoursAbsent": pq(element).find('td').eq(7).text(), 46 | "Attendance": pq(element).find('td').eq(8).text(), 47 | "UniversityPracticalDetails": pq(element).find('td').eq(9).text()}) 48 | 49 | 50 | Marks = [] 51 | 52 | 53 | def get_marks(index, element): 54 | CourseCode = pq(element).find('td').eq(0).text() 55 | Marks_each = {} 56 | MarksTotal = 0 57 | for a in pq(element).find('td').eq(2).find('td'): 58 | testLabel = pq(a).find('strong').text() 59 | testLabelAndMarks = pq(a).text() 60 | testMarks = testLabelAndMarks.replace(testLabel, '') 61 | testMarks = testMarks.replace(" ", "") 62 | Marks_each[testLabel] = testMarks 63 | if (testMarks == "Abs"): 64 | continue 65 | else: 66 | MarksTotal = MarksTotal + float(testMarks) 67 | 68 | Marks_each["CourseCode"] = CourseCode; 69 | Marks_each["Total"] = MarksTotal; 70 | 71 | Marks.append(Marks_each) 72 | 73 | 74 | url = "https://academia.srmuniv.ac.in/liveViewHeader.do" 75 | 76 | 77 | def getAttendenceAndMarks(token): 78 | 79 | 80 | Cookies = getCookieFromToken(token) 81 | if (Cookies == "error"): 82 | json_o = {"status": "error", "msg": "Error in token"} 83 | json_o = json.dumps(json_o) 84 | return json_o 85 | else: 86 | 87 | viewLinkName = "My_Attendance" 88 | 89 | headers = {'Origin': 'https://academia.srmuniv.ac.in', 90 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36' 91 | } 92 | data = {"sharedBy": "srm_university", 93 | "appLinkName": "academia-academic-services", 94 | "viewLinkName": viewLinkName, 95 | "urlParams": {}, 96 | "isPageLoad": "true"} 97 | 98 | dom = requests.post(url, data=data, headers=headers, cookies=Cookies).text 99 | 100 | s1 = '$("#zc-viewcontainer_'+viewLinkName+'").prepend(pageSanitizer.sanitize(' 101 | s2 = '});' 102 | a, b = dom.find(s1), dom.find(s2) 103 | dom = pq(dom[a + 56 + len(viewLinkName):b - 5]) 104 | 105 | 106 | 107 | dom('table[border="1"]').eq(0).find('tr:nth-child(n + 2)').each(get_attendancedata) 108 | dom('table[align="center"]').eq(2).find('tr:nth-child(n + 2)').each(get_marks) 109 | 110 | 111 | AttendanceAndMarks = [] 112 | 113 | for value_att in AttendanceDetails: 114 | 115 | for value_marks in Marks: 116 | 117 | if value_att["CourseCode"] == value_marks["CourseCode"]: 118 | req_marks = value_marks.copy() 119 | req_marks.pop('CourseCode', None) 120 | value_att["Marks"] = req_marks 121 | else: 122 | continue 123 | AttendanceAndMarks.append(value_att) 124 | 125 | 126 | if len(AttendanceAndMarks) > 5: 127 | json_o = {"status": "success", "data": AttendanceAndMarks} 128 | json_o = json.dumps(json_o) 129 | return json_o 130 | else: 131 | json_o = {"status": "error", "msg": "Error occured"} 132 | json_o = json.dumps(json_o) 133 | return json_o 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /course_personal_details.py: -------------------------------------------------------------------------------- 1 | from pyquery import PyQuery as pq 2 | import json 3 | import requests 4 | import base64 5 | 6 | CourseDetails = {} 7 | FacultyAdvisors = [] 8 | 9 | 10 | def getCookieFromToken(token): 11 | try: 12 | token = token.replace('\\n', '\n') 13 | token = base64.decodestring(str.encode(token)) 14 | cookie = json.loads(token) 15 | return cookie 16 | except: 17 | return "error" 18 | 19 | 20 | 21 | 22 | 23 | def get_CourseDetails(index, element): 24 | CourseCode = pq(element).find("td").eq(0).text() 25 | CourseDetails[CourseCode] = {"CourseCode": pq(element).find("td").eq(0).text(), 26 | "CourseTitle": pq(element).find("td").eq(1).text(), 27 | "RegnType": pq(element).find("td").eq(2).text(), 28 | "Category": pq(element).find("td").eq(3).text(), 29 | "CourseType": pq(element).find("td").eq(4).text(), 30 | "FacultyName": pq(element).find("td").eq(5).text(), 31 | "Slot": pq(element).find("td").eq(6).text(), 32 | "RoomNo": pq(element).find("td").eq(7).text() } 33 | 34 | 35 | 36 | def get_facultyadvisordetails(index, element): 37 | FacultyAdvisors_each = {"FacultyAdvisorName": pq(element).find("strong").eq(0).text(), 38 | "FacultyAdvisorEmail": pq(element).find("font").eq(0).text()} 39 | FacultyAdvisors.append(FacultyAdvisors_each) 40 | 41 | 42 | def get_personaldetails(dom): 43 | RegistrationNumber = dom('table[cellspacing="1"]').eq(0).find('td').eq(1).text() 44 | Name = dom('table[cellspacing="1"]').eq(0).find('td').eq(3).text() 45 | Batch = dom('table[cellspacing="1"]').eq(0).find('td').eq(5).text() 46 | Mobile = dom('table[cellspacing="1"]').eq(0).find('td').eq(7).text() 47 | Program = dom('table[cellspacing="1"]').eq(0).find('td').eq(9).text() 48 | Department = dom('table[cellspacing="1"]').eq(0).find('td').eq(11).text() 49 | Semester = dom('table[cellspacing="1"]').eq(0).find('td').eq(13).text() 50 | 51 | PersonalDetails = { "RegistrationNumber": RegistrationNumber, 52 | "Name": Name, 53 | "Batch": Batch, 54 | "Mobile": Mobile, 55 | "Program": Program, 56 | "Department": Department, 57 | "Semester": Semester } 58 | return PersonalDetails 59 | 60 | 61 | 62 | 63 | 64 | url = "https://academia.srmuniv.ac.in/liveViewHeader.do" 65 | 66 | def getCoursePersonalDetailsData(token,sem="ODD"): 67 | if(sem == "EVEN"): 68 | viewLinkName = "My_Time_Table_2018_19_EVEN" 69 | elif(sem == "ODD"): 70 | viewLinkName = "My_Time_Table_2018_19_ODD" 71 | else: 72 | json_o = {"status": "error", "msg": "Error in batch name."} 73 | json_o = json.dumps(json_o) 74 | return json_o 75 | 76 | Cookies = getCookieFromToken(token) 77 | if(Cookies=="error"): 78 | json_o = {"status": "error", "msg": "Error in token"} 79 | json_o = json.dumps(json_o) 80 | return json_o 81 | else: 82 | 83 | headers = {'Origin': 'https://academia.srmuniv.ac.in', 84 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36' 85 | } 86 | data = {"sharedBy": "srm_university", 87 | "appLinkName": "academia-academic-services", 88 | "viewLinkName": viewLinkName, 89 | "urlParams": {}, 90 | "isPageLoad": "true"} 91 | 92 | dom = requests.post(url, data=data, headers=headers, cookies=Cookies).text 93 | 94 | s1 = '$("#zc-viewcontainer_' + viewLinkName + '").prepend(pageSanitizer.sanitize(' 95 | s2 = '});' 96 | 97 | a, b = dom.find(s1), dom.find(s2) 98 | dom = pq(dom[a + 56 + len(viewLinkName):b - 5]) 99 | 100 | print(dom) 101 | 102 | json_o = {"status": "success", "msg": "Error occured"} 103 | json_o = json.dumps(json_o) 104 | return json_o 105 | 106 | 107 | dom('table[border="1"]').find('tr:nth-child(n + 2)').each(get_CourseDetails) 108 | dom('td[align="center"]').each(get_facultyadvisordetails) 109 | 110 | 111 | PersonalDetails = get_personaldetails(dom) 112 | 113 | CompleteDetails = {} 114 | 115 | CompleteDetails['PersonalDetails'] = PersonalDetails 116 | CompleteDetails['FacultyAdvisors'] = FacultyAdvisors 117 | CompleteDetails['CourseDetails'] = CourseDetails 118 | return CompleteDetails 119 | 120 | 121 | def getCoursePersonalDetails(token): 122 | CompleteDetails = getCoursePersonalDetailsData(token, "EVEN") 123 | # if len(CompleteDetails['PersonalDetails']['RegistrationNumber']) > 5: 124 | # json_o = {"status": "success", "data": CompleteDetails} 125 | # json_o = json.dumps(json_o) 126 | # return json_o 127 | # else: 128 | # CompleteDetails2 = getCoursePersonalDetailsData(token, "ODD") 129 | # if len(CompleteDetails2['PersonalDetails']['RegistrationNumber']) > 5: 130 | # json_o = {"status": "success", "data": CompleteDetails2} 131 | # json_o = json.dumps(json_o) 132 | # return json_o 133 | # else: 134 | # json_o = {"status": "error", "msg": "Error occured"} 135 | # json_o = json.dumps(json_o) 136 | # return json_o 137 | 138 | 139 | --------------------------------------------------------------------------------