Skip to content
This repository has been archived by the owner on Sep 22, 2024. It is now read-only.

Commit

Permalink
Merge pull request #10 from nm17/v2.0.0
Browse files Browse the repository at this point in the history
V2.0.0
  • Loading branch information
nm17 authored Sep 11, 2020
2 parents a5d80aa + 8e56233 commit 929e91f
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 86 deletions.
1 change: 1 addition & 0 deletions .bumpversion.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ serialize =
{major}.{minor}.{patch}

[bumpversion:file:setup.py]
[bumpversion:file:netschoolapi/__init__.py]
30 changes: 30 additions & 0 deletions examples/login_form.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from netschoolapi.login_form import LoginForm
import asyncio

# Тест нового /webapi/loginform , спасибо dsolmann за то что помог мне разобраться с ним.


async def main():
url = "https://edu.admoblkaluga.ru:444/"

lf = LoginForm(url)

await lf.get_login_form(
state="Калужская обл",
province="Людиновский район",
city="Букань, с.",
func="Общеобразовательная",
)

assert lf.request_params == {
"CID": 2,
"SID": 122,
"PID": 36,
"CN": 2025,
"SFT": 2,
"SCID": 149,
}
print(lf.request_params)


asyncio.run(main())
7 changes: 4 additions & 3 deletions examples/simple_login.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import trio
import asyncio

from netschoolapi import NetSchoolAPI


async def main():
api = NetSchoolAPI("http://sgo.cit73.ru/")
await api.login("Иван", "Иван555", "МАОУ многопрофильный лицей №20")
await api.login("Иван", "Иван555", school="МАОУ многопрофильный лицей №20")
print(await api.get_announcements())

trio.run(main)

asyncio.run(main())
3 changes: 3 additions & 0 deletions netschoolapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
from netschoolapi.client import NetSchoolAPI
from netschoolapi.login_form import LoginForm

__version__ = "1.5.1"
43 changes: 28 additions & 15 deletions netschoolapi/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@
from datetime import datetime, timedelta
from typing import Optional

import dacite
import dateutil.parser
import httpx
import dacite

from netschoolapi.data import Announcement
from netschoolapi.exceptions import (
WrongCredentialsError,
RateLimitingError,
UnknownServerError,
)
from netschoolapi.utils import LoginForm
from netschoolapi.login_form import LoginForm
from netschoolapi.utils import get_user_agent


class NetSchoolAPI:
Expand All @@ -28,17 +29,17 @@ class NetSchoolAPI:
def __init__(self, url):
self.url = url.rstrip("/")

async def get_form_data(self, for_: Optional[str] = "schools"):
login_data = LoginForm(url=self.url)
return list(map(lambda a: a["name"], (await login_data.login_form_data)[for_]))

