Skip to content

Commit

Permalink
Merge pull request #7 from KarimullinArthur/devolop
Browse files Browse the repository at this point in the history
V0.0.5
  • Loading branch information
KarimullinArthur authored Feb 6, 2025
2 parents 1189831 + 5402caa commit 2dafe98
Show file tree
Hide file tree
Showing 10 changed files with 542 additions and 23 deletions.
5 changes: 2 additions & 3 deletions bonchapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
from .bonchapi import *
from .parser import *
from .__meta__ import __version__
from .bonchapi import BonchAPI
from .schemas import Lesson
1 change: 0 additions & 1 deletion bonchapi/__meta__.py

This file was deleted.

54 changes: 40 additions & 14 deletions bonchapi/bonchapi.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import aiohttp
from typing import List

from . import parser
from . import schemas
from bonchapi import schemas


class AuthError(ValueError):
Expand All @@ -13,8 +16,11 @@ def __init__(self, message=None):
class BonchAPI:
@staticmethod
async def get_token() -> str:
URL = "https://lk.sut.ru/cabinet"

async with aiohttp.ClientSession() as session:
async with session.get("https://lk.sut.ru/cabinet/?") as resp:
async with session.get(URL) as resp:
resp.raise_for_status()
token = (
str(resp.cookies.get("miden"))
.split(":")[1]
Expand All @@ -24,31 +30,51 @@ async def get_token() -> str:
return token

async def login(self, mail: str, password: str) -> bool:
URL = "https://lk.sut.ru/cabinet/lib/autentificationok.php"

token = await self.get_token()
AUTH = f"https://lk.sut.ru/cabinet/lib/autentificationok.php?users={mail}&parole={password}"
CABINET = "https://lk.sut.ru/cabinet/"

self.token = await self.get_token()
self.mail = mail
self.password = password
self.cookies = {"miden": token}
payload = {"users": mail, "parole": password}

self.cookies = {"miden": self.token}

async with aiohttp.ClientSession() as session:
async with session.post(URL, cookies=self.cookies, data=payload) as resp:
if await resp.text() == "1":
return True
else:
raise AuthError
async with session.get(CABINET) as resp:
resp.raise_for_status()

async def get_raw_timetable(self):
self.cookies = resp.cookies
self.cookies["miden"] = self.token

async with session.post(AUTH) as resp:
resp.raise_for_status()
text = await resp.text()
if text == '1':
async with session.get(CABINET) as resp:
return True
else:
raise AuthError

async def get_raw_timetable(self, week_number: int = False) -> str:
URL = "https://lk.sut.ru/cabinet/project/cabinet/forms/raspisanie.php"
if week_number:
URL += f"?week={week_number}"

async with aiohttp.ClientSession() as session:
async with session.post(URL, cookies=self.cookies) as resp:
bonch_acess_error_msg = "У Вас нет прав доступа. Или необходимо перезагрузить приложение.."
if await resp.text() == bonch_acess_error_msg:
await self.login(self.mail, self.password)
await self.get_raw_timetable()
await self.get_raw_timetable(week_number)
return await resp.text()

async def get_timetable(self, week_number: int = False, *, week_offset: int = False) -> List[schemas.Lesson]:
if week_offset:
current_week = await parser.get_week(await self.get_raw_timetable(week_number))
desired_week = current_week + week_offset
return await parser.get_my_lessons(await self.get_raw_timetable(desired_week))

else:
return await parser.get_my_lessons(await self.get_raw_timetable(week_number))

async def click_start_lesson(self):
URL = "https://lk.sut.ru/cabinet/project/cabinet/forms/raspisanie.php"
Expand Down
66 changes: 65 additions & 1 deletion bonchapi/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,78 @@

from bs4 import BeautifulSoup

from bonchapi.schemas import Lesson


async def get_week(html_inp):
soup = BeautifulSoup(html_inp, features="lxml")
week = soup.find("h3").text.split("№")[1].split()[0]
return week
return int(week)


async def get_lesson_id(html_inp):
soup = BeautifulSoup(html_inp, features="lxml")
ids = tuple(map(lambda x: x["id"][4:], soup.find_all("span", {"id": re.compile(r"knop+")})))
return ids


async def get_my_lessons(html_inp) -> list[Lesson]:
soup = BeautifulSoup(html_inp, "html.parser")
table = soup.find("table", class_="simple-little-table")

result = []
schedule_data = []
rows = table.find_all("tr")

for row in rows:
cols = row.find_all("td")
if len(cols) == 1:
current_day = cols[0].text.strip()
elif len(cols) > 1:
time = cols[0].text.strip().split()[-1]
number = cols[0].text.strip().split()[0]
subject = cols[1].find("b").text.strip()
lesson_type = cols[1].find("small").text.strip()
location = cols[3].text.strip()
teacher = cols[4].text.strip()

# Динамит вне времени...
if time[0] == "(":
time = cols[0].text.strip().split()[-1][1:-1]

if number == "13.30-15.00":
number = 3

if "-" in str(number):
number = None

# Нажал кнопку начать занятие..
if "началось" in lesson_type.split():
lesson_type = " ".join(
lesson_type.split()[: lesson_type.split().index("началось") - 1]
)

# Понедельник28.10.2024 -> Понедельник 28.10.2024
for char in current_day:
if char in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "0"):
date = current_day[current_day.index(char):]
current_day = current_day[:current_day.index(char)]
break

