mirror of
https://github.com/cqwu-ehall/cqwu-ehall.git
synced 2024-11-25 11:46:46 +00:00
✨ Support get exam calendar
This commit is contained in:
parent
979d575182
commit
99daff1564
@ -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):
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
from .webvpn import *
|
10
cqwu/enums/webvpn.py
Normal file
10
cqwu/enums/webvpn.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
|
class ExamRound(str, Enum):
|
||||||
|
Supplementation = "1"
|
||||||
|
""" 开学补缓考 """
|
||||||
|
Scattered = "2"
|
||||||
|
""" 分散考试 """
|
||||||
|
Concentration = "3"
|
||||||
|
""" 集中考试 """
|
@ -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
9
cqwu/errors/webvpn.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from .base import CQWUEhallError
|
||||||
|
|
||||||
|
|
||||||
|
class CQWUWebVPNError(CQWUEhallError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NoExamData(CQWUWebVPNError):
|
||||||
|
""" 没有检索到对应的考试记录 """
|
@ -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
|
||||||
|
@ -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 = {
|
||||||
|
@ -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"
|
||||||
|
81
cqwu/methods/webvpn/get_exam_calendar.py
Normal file
81
cqwu/methods/webvpn/get_exam_calendar.py
Normal 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
|
17
cqwu/methods/webvpn/login_jwmis.py
Normal file
17
cqwu/methods/webvpn/login_jwmis.py
Normal 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
|
@ -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
36
cqwu/types/exam.py
Normal 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
|
@ -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
|
||||||
|
2
setup.py
2
setup.py
@ -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.", # 包的简述
|
||||||
|
Loading…
Reference in New Issue
Block a user