Support get exam calendar

This commit is contained in:
xtaodada 2023-06-01 17:03:36 +08:00
parent 979d575182
commit 99daff1564
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
14 changed files with 172 additions and 17 deletions

View File

@ -29,8 +29,10 @@ class Client(Methods):
self.loop = asyncio.get_event_loop()
self.me: Optional[User] = None
self._use_password_login = False
self.xue_nian = 2022 # 学年
self.xue_qi = 1 # 学期 0 为第一学期, 1 为第二学期
self.xue_nian = 2022
""" 学年 """
self.xue_qi = 1
""" 学期0 为第一学期1 为第二学期 """
@staticmethod
def get_input(word: str = "", is_int: bool = False):

View File

@ -0,0 +1 @@
from .webvpn import *

10
cqwu/enums/webvpn.py Normal file
View File

@ -0,0 +1,10 @@
from enum import Enum
class ExamRound(str, Enum):
Supplementation = "1"
""" 开学补缓考 """
Scattered = "2"
""" 分散考试 """
Concentration = "3"
""" 集中考试 """

View File

@ -1,3 +1,4 @@
from .auth import *
from .base import *
from .epay import *
from .webvpn import *

9
cqwu/errors/webvpn.py Normal file
View File

@ -0,0 +1,9 @@
from .base import CQWUEhallError
class CQWUWebVPNError(CQWUEhallError):
pass
class NoExamData(CQWUWebVPNError):
""" 没有检索到对应的考试记录 """

View File

@ -2,12 +2,16 @@ from httpx import URL
from .get_calendar import GetCalendar
from .get_calendar_change import GetCalendarChange
from .get_exam_calendar import GetExamCalendar
from .login_jwmis import LoginJwmis
from .login_webvpn import LoginWebVPN
class WebVPN(
GetCalendar,
GetCalendarChange,
GetExamCalendar,
LoginJwmis,
LoginWebVPN,
):
@staticmethod

View File

