Skip to content

Commit

Permalink
Formatting (black, isort)
Browse files Browse the repository at this point in the history
  • Loading branch information
BelKed committed Mar 4, 2024
1 parent 1bec82a commit 9418918
Show file tree
Hide file tree
Showing 23 changed files with 512 additions and 310 deletions.
28 changes: 17 additions & 11 deletions edupage_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
from edupage_api.lunches import Lunch, Lunches
from edupage_api.messages import Messages
from edupage_api.module import EdupageModule
from edupage_api.parent import Parent
from edupage_api.people import (EduAccount, EduStudent, EduStudentSkeleton,
EduTeacher, People)
from edupage_api.ringing import RingingTime, RingingTimes
from edupage_api.substitution import Substitution, TimetableChange
from edupage_api.timeline import TimelineEvent, TimelineEvents
from edupage_api.timetables import Timetable, Timetables
from edupage_api.parent import Parent


class Edupage(EdupageModule):
Expand All @@ -40,7 +40,9 @@ def __init__(self, request_timeout=5):
self.username = None

self.session = requests.session()
self.session.request = functools.partial(self.session.request, timeout=request_timeout)
self.session.request = functools.partial(
self.session.request, timeout=request_timeout
)

def login(
self, username: str, password: str, subdomain: str
Expand Down Expand Up @@ -100,13 +102,15 @@ def get_teachers(self) -> Optional[list[EduTeacher]]:

return People(self).get_teachers()

def send_message(self, recipients: Union[list[EduAccount], EduAccount], body: str) -> int:
def send_message(
self, recipients: Union[list[EduAccount], EduAccount], body: str
) -> int:
"""Send message.
Args:
recipients (Optional[list[EduAccount]]): Recipients of your message (list of `EduAccount`s).
body (str): Body of your message.
Returns:
int: The timeline id of the new message.
"""
Expand Down Expand Up @@ -166,10 +170,10 @@ def get_grades(self) -> list[EduGrade]:
"""

return Grades(self).get_grades(year=None, term=None)

def get_grades_for_term(self, year: int, term: Term) -> list[EduGrade]:
"""Get a list of all available grades for a given year and term
Returns:
list[EduGrade]: List of `EduGrade`s
"""
Expand All @@ -185,7 +189,9 @@ def get_user_id(self) -> str:

return self.data.get("userid")

def custom_request(self, url: str, method: str, data: str = "", headers: dict = {}) -> Response:
def custom_request(
self, url: str, method: str, data: str = "", headers: dict = {}
) -> Response:
"""Send custom request to EduPage.
Args:
Expand Down Expand Up @@ -255,17 +261,17 @@ def get_next_ringing_time(self, date_time: datetime) -> RingingTime:
RingingTime: The type (break or lesson) and time of the next ringing.
"""
return RingingTimes(self).get_next_ringing_time(date_time)

def switch_to_child(self, child: Union[EduAccount, int]):
"""Switch to an account of a child - can only be used on parent accounts
Args:
child (EduAccount | int): The account or `person_id` of the child you want to switch to
Note: When you switch to a child account, all other methods will return data as if you were logged in as `child`
Note: When you switch to a child account, all other methods will return data as if you were logged in as `child`
"""
Parent(self).switch_to_child(child)

def switch_to_parent(self):
"""Switches back to your parent account - can only be used on parent accounts"""
Parent(self).switch_to_parent()
Expand All @@ -285,4 +291,4 @@ def from_session_id(cls, session_id: str, subdomain: str):

Login(instance).reload_data(subdomain, session_id)

return instance
return instance
6 changes: 4 additions & 2 deletions edupage_api/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def parse(data: dict):
data.get("extension"),
data.get("type"),
data.get("file"),
data.get("name")
data.get("name"),
)


Expand Down Expand Up @@ -77,7 +77,9 @@ def upload_file(self, fd: TextIOWrapper) -> EduCloudFile:
EduCloudFile: `EduCloudFile` object.
"""

request_url = f"https://{self.edupage.subdomain}.edupage.org/timeline/?akcia=uploadAtt"
request_url = (
f"https://{self.edupage.subdomain}.edupage.org/timeline/?akcia=uploadAtt"
)

files = {"att": fd}

Expand Down
84 changes: 44 additions & 40 deletions edupage_api/compression.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
import zlib
from hashlib import sha1
from typing import Union
from edupage_api.exceptions import Base64DecodeError

from edupage_api.exceptions import Base64DecodeError
from edupage_api.module import ModuleHelper


# compression parameters from https://github.com/rgnter/epea_cpp
# encoding and decoding from https://github.com/jsdom/abab
class RequestData:
@staticmethod
def __compress(data: bytes) -> bytes:
compressor = zlib.compressobj(
-1,
zlib.DEFLATED,
-15,
8,
zlib.Z_DEFAULT_STRATEGY
-1, zlib.DEFLATED, -15, 8, zlib.Z_DEFAULT_STRATEGY
)


compressor.compress(data)
return compressor.flush(zlib.Z_FINISH)

Expand All @@ -36,10 +32,11 @@ def chromium_base64_encode(data: str) -> str:
# Lookup table for btoa(), which converts a six-bit number into the
# corresponding ASCII character.
chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

def btoa_lookup(index):
if index >= 0 and index < 64:
return chars[index]

return None

out = ""
Expand All @@ -50,37 +47,38 @@ def btoa_lookup(index):

if length > i + 1:
groups_of_six[1] |= ord(data[i + 1]) >> 4
groups_of_six[2] = (ord(data[i + 1]) & 0x0f) << 2
groups_of_six[2] = (ord(data[i + 1]) & 0x0F) << 2

if length > i + 2:
groups_of_six[2] |= ord(data[i + 2]) >> 6
groups_of_six[3] = ord(data[i + 2]) & 0x3f
groups_of_six[3] = ord(data[i + 2]) & 0x3F

for k in groups_of_six:
if k is None:
out += "="
else:
out += btoa_lookup(k)



i += 3

return out


def chromium_base64_decode(data: str) -> str:
# "Remove all ASCII whitespace from data."
[data := data.replace(char, "") for char in "\t\n\f\r"]

# "If data's code point length divides by 4 leaving no remainder, then: if data ends
# with one or two U+003D (=) code points, then remove them from data."
if len(data) % 4 == 0:
if data.endswith("=="):
data = data.replace("==", "")
elif data.endswith("="):
data = data.replace("=", "")

allowed_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

allowed_chars = (
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
)

def atob_lookup(ch: str):
try:
return allowed_chars.index(ch)
Expand All @@ -100,7 +98,7 @@ def atob_lookup(ch: str):
data_contains_invalid_chars = False in [ch in allowed_chars for ch in data]
if len(data) % 4 == 1 or data_contains_invalid_chars:
return None

# "Let output be an empty byte sequence."
output = ""

Expand All @@ -110,7 +108,7 @@ def atob_lookup(ch: str):
# when we've gotten to 24 bits.
buffer = 0
accumulated_bits = 0

# "Let position be a position variable for data, initially pointing at the
# start of data."
#
Expand All @@ -132,13 +130,13 @@ def atob_lookup(ch: str):
# big-endian numbers. Append three bytes with values equal to those
# numbers to output, in the same order, and then empty buffer."
if accumulated_bits == 24:
output += chr((buffer & 0xff0000) >> 16)
output += chr((buffer & 0xff00) >> 8)
output += chr(buffer & 0xff)
output += chr((buffer & 0xFF0000) >> 16)
output += chr((buffer & 0xFF00) >> 8)
output += chr(buffer & 0xFF)

buffer = 0
accumulated_bits = 0

# "If buffer is not empty, it contains either 12 or 18 bits. If it contains
# 12 bits, then discard the last four and interpret the remaining eight as
# an 8-bit big-endian number. If it contains 18 bits, then discard the last
Expand All @@ -150,49 +148,55 @@ def atob_lookup(ch: str):
output += chr(buffer)
elif accumulated_bits == 18:
buffer >>= 2
output += chr((buffer & 0xff00) >> 8)
output += chr(buffer & 0xff)
output += chr((buffer & 0xFF00) >> 8)
output += chr(buffer & 0xFF)

return output


@staticmethod
def __encode_data(data: str) -> bytes:
compressed = RequestData.__compress(data.encode())

encoded = RequestData.chromium_base64_encode("".join([chr(ch) for ch in compressed]))
encoded = RequestData.chromium_base64_encode(
"".join([chr(ch) for ch in compressed])
)

return encoded

@staticmethod
def __decode_data(data: str) -> str:
return RequestData.chromium_base64_decode(data)

@staticmethod
def encode_request_body(request_data: Union[dict, str]) -> str:
encoded_data = ModuleHelper.encode_form_data(request_data) if type(request_data) == dict else request_data
encoded_data = (
ModuleHelper.encode_form_data(request_data)
if type(request_data) == dict
else request_data
)
encoded_data = RequestData.__encode_data(encoded_data)
data_hash = sha1(encoded_data.encode()).hexdigest()

return ModuleHelper.encode_form_data({
"eqap": f"dz:{encoded_data}",
"eqacs": data_hash,
"eqaz": "1" # use "encryption"? (compression)
})

return ModuleHelper.encode_form_data(
{
"eqap": f"dz:{encoded_data}",
"eqacs": data_hash,
"eqaz": "1", # use "encryption"? (compression)
}
)

@staticmethod
def decode_response(response: str) -> str:
# error
if response.startswith("eqwd:"):
return RequestData.chromium_base64_decode(response[5:])

# response not compressed
if not response.startswith("eqz:"):
return response


decoded = RequestData.__decode_data(response[4:])
if decoded is None:
raise Base64DecodeError("Failed to decode response.")
return decoded

return decoded
4 changes: 3 additions & 1 deletion edupage_api/custom_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@


class CustomRequest(Module):
def custom_request(self, url: str, method: str, data: str = "", headers: dict = {}) -> Response:
def custom_request(
self, url: str, method: str, data: str = "", headers: dict = {}
) -> Response:
if method == "GET":
response = self.edupage.session.get(url, headers=headers)
elif method == "POST":
Expand Down
4 changes: 3 additions & 1 deletion edupage_api/dbi.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,6 @@ def fetch_person_data_by_name(self, name: str) -> Optional[dict]:
student_data = self.fetch_student_data_by_name(name)
parent_data = self.fetch_parent_data_by_name(name)

return ModuleHelper.return_first_not_null(teacher_data, student_data, parent_data)
return ModuleHelper.return_first_not_null(
teacher_data, student_data, parent_data
)
8 changes: 7 additions & 1 deletion edupage_api/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,26 @@ class RequestError(Exception):
class InvalidLunchData(Exception):
pass


class Base64DecodeError(Exception):
pass


class InvalidRecipientsException(Exception):
pass


class InvalidChildException(Exception):
pass


class UnknownServerError(Exception):
pass


class NotParentException(Exception):
pass


class SecondFactorFailedException(Exception):
pass
pass
Loading

0 comments on commit 9418918

Please sign in to comment.