# date to ISO 8601
date = date.split('.')
date = '-'.join(date[::-1])

schedule_data = {
"date": date,
"day": current_day,
"time": time,
"number": number,
"subject": subject,
"lesson_type": lesson_type,
"location": location,
"teacher": teacher,
}
result.append(Lesson(**schedule_data))

return result
28 changes: 28 additions & 0 deletions bonchapi/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from dataclasses import dataclass
import datetime

from . import validator


@dataclass()
class Lesson(validator.Validations):
date: str
day: str
number: int
time: str
subject: str
lesson_type: str
location: str
teacher: str

def date_to_datetime(self):
self.date = datetime.datetime.strptime(self.date, "%Y-%m-%d")

def validate_number(self, value, **_) -> int:
if isinstance(value, str):
value = int(value)
return value

def __iter__(self):
# return self
return iter([self.date, self.day, self.number, self.time, self.subject, self.lesson_type, self.location, self.teacher])
14 changes: 14 additions & 0 deletions bonchapi/validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class Validations:
def __post_init__(self):
"""Run validation methods if declared.
The validation method can be a simple check
that raises ValueError or a transformation to
the field value.
The validation is performed by calling a function named:
`validate_<field_name>(self, value, field) -> field.type`
"""
for name, field in self.__dataclass_fields__.items():
if (method := getattr(self, f"validate_{name}", None)):
setattr(self, name, method(getattr(self, name), field=field))
100 changes: 100 additions & 0 deletions examples/get_timetable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import asyncio
import argparse
import os

from bonchapi import BonchAPI

import dotenv


dotenv.load_dotenv(dotenv_path="./examples/autoclick/.env")


parser = argparse.ArgumentParser(
prog="bonchcli",
description='What the program does',
epilog='Text at the bottom of help')

parser.add_argument('week_offset', nargs="?", type=int, default=0)
args = parser.parse_args()


async def main():
api = BonchAPI()

api.cookies = {}

mail = str(os.environ.get("mail"))
password = str(os.environ.get("password"))

await api.login(mail, password)

rsp = await api.get_timetable(week_offset=args.week_offset)

week: list[str] = []
state = True
firstly = True
count = 0

for lesson in rsp:
try:
if week[-1] != lesson.date:
week.append(lesson.date)
except IndexError:
week.append(lesson.date)

for lesson in rsp:
if firstly:
firstly = False
state = True
elif lesson.date == week[count]:
state = False
else:
print("╚", "─"*32, sep="")
state = True
count += 1

for arg in lesson:
if arg == lesson.date and state:
print("\t", "\x1b[93;1m", arg, "\x1b[0m")
# print("\t", arg, "\x1b[0m")
elif arg == lesson.day and state:
print("\t", arg, "\n")
elif arg in (lesson.date, lesson.day) and state == False:
pass
elif arg == lesson.number:
if arg is not None:
print("\x1b[93;41m", arg, "\x1b[0m", end='')
else:
print("╔", "─", sep="", end='')
elif arg == lesson.time:
if state:
end = "─"*12
else:
end = ''
print("─"*4, "\x1b[93;5m",arg, "\x1b[0m", "─"*4, end, sep="")

elif arg == lesson.lesson_type:
if lesson.lesson_type == "Лекция":
color = "\x1b[94;5m"
elif lesson.lesson_type == "Практические занятия":
color = "\x1b[92;5m"
elif lesson.lesson_type == "Лабораторная работа":
color = "\x1b[91;5m"
elif lesson.lesson_type == "Консультация":
color = "\x1b[95;5m"
elif lesson.lesson_type in ("Экзамен", "Зачет"):
color = "🕱 \x1b[91;5m"
else:
color = "\x1b[94;5m"
print("│ ", color, arg, "\x1b[0m", sep="")

elif arg == lesson.teacher:
print("│ ", "\x1b[77;1m", arg, "\x1b[0m", sep="")
else:
print('│', arg)


print("╚", "─"*32, sep="")

asyncio.run(main())
Loading

0 comments on commit 2dafe98

Please sign in to comment.