MagisterPy 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
MagisterPy/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .magister_session import MagisterSession
2
+ from .request_manager import LoginRequestsSender
3
+ from .jsparser import *
4
+ from .magister_errors import *
@@ -0,0 +1,27 @@
1
+ import requests
2
+ from .magister_errors import *
3
+ def error_handler(func):
4
+ '''
5
+ A decorator used to handle errors that can occure when when executing the functions
6
+ '''
7
+
8
+ def wrapper(*args, **kwargs):
9
+ _self = args[0]
10
+ try:
11
+ result = func(*args,**kwargs)
12
+ return result
13
+ except KeyboardInterrupt:
14
+ raise KeyboardInterrupt
15
+
16
+ except requests.exceptions.ConnectionError as e:
17
+ _self._logMessage("Could not connect to Magister")
18
+ if _self.automatically_handle_errors:
19
+ pass
20
+ else:
21
+ raise ConnectionError()
22
+ except BaseMagisterError as e:
23
+ if _self.automatically_handle_errors:
24
+ _self._logMessage(e.message)
25
+ else:
26
+ raise e
27
+ return wrapper
MagisterPy/jsparser.py ADDED
@@ -0,0 +1,43 @@
1
+ import json
2
+
3
+ class JsParser():
4
+ def __init__(self):
5
+ pass
6
+ def get_authcode_from_js(self,js_content:str):
7
+ line = 1192
8
+ column = 15476
9
+ buffer = 200
10
+ authcode = ""
11
+ js_content = js_content.split("\n")[line][column:column+buffer]
12
+ start_list_index = None
13
+ content_list = [] #stores the 2 lists containing info about the authcode
14
+
15
+ #Find the first and the second list
16
+ for idx, _char in enumerate(js_content):
17
+
18
+ if _char == "[":
19
+ start_list_index = idx
20
+
21
+ if _char == "]" and (not (start_list_index is None)):
22
+
23
+ content_list.append(json.loads(js_content[start_list_index:idx+1]))
24
+
25
+ if len(content_list)>1:
26
+ break
27
+
28
+ if len(content_list) == 2:
29
+ convert_to_int = lambda a: int(a)
30
+
31
+ random_char_list, index_list = content_list
32
+
33
+ index_list = list(map(convert_to_int,index_list))
34
+
35
+
36
+ for idx in index_list:
37
+ authcode+= random_char_list[idx]
38
+ if len(authcode) == 8:
39
+ return authcode
40
+ return None
41
+
42
+
43
+
@@ -0,0 +1,23 @@
1
+ class BaseMagisterError(Exception):
2
+
3
+ def __init__(self, message=None):
4
+ super().__init__(message)
5
+ self.message = message
6
+
7
+
8
+ class UnableToInputCredentials(BaseMagisterError):
9
+
10
+ def __init__(self, message="\nCouldn't input the credentials\nThis error can occure if the credentials were not Input in order\nSchool->Username->Passwords"):
11
+ super().__init__(message)
12
+ self.message = message
13
+
14
+
15
+ class IncorrectCredentials(BaseMagisterError):
16
+ def __init__(self, message="\nThe credentials provided were either incorrect or Magister rejected them"):
17
+ super().__init__(message)
18
+ self.message = message
19
+
20
+ class ConnectionError(BaseMagisterError):
21
+ def __init__(self, message="\nCould not connect to Magister. Please check your internet connection"):
22
+ super().__init__(message)
23
+ self.message = message
@@ -0,0 +1,342 @@
1
+ import requests
2
+ from urllib.parse import urlparse, parse_qs
3
+ from .request_manager import LoginRequestsSender
4
+ from typing import Optional
5
+ from .error_handler import error_handler
6
+ from .magister_errors import *
7
+
8
+ class MagisterSession():
9
+ '''
10
+ Creates a session with Magister
11
+
12
+ Parameters:
13
+ enable_logging (bool): Used to display errors in the standard output
14
+
15
+ automatically_handle_errors (bool): Used to automatically handle errors. If any function fails it returns None instead of raising an error
16
+ '''
17
+
18
+ def __init__(self, enable_logging = False, automatically_handle_errors = True):
19
+
20
+ self.request_sender = LoginRequestsSender()
21
+ self.session = requests.Session()
22
+ self.profile_auth_token = None #auth token for the redirect page
23
+ self.app_auth_token = None #auth token for the main app
24
+ self.authcode = None # gets randomly generated once every 3-7 days
25
+ self.sessionid = None # gets assigned when entering the login page
26
+ self.returnurl = None # used for some requests
27
+ self.main_payload = None #payload containing common parameters (authcode, returnurl, sessionid)
28
+ self.person_id = None # your account's person_id
29
+ self.account_id = None #your account id
30
+ self.api_url = None #url for accessing magister API
31
+ self.x_correlation_id = None #used in login requests
32
+ self.automatically_handle_errors = automatically_handle_errors
33
+
34
+ self.recieve_log = enable_logging
35
+ def _logMessage(self,msg:str):
36
+ if self.recieve_log:
37
+ print(msg)
38
+
39
+
40
+ @error_handler
41
+ def input_school(self, school_name:str) ->Optional[requests.Response]:
42
+ '''
43
+ Sets up a session by inputting the school name. This is the **first step** in the login sequence
44
+ and must be called before `input_username()` and `input_password()`.
45
+
46
+ Parameters:
47
+ school_name (str): The name of the school to authenticate against.
48
+
49
+ Usage:
50
+ This method is the **first step** of the login process. It must be called before
51
+ `input_username()` and `input_password()` to initialize the session.
52
+
53
+ Returns:
54
+ requests.Response: if the school is found and session is successfully initiated.
55
+ None: if the school is not found or there’s an issue in the school lookup.
56
+
57
+ Example:
58
+ session.input_school("MySchoolName")
59
+ '''
60
+
61
+
62
+ #Initializing the login session
63
+ url_login_page = "https://accounts.magister.net/"
64
+
65
+ response = self.session.get(url_login_page,allow_redirects=False)
66
+
67
+ redirect_url_1 = r"https://accounts.magister.net/connect/authorize?client_id=iam-profile&redirect_uri=https%3A%2F%2Faccounts.magister.net%2Fprofile%2Foidc%2Fredirect_callback.html&response_type=id_token%20token&scope=openid%20profile%20email%20magister.iam.profile&state=57dcb9c3b667407791ff32a7af41e703&nonce=ec78d557c0e44751bf573db6719445cd"
68
+ response = self.session.get(redirect_url_1,allow_redirects=False)
69
+
70
+
71
+ response = self.session.get(response.headers.get("location"), allow_redirects= False)
72
+
73
+ response = self.session.get("https://accounts.magister.net/" + response.headers.get("location"), allow_redirects= False)
74
+
75
+ self.sessionid = parse_qs(urlparse(response.url).query).get('sessionId', [None])[0]
76
+ self.returnurl = parse_qs(urlparse(response.url).query).get('returnUrl', [None])[0]
77
+ self.x_correlation_id = parse_qs(urlparse(self.returnurl).query).get('X-Correlation-ID', [None])[0]
78
+
79
+ javascript_redirect_url = self.request_sender.extract_redirect_url_from_html(response.text)
80
+
81
+
82
+ response = self.session.get(f"https://accounts.magister.net/{javascript_redirect_url}")
83
+
84
+ self.authcode = self.request_sender.extract_dynamic_authcode(response.text)
85
+
86
+
87
+
88
+ self.main_payload = {
89
+ 'authCode': self.authcode,
90
+ 'returnUrl': self.returnurl,
91
+ 'sessionId': self.sessionid
92
+ }
93
+ #Inputting the credentials
94
+
95
+ #school
96
+ try:
97
+ response = self.request_sender.set_school(request_session=self.session,school_name=school_name,main_payload=self.main_payload)
98
+ if response.status_code !=200:
99
+ raise IncorrectCredentials(f"Could not find school: {school_name}")
100
+ except requests.exceptions.JSONDecodeError:
101
+ raise IncorrectCredentials(f"Could not find school: {school_name}")
102
+
103
+ return response
104
+ @error_handler
105
+ def input_username(self,username:str) -> Optional[requests.Response]:
106
+ '''
107
+ Sets the username for the current session. This is the **second step** in the login sequence
108
+ and must be called after `input_school()` but before `input_password()`.
109
+
110
+ Parameters:
111
+ username (str): The username for the account.
112
+
113
+ Usage:
114
+ This function must be called **after `input_school()`** and **before `input_password()`** to
115
+ authenticate the username.
116
+
117
+ Returns:
118
+ requests.Response: if the username is accepted.
119
+ None: if the username is not found or if there’s an issue during authentication.
120
+
121
+ Example:
122
+ session.input_username("myusername")
123
+ '''
124
+
125
+ if not self.main_payload:
126
+ raise UnableToInputCredentials()
127
+ response = self.request_sender.set_username(request_session=self.session,username=username,main_payload=self.main_payload)
128
+ if response.status_code != 200:
129
+ raise IncorrectCredentials()
130
+
131
+
132
+ return response
133
+ @error_handler
134
+ def input_password(self,password:str) -> Optional[requests.Response]:
135
+ '''
136
+ Sets the password for the session and finalizes the login process. This is the **third and final step**
137
+ in the login sequence and must be called after `input_school()` and `input_username()`.
138
+
139
+ Upon success, this method retrieves and stores essential session variables like `profile_auth_token`,
140
+ `api_url`, `app_auth_token`, `account_id`, and `person_id` for further interactions with the API.
141
+
142
+ Parameters:
143
+ password (str): The password associated with the username.
144
+
145
+ Usage:
146
+ This function is the **final step** in the login process and should only be called **after
147
+ `input_school()` and `input_username()`**.
148
+
149
+ Returns:
150
+ requests.Response: if the password is correct and login is successful.
151
+ None: if the password is incorrect or if there’s a login cooldown.
152
+
153
+ Example:
154
+ session.input_password("mypassword")
155
+ '''
156
+ if not self.main_payload:
157
+ raise UnableToInputCredentials()
158
+ response = self.request_sender.set_password(request_session=self.session,password=password,main_payload=self.main_payload)
159
+ if response.status_code !=200:
160
+ raise IncorrectCredentials("Incorrect password or the password input is on cooldown")
161
+
162
+ #setup for variables
163
+ self.profile_auth_token = self.request_sender.get_profile_auth_token(request_session=self.session)
164
+ self.api_url = self.request_sender.get_api_url(request_session=self.session,profile_auth_token=self.profile_auth_token)
165
+ self.app_auth_token = self.request_sender.get_app_auth_token(request_session=self.session,api_url=self.api_url)
166
+ self.account_id = self.request_sender.get_accountid(request_session=self.session,app_auth_token=self.app_auth_token,api_url=self.api_url)
167
+ self.person_id = self.request_sender.get_personid(request_session=self.session,app_auth_token=self.app_auth_token,api_url=self.api_url,account_id=self.account_id)
168
+
169
+ self._logMessage("you have successfully logged in!")
170
+ return response
171
+ @error_handler
172
+ def login(self,school_name:str,username:str,password:str) -> bool:
173
+ '''
174
+ logs the user into their account
175
+
176
+ returns:
177
+ True -> if user logged in successfully
178
+ False -> if user wasn't able to login
179
+
180
+ params:
181
+ school_name -> a string of school name (not case sensitive. It sets the first school from the list that magister provides)
182
+ username -> a string of a username (should be exact)
183
+ password -> a string with the password of the user (should be exact)
184
+ '''
185
+ #Inputting the school
186
+ input_school_response = self.input_school(school_name=school_name)
187
+
188
+ if not input_school_response:
189
+ return False
190
+
191
+
192
+ #username
193
+ input_username_response = self.input_username(username=username)
194
+ if not input_username_response:
195
+ return False
196
+ #password
197
+ input_password_response = self.input_password(password=password)
198
+ if not input_password_response:
199
+ return False
200
+
201
+ return True
202
+
203
+
204
+ @error_handler
205
+ def get_schedule(self, _from:str, to:str,with_changes = False) -> list[dict]:
206
+ '''
207
+ Retrieves the user’s schedule within a specified date range.
208
+
209
+ This method fetches all scheduled items between two dates, starting from `_from` to `to`.
210
+ The session must be authenticated by calling `.login()` first.
211
+
212
+ Parameters:
213
+ - _from (str): Start date of the schedule period in "YYYY-MM-DD" format.
214
+ - to (str): End date of the schedule period in "YYYY-MM-DD" format.
215
+ - with_changes: Sends a different requests which retrieves recent changes. (It's not getting the changes on specific dates specifically so it's recomended to use with_changes = False instead)
216
+ Returns:
217
+ - list[dict]: A list of dictionaries representing schedule items, each with detailed fields.
218
+ The schedule items are sorted chronologically from earliest to latest.
219
+
220
+ Structure of Each Schedule Item:
221
+ ```json
222
+ {
223
+ "Start": "datetime", # Start time of the scheduled item
224
+ "Einde": "datetime", # End time of the scheduled item
225
+ "LesuurVan": bool, # Boolean for lesson period start
226
+ "LesuurTotMet": bool, # Boolean for lesson period end
227
+ "DuurtHeleDag": bool, # Whether the event lasts all day
228
+ "Omschrijving": str, # Description of the event
229
+ "Lokatie": str, # Location of the event
230
+ "Status": int, # Status code of the event
231
+ "Type": int, # Type identifier of the event
232
+ "Subtype": int, # Subtype identifier of the event
233
+ "IsOnlineDeelname": bool, # Whether online attendance is allowed
234
+ "WeergaveType": int, # Display type identifier
235
+ "Inhoud": str, # Content or details about the event
236
+ "Opmerking": str, # Additional notes
237
+ "InfoType": int, # Information type identifier
238
+ "Aantekening": str, # Notes or annotations
239
+ "Afgerond": bool, # Whether the event is completed
240
+ "HerhaalStatus": int, # Repeat status identifier
241
+ "Herhaling": None, # Repeat information (usually null)
242
+ "Vakken": list[dict], # List of subjects related to the event
243
+ "Docenten": list[dict], # List of teachers associated with the event
244
+ "Lokalen": list[dict], # List of rooms assigned for the event
245
+ "Groepen": None, # Group information (usually null)
246
+ "OpdrachtId": int, # Task ID if associated
247
+ "HeeftBijlagen": bool, # Whether attachments are available
248
+ "Bijlagen": None # Attachment information (usually null)
249
+ }
250
+ ```
251
+
252
+ Example Usage:
253
+ ```python
254
+ session.get_schedule(_from="2024-11-10", to="2024-11-11")
255
+ ```
256
+ '''
257
+ if not self.app_auth_token:
258
+ self._logMessage("You have not logged in yet")
259
+ return
260
+ #Returns a schedule with no changes in it
261
+ remove_links_and_id = lambda a: {k: v for k, v in a.items() if k not in ["Links", "Id"]}
262
+ params = {
263
+ "status" : 1,
264
+ "tot": to,
265
+ "van": _from
266
+ }
267
+ headers = {"authorization":self.app_auth_token}
268
+ if not with_changes:
269
+
270
+
271
+
272
+ url = f"{self.api_url}/personen/{self.person_id}/afspraken"
273
+ respone = self.session.get(url=url,params=params,headers=headers)
274
+
275
+
276
+ else:
277
+ del params["status"]
278
+ url = f"{self.api_url}/personen/{self.person_id}/roosterwijzigingen"
279
+ respone = self.session.get(url=url,params=params,headers=headers)
280
+
281
+ if respone.status_code == 200:
282
+
283
+ response_json = respone.json()["Items"]
284
+ return list(map(remove_links_and_id,response_json))
285
+ @error_handler
286
+ def get_grades(self, top:int = 25,skip:int = 0) -> list[dict]:
287
+ '''
288
+ Retrieves the most recent grades for the user.
289
+
290
+ This method fetches the latest grades for the authenticated user.
291
+ The session must be authenticated by calling `.login()` first.
292
+
293
+ Parameters:
294
+ - top (int): Number of grades to retrieve (default is 25).
295
+ - skip (int): Number of grades to skip, for pagination (default is 0).
296
+
297
+ Returns:
298
+ - list[dict]: A list of dictionaries, each representing a grade item with relevant details.
299
+ Grades are sorted from the most recent to the oldest.
300
+
301
+ Structure of Each Grade Item:
302
+ ```json
303
+ {
304
+ "omschrijving": str, # Description of the grade item
305
+ "ingevoerdOp": "datetime", # Date when the grade was entered
306
+ "vak": { # Subject information
307
+ "code": str, # Subject code
308
+ "omschrijving": str # Subject description
309
+ },
310
+ "waarde": str, # Grade value or score
311
+ "weegfactor": float, # Weight factor of the grade
312
+ "isVoldoende": bool, # Whether the grade is sufficient
313
+ "teltMee": bool, # Whether the grade counts in the final score
314
+ "moetInhalen": bool, # If the grade needs to be retaken
315
+ "heeftVrijstelling": bool, # If the grade has an exemption
316
+ "behaaldOp": None, # Date achieved (if available)
317
+ "links": dict # Additional links or references (usually empty)
318
+ }
319
+ ```
320
+
321
+ Example Usage:
322
+ ```python
323
+ session.get_grades(top=1)
324
+ ```
325
+ '''
326
+ if not self.app_auth_token:
327
+ self._logMessage("You have not logged in yet")
328
+ return
329
+ remove_id = lambda a: {k: v for k, v in a.items() if k not in ["kolomId"]}
330
+ params = {
331
+ "top": top,
332
+ "skip": skip
333
+ }
334
+ headers = {"authorization":self.app_auth_token}
335
+ url = f"{self.api_url}/personen/{self.person_id}/cijfers/laatste"
336
+ respone = self.session.get(url=url,params=params,headers=headers)
337
+
338
+ if respone.status_code == 200:
339
+
340
+ response_json = respone.json()["items"]
341
+ return list(map(remove_id,response_json))
342
+
@@ -0,0 +1,169 @@
1
+ import requests
2
+ from urllib.parse import urlparse, parse_qs
3
+ from bs4 import BeautifulSoup
4
+ from .jsparser import *
5
+ from typing import Optional
6
+ class LoginRequestsSender():
7
+
8
+
9
+
10
+ def __init__(self):
11
+ pass
12
+
13
+ def get_subdomain(self,url):
14
+
15
+ parsed_url = urlparse(url)
16
+
17
+
18
+ netloc = parsed_url.netloc
19
+
20
+
21
+ parts = netloc.split('.')
22
+
23
+
24
+ if len(parts) > 2:
25
+
26
+ return '.'.join(parts[:-2])
27
+ else:
28
+ return None
29
+
30
+
31
+ def get_accountid(self,request_session:requests.Session, app_auth_token,api_url) -> str:
32
+ url = f"{api_url}/sessions/current"
33
+
34
+
35
+ headers = {
36
+ "authorization": app_auth_token
37
+ }
38
+ response = request_session.get(url, headers=headers,cookies=request_session.cookies)
39
+ if response.status_code == 200:
40
+ response_link = response.json()["links"]["account"]["href"]
41
+ account_id = response_link[response_link.rfind("/")+1:]
42
+
43
+ return account_id
44
+
45
+ def get_personid(self,request_session:requests.Session, app_auth_token,api_url,account_id) -> str:
46
+
47
+
48
+ url = f"{api_url}/accounts/{account_id}"
49
+
50
+
51
+
52
+ headers = {
53
+ "authorization": app_auth_token
54
+ }
55
+
56
+ response = request_session.get(url=url,headers=headers)
57
+
58
+ if response.status_code == 200:
59
+ response_link = response.json()["links"]["leerling"]["href"]
60
+ personid = response_link[response_link.rfind("/")+1:]
61
+
62
+ return personid
63
+ def extract_auth_token(self,url) -> str:
64
+ # Parse the URL to get the fragment
65
+ parsed_url = urlparse(url)
66
+
67
+ fragment = parsed_url.fragment
68
+
69
+ # Parse the fragment to get the id_token
70
+ token_params = parse_qs(fragment)
71
+ id_token = token_params.get('access_token')
72
+
73
+ if id_token:
74
+ return id_token[0] # Return the first id_token found
75
+ else:
76
+ return None
77
+ def get_profile_auth_token(self,request_session:requests.Session) -> str:
78
+ url = r"https://accounts.magister.net/connect/authorize?client_id=iam-profile&redirect_uri=https%3A%2F%2Faccounts.magister.net%2Fprofile%2Foidc%2Fredirect_callback.html&response_type=id_token%20token&scope=openid%20profile%20email%20magister.iam.profile&state=57dcb9c3b667407791ff32a7af41e703&nonce=ec78d557c0e44751bf573db6719445cd"
79
+ response = request_session.get(url,allow_redirects=False)
80
+
81
+ url = response.headers["Location"]
82
+ return "Bearer " + self.extract_auth_token(url)
83
+
84
+
85
+ def get_app_auth_token(self,request_session:requests.Session,api_url) -> str:
86
+ url = "https://accounts.magister.net/connect/authorize"
87
+ subdomain = self.get_subdomain(api_url)
88
+ # Parameters as a dictionary
89
+ params = {
90
+ "client_id": f"M6-{subdomain}.magister.net",
91
+ "redirect_uri": f"https://{subdomain}.magister.net/oidc/redirect_callback.html",
92
+ "response_type": "id_token token",
93
+ "scope": "openid profile opp.read opp.manage attendance.overview attendance.administration "
94
+ "calendar.user calendar.ical.user calendar.to-do.user grades.read grades.manage "
95
+ "oso.administration registration.admin lockers.administration enrollment.admin",
96
+ "state": "57dcb9c3b667407791ff32a7af41e703",
97
+ "nonce": "ec78d557c0e44751bf573db6719445cd",
98
+ "acr_values": f"tenant:{subdomain}.magister.net"
99
+ }
100
+
101
+ response = request_session.get(url, params=params,allow_redirects=False)
102
+ url = response.headers["Location"]
103
+ return "Bearer " + self.extract_auth_token(url)
104
+
105
+
106
+ def get_api_url(self,request_session:requests.Session,profile_auth_token) -> str:
107
+ headers = {"authorization": profile_auth_token}
108
+
109
+ response = request_session.get("https://magister.net/.well-known/host-meta.json",headers=headers)
110
+
111
+
112
+ items = response.json()
113
+ main_page = items["links"][0]["href"]
114
+ return main_page
115
+
116
+
117
+ def search_for_tenant_id(self,request_session:requests.Session,school_name,session_id) ->str :
118
+ response = request_session.get(f"https://accounts.magister.net/challenges/tenant/search?sessionId={session_id}&key={school_name}")
119
+ return response.json()[0]["id"]
120
+ def set_school(self,request_session:requests.Session,school_name,main_payload) ->requests.Response:
121
+
122
+ tenant_id = self.search_for_tenant_id(request_session,school_name, main_payload["sessionId"])
123
+ main_payload["tenant"] = tenant_id
124
+
125
+ response= self.send_post_request(request_session,main_payload,"https://accounts.magister.net/challenges/tenant")
126
+ return response
127
+
128
+ def set_password(self,request_session,password,main_payload) ->requests.Response:
129
+ main_payload["password"] = password
130
+ main_payload["userWantsToPairSoftToken"] = False
131
+ return self.send_post_request(request_session, main_payload,"https://accounts.magister.net/challenges/password")
132
+
133
+ def set_username(self,request_session,username,main_payload) -> requests.Response:
134
+
135
+
136
+ main_payload["username"] = username
137
+ response = self.send_post_request(request_session,main_payload,"https://accounts.magister.net/challenges/username")
138
+ return response
139
+ def send_post_request(self,request_session:requests.Session,payload,url,auth_token = None) -> requests.Response:
140
+
141
+ headers = {
142
+ "accept": "application/json",
143
+ "content-type": "application/json",
144
+ "authorization": auth_token,
145
+ "origin": "https://accounts.magister.net",
146
+ "x-xsrf-token": request_session.cookies.get("XSRF-TOKEN")
147
+ }
148
+
149
+
150
+
151
+
152
+ response = request_session.post(url=url,json=payload,headers=headers, cookies=request_session.cookies)
153
+ return response
154
+ def extract_redirect_url_from_html(self,html_content:str) ->str:
155
+ soup = BeautifulSoup(html_content, 'html.parser')
156
+
157
+ # Find the script tag with defer attribute
158
+ script_tag = soup.find('script', {'defer': 'defer'})
159
+
160
+ # Extract the src attribute
161
+ if script_tag and 'src' in script_tag.attrs:
162
+ script_src = script_tag['src']
163
+ return script_src
164
+ else:
165
+ return None
166
+ def extract_dynamic_authcode(self,js_content):
167
+ return JsParser().get_authcode_from_js(js_content=js_content)
168
+
169
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 H3LL0U
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.
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.1
2
+ Name: MagisterPy
3
+ Version: 0.1.0
4
+ Summary: A Python package for retrieving information from magister
5
+ Home-page: https://github.com/H3LL0U/MagisterPy
6
+ Author: H3LL0U
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: beautifulsoup4==4.12.3
14
+ Requires-Dist: certifi==2024.8.30
15
+ Requires-Dist: charset-normalizer==3.4.0
16
+ Requires-Dist: idna==3.10
17
+ Requires-Dist: requests==2.32.3
18
+ Requires-Dist: soupsieve==2.6
19
+ Requires-Dist: urllib3==2.2.3
20
+
21
+ # MagisterPY
22
+
23
+ This library will help you interact with your magister account using python!
24
+
25
+ ## Disclaimer:
26
+ Please note: Using unauthorized APIs to access Magister is against Magister’s Terms of Service.
27
+ By using this library, you assume all responsibility and accept any risks associated with breaching these terms.
28
+ For more details, please refer to Magister's Terms of Service. (https://magister.nl/over-ons/juridische-zaken/)
29
+
30
+ ## Installation
31
+ ```
32
+ pip install git+https://github.com/H3LL0U/MagisterPy.git
33
+ ```
34
+
35
+ ## Contributing
36
+ Feel free to create an issue if something doesn't work. It's only been tested on a singular school so far so it is to be expected. If you want to help add a feature it would be great as well! :D
37
+
38
+ ## Basic usage
39
+ The following code snippet demonstrates how to create a session, log in, and retrieve your schedule and recent grades:
40
+ ```
41
+ from magisterpy import MagisterSession
42
+
43
+ # Create a new session and log in
44
+ session = MagisterSession()
45
+ session.login(school_name="School_name", username="your_username", password="your_password")
46
+
47
+ # Get schedule for a specific date range
48
+ my_schedule = session.get_schedule("2024-11-03", "2024-11-10")
49
+
50
+ # Get the most recent grade
51
+ my_most_recent_grade = session.get_grades(top=1)[0]["waarde"]
52
+
53
+ print("Schedule:", my_schedule)
54
+ print("Most Recent Grade:", my_most_recent_grade)
55
+ ```
56
+ With MagisterPy, you can access and manage your Magister account directly from Python, automating repetitive tasks and integrating your school data into your projects. We hope you find it helpful!
57
+ More functionality to come!
@@ -0,0 +1,11 @@
1
+ MagisterPy/__init__.py,sha256=pBqUptTl7dfnWfMw3LSyzfLF3YELq25SGxtVt9oe4OI,152
2
+ MagisterPy/error_handler.py,sha256=CnK60mfbn6VtFG4wD5Nt7afFMkSfqawYRl15ZM5RpWw,872
3
+ MagisterPy/jsparser.py,sha256=IkRqD_NcwXTE7CgeNP2OKpUOmnm2cLvEXYuFVYpPrpo,1283
4
+ MagisterPy/magister_errors.py,sha256=jFXMc1dSwMep6JYMx4ZdsmQRzTSuW3mdIhSfToZ0eQY,897
5
+ MagisterPy/magister_session.py,sha256=mFIDwNcYWS9dU0Qfw3IBzZjQKVKkpK1Jx0wWFiBDNG4,15829
6
+ MagisterPy/request_manager.py,sha256=V5lIgs1GoLMoCMx-qEOVmIS5W6klWX8YqJ9ugnsbkqo,6697
7
+ MagisterPy-0.1.0.dist-info/LICENSE,sha256=beoj0kwBYTN1yTItpASt3XIMGW0KX4rcbJOVpy2mZ6c,1084
8
+ MagisterPy-0.1.0.dist-info/METADATA,sha256=0pLOSEu9ojJw7phtlWx7SzON0QF1Jaxyp1IW0zJ40c4,2278
9
+ MagisterPy-0.1.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
10
+ MagisterPy-0.1.0.dist-info/top_level.txt,sha256=-8fWbu8ze-lHyvaI77aZo1gQkd8KNU5vvZBPkLLum-4,11
11
+ MagisterPy-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.6.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ MagisterPy