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.loop = asyncio.get_event_loop()
self.me: Optional[User] = None self.me: Optional[User] = None
self._use_password_login = False self._use_password_login = False
self.xue_nian = 2022 # 学年 self.xue_nian = 2022
self.xue_qi = 1 # 学期 0 为第一学期, 1 为第二学期 """ 学年 """
self.xue_qi = 1
""" 学期0 为第一学期1 为第二学期 """
@staticmethod @staticmethod
def get_input(word: str = "", is_int: bool = False): 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 .auth import *
from .base import * from .base import *
from .epay 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 import GetCalendar
from .get_calendar_change import GetCalendarChange from .get_calendar_change import GetCalendarChange
from .get_exam_calendar import GetExamCalendar
from .login_jwmis import LoginJwmis
from .login_webvpn import LoginWebVPN from .login_webvpn import LoginWebVPN
class WebVPN( class WebVPN(
GetCalendar, GetCalendar,
GetCalendarChange, GetCalendarChange,
GetExamCalendar,
LoginJwmis,
LoginWebVPN, LoginWebVPN,
): ):
@staticmethod @staticmethod

View File

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

View File

@ -1,5 +1,4 @@
import cqwu import cqwu
from cqwu.errors import CookieError
def get_in_middle(text: str, start: str, end: str) -> str: 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_nian = xue_nian or self.xue_nian
xue_qi = xue_qi or self.xue_qi xue_qi = xue_qi or self.xue_qi
jw_html = await self.request.get( jw_html = await self.login_jwmis()
f"{self.web_ehall_path}/appShow?appId=5299144291521305", follow_redirects=True
)
if "教学管理服务平台" not in jw_html.text:
raise CookieError
jw_host = self.get_web_vpn_host(jw_html.url, https=True) jw_host = self.get_web_vpn_host(jw_html.url, https=True)
jw_url = f"{jw_host}/cqwljw/student/jxap.jxaptzxx_rpt.jsp" jw_url = f"{jw_host}/cqwljw/student/jxap.jxaptzxx_rpt.jsp"
jw_sg_url = f"{jw_host}/cqwljw/STU_DynamicInitDataAction.do" 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 .score import Score
from .user import User 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 lxml==4.9.2
PyExecJS2==1.6.1 PyExecJS2==1.6.1
beautifulsoup4==4.11.2 beautifulsoup4==4.12.2
qrcode==7.4.2 qrcode==7.4.2
pillow pillow
pydantic==1.10.5 pydantic==1.10.5

View File

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