async def login(
self,
login: str,
password: str,
school: str,
login_form: Optional[LoginForm] = None,
school: Optional[str] = None,
country: Optional[str] = None,
func: Optional[str] = None,
province: Optional[str] = None,
state: Optional[str] = None,
city: Optional[str] = None,
oo: Optional[str] = None,
):
async with self.session:
await self.session.get(self.url)
Expand All @@ -47,8 +48,16 @@ async def login(
data = resp.json()
lt, ver, salt = data["lt"], data["ver"], data["salt"]

login_data = LoginForm(url=self.url)
await login_data.get_login_data(school=school, city=city, func=oo)
if login_form is None:
login_data = LoginForm(url=self.url)
await login_data.get_login_form(
school=school,
city=city,
func=func,
country=country,
state=state,
province=province,
)

pw2 = hashlib.new(
"md5",
Expand All @@ -61,7 +70,7 @@ async def login(

data = {
"LoginType": "1",
**login_data.__dict__,
**login_data.request_params,
"UN": login,
"PW": pw,
"lt": lt,
Expand Down Expand Up @@ -95,12 +104,14 @@ async def login(
self.url + "/angular/school/studentdiary/",
data={"AT": self.at, "VER": ver},
)
self.user_id = (
int(re.search(r"userId = (\d+)", resp.text, re.U).group(1)) - 2
) # TODO: Investigate this
self.user_id = int(
re.search(r"userId = (\d+)", resp.text, re.U).group(1)
) # Note to self: the -2 thing seems to be fixed.
self.year_id = int(re.search(r'yearId = "(\d+)"', resp.text, re.U).group(1))
self.logged_in = True

self.session.headers["User-Agent"] = get_user_agent()

self.session.headers["at"] = self.at

async def get_diary(
Expand Down Expand Up @@ -142,7 +153,9 @@ async def get_diary_df(
try:
import pandas as pd
except ImportError as err:
raise ModuleNotFoundError("Pandas not installed. Install netschoolapi[tables].") from err
raise ModuleNotFoundError(
"Pandas not installed. Install netschoolapi[tables]."
) from err
resp = await self.get_diary(week_start, week_end)
df = pd.DataFrame()

Expand Down
23 changes: 5 additions & 18 deletions netschoolapi/data.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,17 @@
from dataclasses import dataclass
from datetime import datetime, time
from typing import NamedTuple, List
from typing import List


class Lesson(NamedTuple):
class_meeting_id: str = None
day: datetime = None
@dataclass
class Lesson:
class_meeting_id: str
day: datetime
room: str = None
start_time: time = None
end_time: time = None
subject_name: str = None

# noinspection PyProtectedMember
@staticmethod
def create(data):
lesson = Lesson()
lesson = lesson._replace(
class_meeting_id=data["classmeetingId"]
) # https://stackoverflow.com/a/22562687
lesson = lesson._replace(day=data["day"])
lesson = lesson._replace(room=data["room"])
lesson = lesson._replace(start_time=data["startTime"])
lesson = lesson._replace(end_time=data["endTime"])
lesson = lesson._replace(subject_name=data["subjectName"])
return lesson


@dataclass
class Attachment:
Expand Down
98 changes: 98 additions & 0 deletions netschoolapi/login_form.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from typing import Optional

import httpx
import random

ALL_LOGIN_KWARGS = ["country", "state", "province", "city", "func", "school"]
LOGIN_FORM_QUEUE = {
"countries": "CID",
"states": "SID",
"provinces": "PID",
"cities": "CN",
"funcs": "SFT",
"schools": "SCID",
}


class LoginForm:
SID: int
PID: int
CN: int
SFT: int
CID: int
SCID: int
ECardID = "" # TODO: Remove in later versions?

def __init__(self, url: Optional[str] = None):
self.__url = url
self.__client = None

async def get_prepare_form_data(self) -> dict:
self.__client = self.__client or httpx.AsyncClient()
async with self.__client as client:
resp = await client.get(self.__url.rstrip("/") + "/webapi/prepareloginform")
assert resp.status_code == 200

return resp.json()

async def get_login_data(self, **request_params):
self.__client = self.__client or httpx.AsyncClient()

items = list(LOGIN_FORM_QUEUE.values())
last_name = items[
max(map(lambda a: items.index(a.upper()), request_params.keys()))
].lower()
request_params["cacheVer"] = random.randint(1000000, 100000000)
request_params["LASTNAME"] = last_name
self.__client = self.__client or httpx.AsyncClient()
async with self.__client as client:
resp = await client.get(
self.__url.rstrip("/") + "/webapi/loginform", params=request_params
)

return last_name, resp.json()["items"]

# noinspection PyTypeChecker
@property
def request_params(self):
return dict(filter(lambda a: not a[0].startswith("_"), self.__dict__.items()))

async def get_login_form(self, **login_kwargs):
# TODO: Reorder everything and make it not look ugly without pulling in
# TODO: other libs

prepare_data = await self.get_prepare_form_data()

item_reordered = {
item["name"].strip(): item["id"]
for item in prepare_data[list(LOGIN_FORM_QUEUE.keys())[0]]
}

first_name = list(LOGIN_FORM_QUEUE.values())[0]

setattr(
self,
first_name, # class attribute name
prepare_data[first_name.lower()] # default
if login_kwargs.get(ALL_LOGIN_KWARGS[0], None)
is None # use default if param is none
else item_reordered[login_kwargs[ALL_LOGIN_KWARGS[0]]],
)

for login_arg in ALL_LOGIN_KWARGS[1:]:
last_name, items = await self.get_login_data(**self.request_params)

items = {item["name"].strip(): item["id"] for item in items}

next_name = list(LOGIN_FORM_QUEUE.values())[
list(LOGIN_FORM_QUEUE.values()).index(last_name.upper()) + 1
]

setattr(
self,
next_name, # class attribute name
list(items.values())[0] # default
if login_kwargs.get(login_arg, None)
is None # use default if param is none
else items[login_kwargs[login_arg]],
)
55 changes: 6 additions & 49 deletions netschoolapi/utils.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,8 @@
from typing import Optional
import pkg_resources
import netschoolapi

import httpx


class LoginForm:
CID: int
SID: int
PID: int
CN: int
SFT: int
SCID: int
ECardID = ""

def __init__(self, url):
self.__url = url

@property
async def login_form_data(self) -> dict:
async with httpx.AsyncClient() as client:
resp = await client.get(self.__url.rstrip("/") + "/webapi/prepareloginform")
assert resp.status_code == 200

return resp.json()

async def get_login_data(
self,
school: str,
country: Optional[str] = None,
func: Optional[str] = None,
province: Optional[str] = None,
state: Optional[str] = None,
city: Optional[str] = None,
):
# TODO: Reorder everything
data = await self.login_form_data

countries = {item["name"].strip(): item["id"] for item in data["countries"]}
cities = {item["name"].strip(): item["id"] for item in data["cities"]}
funcs = {item["name"].strip(): item["id"] for item in data["funcs"]}
provinces = {item["name"].strip(): item["id"] for item in data["provinces"]}
schools = {item["name"].strip(): item["id"] for item in data["schools"]}

states = {item["name"].strip(): item["id"] for item in data["states"]}

self.CID = data["cid"] if country is None else countries[country]
self.CN = data["cn"] if city is None else cities[city]
self.PID = data["pid"] if province is None else provinces[province]
self.SFT = data["sft"] if func is None else funcs[func]
self.SID = data["sid"] if state is None else states[state]
self.SCID = data["scid"] if school is None else schools[school]
def get_user_agent():
httpx_version = pkg_resources.get_distribution("httpx").version
api_version = netschoolapi.__version__
return f"httpx/{httpx_version} (NetSchoolAPI/{api_version}; +https://github.com/nm17/netschoolapi)"
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
author_email="[email protected]",
description="A fully asynchronous API wrapper for NetSchool written in Python",
long_description=open("README.md").read(),
install_requires=["httpx", "trio", "python-dateutil", 'dacite'],
install_requires=["httpx", "python-dateutil", 'dacite'],
extras_require={"tables": ["pandas"]},
python_requires=">=3.6",
long_description_content_type='text/markdown',
Expand Down

0 comments on commit 929e91f

Please sign in to comment.