@ -4,7 +4,6 @@ from typing import Tuple, List, Union
from bs4 import BeautifulSoup
import cqwu
from cqwu.errors import CookieError
from cqwu.types.calendar import AiCourse
@ -18,11 +17,7 @@ class GetCalendar:
""" 获取课程表 """
xue_nian = xue_nian or self.xue_nian
xue_qi = xue_qi or self.xue_qi
jw_html = await self.request.get(
f"{self.web_ehall_path}/appShow?appId=5299144291521305", follow_redirects=True
)
if "教学管理服务平台" not in jw_html.text:
raise CookieError
jw_html = await self.login_jwmis()
jw_host = self.get_web_vpn_host(jw_html.url)
jw_url = f"{jw_host}/cqwljw/student/wsxk.xskcb10319.jsp"
params = {

View File

@ -1,5 +1,4 @@
import cqwu
from cqwu.errors import CookieError
def get_in_middle(text: str, start: str, end: str) -> str:
@ -15,11 +14,7 @@ class GetCalendarChange:
""" 获取课程表 """
xue_nian = xue_nian or self.xue_nian
xue_qi = xue_qi or self.xue_qi
jw_html = await self.request.get(
f"{self.web_ehall_path}/appShow?appId=5299144291521305", follow_redirects=True
)
if "教学管理服务平台" not in jw_html.text:
raise CookieError
jw_html = await self.login_jwmis()
jw_host = self.get_web_vpn_host(jw_html.url, https=True)
jw_url = f"{jw_host}/cqwljw/student/jxap.jxaptzxx_rpt.jsp"
jw_sg_url = f"{jw_host}/cqwljw/STU_DynamicInitDataAction.do"

View File

@ -0,0 +1,81 @@
from typing import Union, List
from bs4 import BeautifulSoup
import cqwu
from cqwu.enums import ExamRound
from cqwu.errors import NoExamData
from cqwu.types import AiExam
class GetExamCalendar:
async def get_exam_calendar(
self: "cqwu.Client",
exam_round: Union[str, ExamRound] = ExamRound.Supplementation,
xue_nian: int = None,
xue_qi: int = None,
use_model: bool = False,
) -> Union[str, List[AiExam]]:
""" 获取考试安排表 """
xue_nian = xue_nian or self.xue_nian
xue_qi = xue_qi or self.xue_qi
exam_round = ExamRound(exam_round)
jw_html = await self.login_jwmis()
jw_host = self.get_web_vpn_host(jw_html.url, https=True)
jw_url = f"{jw_host}/cqwljw/student/ksap.ksapb_date.jsp"
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'zh-CN,zh;q=0.9,zh-Hans;q=0.8,und;q=0.7,en;q=0.6,zh-Hant;q=0.5,ja;q=0.4',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded',
'DNT': '1',
'Pragma': 'no-cache',
'Referer': f'{jw_host}/cqwljw/student/ksap.ksapb.html?menucode=S20403',
'Sec-Fetch-Dest': 'iframe',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'same-origin',
'Sec-Fetch-User': '?1',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36',
'sec-ch-ua': '"Chromium";v="112", "Not:A-Brand";v="99"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
data = {
'xn': str(xue_nian),
'xq': str(xue_qi),
'title': '',
'xnxq': f'{xue_nian}{xue_qi}',
'kslc': exam_round.value,
}
jw_html = await self.request.post(jw_url, data=data, headers=headers, timeout=60, follow_redirects=True)
if "没有检索到记录!" in jw_html.text:
raise NoExamData("没有检索到记录!")
jw_html = jw_html.text.replace("""<script type="text/javascript" src="//clientvpn.cqwu.edu.cn/webvpn/bundle.debug.js" charset="utf-8"></script>""", "")
jw_html = jw_html.replace("""<script language='javascript' type='text/javascript' src='../js/Print.js'></script>""", "")
jw_html = jw_html.replace("charset=GBK", 'charset=UTF-8')
if not use_model:
return jw_html
return parse_html(jw_html, exam_round)
def parse_html(html: str, exam_round: ExamRound) -> List[AiExam]:
data: List[AiExam] = []
soup = BeautifulSoup(html, "html.parser")
trs = soup.find_all("tr")[1:]
for tr in trs:
tds = tr.find_all("td")
if len(tds) != 5:
continue
data.append(
AiExam(
name=tds[0].text,
credit=float(tds[1].text),
time=tds[2].text,
position=tds[3].text,
seat=tds[4].text,
exam_round=exam_round,
)
)
return data

View File

@ -0,0 +1,17 @@
from httpx import Response
import cqwu
from cqwu.errors import CookieError
class LoginJwmis:
async def login_jwmis(
self: "cqwu.Client",
) -> Response:
""" 登录教学管理平台 """
jw_html = await self.request.get(
f"{self.web_ehall_path}/appShow?appId=5299144291521305", follow_redirects=True
)
if "教学管理服务平台" not in jw_html.text:
raise CookieError
return jw_html

View File

@ -1,2 +1,6 @@
from .calendar import AiCourse
from .cp import CP, PublicCPRaw, CPGS
from .epay import PayBill, PayBillPage
from .exam import AiExam
from .score import Score
from .user import User

36
cqwu/types/exam.py Normal file
View File

@ -0,0 +1,36 @@
import datetime
from typing import Tuple
from pydantic import BaseModel
from cqwu.enums import ExamRound
class AiExam(BaseModel):
name: str
""" 课程名称 """
credit: float
""" 学分 """
time: str
""" 考试时间 """
position: str
""" 考试地点 """
seat: str
""" 座位号 """
exam_round: ExamRound
""" 考试轮次 """
@property
def name_no_id(self) -> str:
""" 获取课程名称(去除课程编号) """
return self.name.split("]")[-1]
def get_time(self) -> Tuple[datetime.datetime, datetime.datetime]:
""" 获取格式化后的考试时间 """
# 2023-06-25(18周 星期日)09:00-11:00
day = datetime.datetime.strptime(self.time.split("(")[0], "%Y-%m-%d")
start_time = datetime.datetime.strptime(self.time.split(")")[1].split("-")[0], "%H:%M")
start_time = datetime.datetime(day.year, day.month, day.day, start_time.hour, start_time.minute)
end_time = datetime.datetime.strptime(self.time.split(")")[1].split("-")[1], "%H:%M")
end_time = datetime.datetime(day.year, day.month, day.day, end_time.hour, end_time.minute)
return start_time, end_time

View File

@ -1,7 +1,7 @@
httpx==0.23.3
httpx==0.24.1
lxml==4.9.2
PyExecJS2==1.6.1
beautifulsoup4==4.11.2
beautifulsoup4==4.12.2
qrcode==7.4.2
pillow
pydantic==1.10.5

View File

@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
setuptools.setup(
name="cqwu", # 用自己的名替换其中的YOUR_USERNAME_
version="0.0.10", # 包版本号,便于维护版本
version="0.0.11", # 包版本号,便于维护版本
author="omg-xtao", # 作者,可以写自己的姓名
author_email="xtao@xtaolink.cn", # 作者联系方式,可写自己的邮箱地址
description="A cqwu ehall client.", # 包的简述