Support get exam action

This commit is contained in:
xtaodada 2023-12-29 18:04:00 +08:00
parent e79b7dc429
commit 23407d68ff
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
32 changed files with 441 additions and 281 deletions

View File

@ -1,17 +1,9 @@
from enum import Enum from enum import Enum
class ExamRound(str, Enum):
Supplementation = "1"
""" 开学补缓考 """
Scattered = "2"
""" 分散考试 """
Concentration = "3"
""" 集中考试 """
class ScoreSearchType(str, Enum): class ScoreSearchType(str, Enum):
""" 成绩查询类型 """ """成绩查询类型"""
All = "1" All = "1"
"""入学以来""" """入学以来"""
XUENIAN = "2" XUENIAN = "2"

View File

@ -6,14 +6,15 @@ class AuthError(CQWUEhallError):
class UsernameOrPasswordError(AuthError): class UsernameOrPasswordError(AuthError):
""" 用户名或密码错误 """ """用户名或密码错误"""
class CookieError(AuthError): class CookieError(AuthError):
""" Cookie 失效 """ """Cookie 失效"""
class NeedCaptchaError(AuthError): class NeedCaptchaError(AuthError):
""" 需要验证码才能登录 """ """需要验证码才能登录"""
def __init__(self, captcha: bytes): def __init__(self, captcha: bytes):
self.captcha = captcha self.captcha = captcha

View File

@ -1,3 +1,4 @@
class CQWUEhallError(Exception): class CQWUEhallError(Exception):
"""Base class for exceptions in this module.""" """Base class for exceptions in this module."""
pass pass

View File

@ -6,8 +6,8 @@ class CQWUWebVPNError(CQWUEhallError):
class NoExamData(CQWUWebVPNError): class NoExamData(CQWUWebVPNError):
""" 没有检索到对应的考试记录 """ """没有检索到对应的考试记录"""
class NoScoreDetailData(CQWUWebVPNError): class NoScoreDetailData(CQWUWebVPNError):
""" 没有检索到对应的成绩明细记录 """ """没有检索到对应的成绩明细记录"""

View File

@ -14,6 +14,6 @@ class Auth(
LoginWithCookie, LoginWithCookie,
LoginWithCookieFile, LoginWithCookieFile,
LoginWithPassword, LoginWithPassword,
Oauth Oauth,
): ):
pass pass

View File

@ -10,21 +10,23 @@ class CheckCaptcha:
username: int = None, username: int = None,
show_qrcode: bool = True, show_qrcode: bool = True,
): ):
""" 检查是否需要验证码 """ """检查是否需要验证码"""
username = username or self.username username = username or self.username
params = { params = {
"username": username, "username": username,
"pwdEncrypt2": "pwdEncryptSalt", "pwdEncrypt2": "pwdEncryptSalt",
"_": str(round(time.time() * 1000)) "_": str(round(time.time() * 1000)),
} }
url = f"{self.auth_host}/authserver/needCaptcha.html" url = f"{self.auth_host}/authserver/needCaptcha.html"
captcha_html = await self.request.get(url, params=params, follow_redirects=False) captcha_html = await self.request.get(
if captcha_html.text == 'true': url, params=params, follow_redirects=False
params = { )
"ts": str(round(time.time())) if captcha_html.text == "true":
} params = {"ts": str(round(time.time()))}
captcha_url = f"{self.auth_host}/authserver/captcha.html" captcha_url = f"{self.auth_host}/authserver/captcha.html"
res = await self.request.get(captcha_url, params=params, follow_redirects=False) res = await self.request.get(
captcha_url, params=params, follow_redirects=False
)
if not show_qrcode: if not show_qrcode:
raise NeedCaptchaError(res.content) raise NeedCaptchaError(res.content)
with open("captcha.jpg", mode="wb") as f: with open("captcha.jpg", mode="wb") as f:

View File

@ -9,7 +9,7 @@ class Login:
async def login( async def login(
self: "cqwu.Client", self: "cqwu.Client",
): ):
""" 登录 """ """登录"""
with contextlib.suppress(CookieError): with contextlib.suppress(CookieError):
if self.cookie: if self.cookie:
await self.login_with_cookie() await self.login_with_cookie()

View File

@ -17,47 +17,51 @@ class LoginWithPassword:
""" """
auth_host = auth_host or self.auth_host auth_host = auth_host or self.auth_host
headers = { headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
'Accept-Language': 'zh-CN,zh;q=0.9', "Accept-Language": "zh-CN,zh;q=0.9",
'Cache-Control': 'max-age=0', "Cache-Control": "max-age=0",
'Connection': 'keep-alive', "Connection": "keep-alive",
'Upgrade-Insecure-Requests': '1', "Upgrade-Insecure-Requests": "1",
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63', "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63",
} }
html = await self.request.get(f"{auth_host}/authserver/login", headers=headers, follow_redirects=True) html = await self.request.get(
f"{auth_host}/authserver/login", headers=headers, follow_redirects=True
)
self.cookies.update(html.cookies) self.cookies.update(html.cookies)
tree = etree.HTML(html.text) tree = etree.HTML(html.text)
try: try:
pwd_default_encrypt_salt = tree.xpath('//*[@id="pwdDefaultEncryptSalt"]/@value')[0] pwd_default_encrypt_salt = tree.xpath(
'//*[@id="pwdDefaultEncryptSalt"]/@value'
)[0]
except IndexError: except IndexError:
if auth_host == self.auth_host: if auth_host == self.auth_host:
self._use_password_login = True # noqa self._use_password_login = True # noqa
self.me = await self.get_me() # noqa self.me = await self.get_me() # noqa
return return
form_data = { form_data = {
'username': str(self.username), "username": str(self.username),
'password': encode_password(self.password, pwd_default_encrypt_salt), "password": encode_password(self.password, pwd_default_encrypt_salt),
'lt': tree.xpath('//*[@id="casLoginForm"]/input[1]/@value')[0], "lt": tree.xpath('//*[@id="casLoginForm"]/input[1]/@value')[0],
'dllt': tree.xpath('//*[@id="casLoginForm"]/input[2]/@value')[0], "dllt": tree.xpath('//*[@id="casLoginForm"]/input[2]/@value')[0],
'execution': tree.xpath('//*[@id="casLoginForm"]/input[3]/@value')[0], "execution": tree.xpath('//*[@id="casLoginForm"]/input[3]/@value')[0],
'_eventId': tree.xpath('//*[@id="casLoginForm"]/input[4]/@value')[0], "_eventId": tree.xpath('//*[@id="casLoginForm"]/input[4]/@value')[0],
'rmShown': tree.xpath('//*[@id="casLoginForm"]/input[5]/@value')[0] "rmShown": tree.xpath('//*[@id="casLoginForm"]/input[5]/@value')[0],
} }
# 是否需要验证码 # 是否需要验证码
if not captcha_code: if not captcha_code:
captcha_code = await self.check_captcha(show_qrcode=show_qrcode) captcha_code = await self.check_captcha(show_qrcode=show_qrcode)
if captcha_code: if captcha_code:
form_data['captchaResponse'] = captcha_code form_data["captchaResponse"] = captcha_code
# 登录 # 登录
headers = { headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
'Accept-Language': 'zh-CN,zh;q=0.9', "Accept-Language": "zh-CN,zh;q=0.9",
'Cache-Control': 'max-age=0', "Cache-Control": "max-age=0",
'Connection': 'keep-alive', "Connection": "keep-alive",
'Origin': auth_host, "Origin": auth_host,
'Referer': f'{auth_host}/authserver/login', "Referer": f"{auth_host}/authserver/login",
'Upgrade-Insecure-Requests': '1', "Upgrade-Insecure-Requests": "1",
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63', "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63",
} }
html = await self.request.post( html = await self.request.post(
f"{auth_host}/authserver/login", f"{auth_host}/authserver/login",
@ -66,7 +70,7 @@ class LoginWithPassword:
follow_redirects=False, follow_redirects=False,
) )
if auth_host == self.auth_host: if auth_host == self.auth_host:
if 'CASTGC' not in html.cookies.keys(): if "CASTGC" not in html.cookies.keys():
raise UsernameOrPasswordError raise UsernameOrPasswordError
self.cookies.update(html.cookies) self.cookies.update(html.cookies)
self._use_password_login = True # noqa self._use_password_login = True # noqa

View File

@ -22,6 +22,6 @@ class GetPayBill:
raise CookieError() raise CookieError()
data = await self.request.post( data = await self.request.post(
"http://218.194.176.214:8382/epay/thirdapp/loadbill.json", "http://218.194.176.214:8382/epay/thirdapp/loadbill.json",
data={"pageno": page_number} data={"pageno": page_number},
) )
return PayBillPage(**data.json()) return PayBillPage(**data.json())

View File

@ -19,7 +19,10 @@ class Pay(
html = await self.request.get(url, follow_redirects=False) html = await self.request.get(url, follow_redirects=False)
if html.status_code == 302: if html.status_code == 302:
location = html.headers["location"] location = html.headers["location"]
params = {i.split("=")[0]: i.split("=")[1] for i in location.split("?")[1].split("&")} params = {
i.split("=")[0]: i.split("=")[1]
for i in location.split("?")[1].split("&")
}
self._pay_x_token = params["token"] self._pay_x_token = params["token"]
@property @property

View File

@ -18,8 +18,10 @@ class GetPayProjectDetail:
""" """
if not self._pay_x_token: if not self._pay_x_token:
await self._oauth_pay() await self._oauth_pay()
url = (f"https://pay.cqwu.edu.cn/api/pay/web/tuitionAndDorm/getTuitionAndDormList/" url = (
f"{self.username}/{project_id}") f"https://pay.cqwu.edu.cn/api/pay/web/tuitionAndDorm/getTuitionAndDormList/"
f"{self.username}/{project_id}"
)
html = await self.request.get(url, headers=self.pay_headers) html = await self.request.get(url, headers=self.pay_headers)
if html.status_code != 200: if html.status_code != 200:
raise CookieError() raise CookieError()

View File

@ -1,7 +1,5 @@
from .get_me import GetMe from .get_me import GetMe
class Users( class Users(GetMe):
GetMe
):
pass pass

View File

@ -6,7 +6,9 @@ from cqwu import types
from cqwu.errors.auth import CookieError from cqwu.errors.auth import CookieError
def get_value_from_soup(soup: BeautifulSoup, attr_id: str) -> Union[type(None), str, int]: def get_value_from_soup(
soup: BeautifulSoup, attr_id: str
) -> Union[type(None), str, int]:
try: try:
data = soup.find("input", attrs={"id": attr_id})["value"] data = soup.find("input", attrs={"id": attr_id})["value"]
try: try:
@ -53,7 +55,9 @@ class GetMe:
temp = {key: get_value_from_soup(soup, value) for key, value in data.items()} temp = {key: get_value_from_soup(soup, value) for key, value in data.items()}
temp["password"] = self.password temp["password"] = self.password
try: try:
temp["specialty"] = soup.find_all("input", attrs={"id": "detail_xy"})[1]["value"] temp["specialty"] = soup.find_all("input", attrs={"id": "detail_xy"})[1][
"value"
]
except (ValueError, TypeError, KeyError, IndexError): except (ValueError, TypeError, KeyError, IndexError):
temp["specialty"] = None temp["specialty"] = None
return types.User(**temp) return types.User(**temp)

View File

@ -3,6 +3,7 @@ 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 .get_exam_calendar import GetExamCalendar
from .get_exam_calendar_action import GetExamCalendarAction
from .get_score_detail import GetScoreDetail from .get_score_detail import GetScoreDetail
from .get_selected_courses import GetSelectedCourses from .get_selected_courses import GetSelectedCourses
from .login_jwmis import LoginJwmis from .login_jwmis import LoginJwmis
@ -13,6 +14,7 @@ class WebVPN(
GetCalendar, GetCalendar,
GetCalendarChange, GetCalendarChange,
GetExamCalendar, GetExamCalendar,
GetExamCalendarAction,
GetScoreDetail, GetScoreDetail,
GetSelectedCourses, GetSelectedCourses,
LoginJwmis, LoginJwmis,

View File

@ -14,7 +14,7 @@ class GetCalendar:
xue_qi: int = None, xue_qi: int = None,
use_model: bool = False, use_model: bool = False,
) -> Union[str, List[AiCourse]]: ) -> Union[str, List[AiCourse]]:
""" 获取课程表 """ """获取课程表"""
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.login_jwmis() jw_html = await self.login_jwmis()
@ -24,22 +24,27 @@ class GetCalendar:
"params": base64.b64encode(f"xn={xue_nian}&xq={xue_qi}".encode()).decode(), "params": base64.b64encode(f"xn={xue_nian}&xq={xue_qi}".encode()).decode(),
} }
headers = { headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
'Accept-Language': 'zh-CN,zh;q=0.9', "Accept-Language": "zh-CN,zh;q=0.9",
'Connection': 'keep-alive', "Connection": "keep-alive",
'Referer': f'{jw_host}/cqwljw/student/xkjg.wdkb.jsp?menucode=S20301', "Referer": f"{jw_host}/cqwljw/student/xkjg.wdkb.jsp?menucode=S20301",
'Sec-Fetch-Dest': 'iframe', "Sec-Fetch-Dest": "iframe",
'Sec-Fetch-Mode': 'navigate', "Sec-Fetch-Mode": "navigate",
'Sec-Fetch-Site': 'same-origin', "Sec-Fetch-Site": "same-origin",
'Sec-Fetch-User': '?1', "Sec-Fetch-User": "?1",
'Upgrade-Insecure-Requests': '1', "Upgrade-Insecure-Requests": "1",
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.41', "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.41",
'sec-ch-ua': '"Microsoft Edge";v="111", "Not(A:Brand";v="8", "Chromium";v="111"', "sec-ch-ua": '"Microsoft Edge";v="111", "Not(A:Brand";v="8", "Chromium";v="111"',
'sec-ch-ua-mobile': '?0', "sec-ch-ua-mobile": "?0",
'sec-ch-ua-platform': '"Windows"', "sec-ch-ua-platform": '"Windows"',
} }
jw_html = await self.request.get(jw_url, params=params, headers=headers, timeout=60, follow_redirects=True) jw_html = await self.request.get(
jw_html = jw_html.text.replace("""<script type="text/javascript" src="//clientvpn.cqwu.edu.cn/webvpn/bundle.debug.js" charset="utf-8"></script>""", "") jw_url, params=params, headers=headers, timeout=60, follow_redirects=True
)
jw_html = jw_html.text.replace(
"""<script type="text/javascript" src="//clientvpn.cqwu.edu.cn/webvpn/bundle.debug.js" charset="utf-8"></script>""",
"",
)
return ( return (
parse_courses(jw_html) parse_courses(jw_html)
if use_model if use_model

View File

@ -11,7 +11,7 @@ class GetCalendarChange:
xue_nian: int = None, xue_nian: int = None,
xue_qi: int = None, xue_qi: int = None,
) -> str: ) -> str:
""" 获取课程表 """ """获取课程表"""
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.login_jwmis() jw_html = await self.login_jwmis()
@ -19,50 +19,60 @@ class GetCalendarChange:
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"
headers = { headers = {
'Accept': '*/*', "Accept": "*/*",
'Accept-Language': 'zh-CN,zh;q=0.9', "Accept-Language": "zh-CN,zh;q=0.9",
'Connection': 'keep-alive', "Connection": "keep-alive",
'Origin': 'https://clientvpn.cqwu.edu.cn', "Origin": "https://clientvpn.cqwu.edu.cn",
'Referer': f'{jw_host}/cqwljw/student/jxap.jxaptzxx.html?menucode=S20302', "Referer": f"{jw_host}/cqwljw/student/jxap.jxaptzxx.html?menucode=S20302",
'Sec-Fetch-Dest': 'empty', "Sec-Fetch-Dest": "empty",
'Sec-Fetch-Mode': 'cors', "Sec-Fetch-Mode": "cors",
'Sec-Fetch-Site': 'same-origin', "Sec-Fetch-Site": "same-origin",
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.41', "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.41",
'content-type': 'application/x-www-form-urlencoded', "content-type": "application/x-www-form-urlencoded",
'sec-ch-ua': '"Microsoft Edge";v="111", "Not(A:Brand";v="8", "Chromium";v="111"', "sec-ch-ua": '"Microsoft Edge";v="111", "Not(A:Brand";v="8", "Chromium";v="111"',
'sec-ch-ua-mobile': '?0', "sec-ch-ua-mobile": "?0",
'sec-ch-ua-platform': '"Windows"', "sec-ch-ua-platform": '"Windows"',
} }
params = { params = {
"classPath": "C73E288D0DEA8D7F772BBD7F8FDC7E66F44C9E3992261989ECBAC5A3D722B306C6354658E0F25121E24CED075326C19885F263F369E5CD668E2EEE7CFB7EB5788F202FC6FD7DB0C96FB6995C1DD96ADE84BE3E72CFFBE9EC74FA044498BD2D21EA0439F9DC625F0EF61B7159924C542D577F814848F27128" "classPath": "C73E288D0DEA8D7F772BBD7F8FDC7E66F44C9E3992261989ECBAC5A3D722B306C6354658E0F25121E24CED075326C19885F263F369E5CD668E2EEE7CFB7EB5788F202FC6FD7DB0C96FB6995C1DD96ADE84BE3E72CFFBE9EC74FA044498BD2D21EA0439F9DC625F0EF61B7159924C542D577F814848F27128"
} }
res = await self.request.post(jw_sg_url, params=params, headers=headers, timeout=60, follow_redirects=True) res = await self.request.post(
jw_sg_url, params=params, headers=headers, timeout=60, follow_redirects=True
)
data = { data = {
'xh': get_in_middle(res.text, '<xh>', '</xh>'), "xh": get_in_middle(res.text, "<xh>", "</xh>"),
'xn': str(xue_nian), "xn": str(xue_nian),
'xq': str(xue_qi), "xq": str(xue_qi),
'xnxq': f'{xue_nian},{xue_qi}', "xnxq": f"{xue_nian},{xue_qi}",
'menucode_current': 'S20302', "menucode_current": "S20302",
} }
headers = { headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
'Accept-Language': 'zh-CN,zh;q=0.9', "Accept-Language": "zh-CN,zh;q=0.9",
'Cache-Control': 'max-age=0', "Cache-Control": "max-age=0",
'Connection': 'keep-alive', "Connection": "keep-alive",
'Content-Type': 'application/x-www-form-urlencoded', "Content-Type": "application/x-www-form-urlencoded",
'Origin': 'https://clientvpn.cqwu.edu.cn', "Origin": "https://clientvpn.cqwu.edu.cn",
'Referer': f'{jw_host}/cqwljw/student/jxap.jxaptzxx.html?menucode=S20302', "Referer": f"{jw_host}/cqwljw/student/jxap.jxaptzxx.html?menucode=S20302",
'Sec-Fetch-Dest': 'iframe', "Sec-Fetch-Dest": "iframe",
'Sec-Fetch-Mode': 'navigate', "Sec-Fetch-Mode": "navigate",
'Sec-Fetch-Site': 'same-origin', "Sec-Fetch-Site": "same-origin",
'Sec-Fetch-User': '?1', "Sec-Fetch-User": "?1",
'Upgrade-Insecure-Requests': '1', "Upgrade-Insecure-Requests": "1",
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.41', "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.41",
'sec-ch-ua': '"Microsoft Edge";v="111", "Not(A:Brand";v="8", "Chromium";v="111"', "sec-ch-ua": '"Microsoft Edge";v="111", "Not(A:Brand";v="8", "Chromium";v="111"',
'sec-ch-ua-mobile': '?0', "sec-ch-ua-mobile": "?0",
'sec-ch-ua-platform': '"Windows"', "sec-ch-ua-platform": '"Windows"',
} }
jw_html = await self.request.post(jw_url, data=data, headers=headers, timeout=60, follow_redirects=True) jw_html = await self.request.post(
jw_html = jw_html.text.replace("""<script type="text/javascript" src="//clientvpn.cqwu.edu.cn/webvpn/bundle.debug.js" charset="utf-8"></script>""", "") jw_url, data=data, headers=headers, timeout=60, follow_redirects=True
jw_html = jw_html.replace("""<script language='javascript' type='text/javascript' src='../js/Print.js'></script>""", "") )
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>""",
"",
)
return jw_html.replace("<title></title>", '<meta charset="UTF-8">') return jw_html.replace("<title></title>", '<meta charset="UTF-8">')

View File

@ -3,7 +3,6 @@ from typing import Union, List
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import cqwu import cqwu
from cqwu.enums import ExamRound
from cqwu.errors import NoExamData from cqwu.errors import NoExamData
from cqwu.types import AiExam from cqwu.types import AiExam
@ -11,56 +10,63 @@ from cqwu.types import AiExam
class GetExamCalendar: class GetExamCalendar:
async def get_exam_calendar( async def get_exam_calendar(
self: "cqwu.Client", self: "cqwu.Client",
exam_round: Union[str, ExamRound] = ExamRound.Supplementation, exam_round: str = "1",
xue_nian: int = None, xue_nian: int = None,
xue_qi: int = None, xue_qi: int = None,
use_model: bool = False, use_model: bool = False,
) -> Union[str, List[AiExam]]: ) -> Union[str, List[AiExam]]:
""" 获取考试安排表 """ """获取考试安排表"""
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
exam_round = ExamRound(exam_round)
jw_html = await self.login_jwmis() jw_html = await self.login_jwmis()
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/ksap.ksapb_date.jsp" jw_url = f"{jw_host}/cqwljw/student/ksap.ksapb_date.jsp"
headers = { 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": "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', "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', "Cache-Control": "no-cache",
'Connection': 'keep-alive', "Connection": "keep-alive",
'Content-Type': 'application/x-www-form-urlencoded', "Content-Type": "application/x-www-form-urlencoded",
'DNT': '1', "DNT": "1",
'Pragma': 'no-cache', "Pragma": "no-cache",
'Referer': f'{jw_host}/cqwljw/student/ksap.ksapb.html?menucode=S20403', "Referer": f"{jw_host}/cqwljw/student/ksap.ksapb.html?menucode=S20403",
'Sec-Fetch-Dest': 'iframe', "Sec-Fetch-Dest": "iframe",
'Sec-Fetch-Mode': 'navigate', "Sec-Fetch-Mode": "navigate",
'Sec-Fetch-Site': 'same-origin', "Sec-Fetch-Site": "same-origin",
'Sec-Fetch-User': '?1', "Sec-Fetch-User": "?1",
'Upgrade-Insecure-Requests': '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', "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": '"Chromium";v="112", "Not:A-Brand";v="99"',
'sec-ch-ua-mobile': '?0', "sec-ch-ua-mobile": "?0",
'sec-ch-ua-platform': '"Windows"', "sec-ch-ua-platform": '"Windows"',
} }
data = { data = {
'xn': str(xue_nian), "xn": str(xue_nian),
'xq': str(xue_qi), "xq": str(xue_qi),
'title': '', "title": "",
'xnxq': f'{xue_nian}{xue_qi}', "xnxq": f"{xue_nian}{xue_qi}",
'kslc': exam_round.value, "kslc": exam_round,
} }
jw_html = await self.request.post(jw_url, data=data, headers=headers, timeout=60, follow_redirects=True) jw_html = await self.request.post(
jw_url, data=data, headers=headers, timeout=60, follow_redirects=True
)
if "没有检索到记录!" in jw_html.text: if "没有检索到记录!" in jw_html.text:
raise NoExamData("没有检索到记录!") 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.text.replace(
jw_html = jw_html.replace("""<script language='javascript' type='text/javascript' src='../js/Print.js'></script>""", "") """<script type="text/javascript" src="//clientvpn.cqwu.edu.cn/webvpn/bundle.debug.js" charset="utf-8"></script>""",
jw_html = jw_html.replace("charset=GBK", 'charset=UTF-8') "",
)
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: if not use_model:
return jw_html return jw_html
return parse_html(jw_html, exam_round) return parse_html(jw_html, exam_round)
def parse_html(html: str, exam_round: ExamRound) -> List[AiExam]: def parse_html(html: str, exam_round: str) -> List[AiExam]:
data: List[AiExam] = [] data: List[AiExam] = []
soup = BeautifulSoup(html, "html.parser") soup = BeautifulSoup(html, "html.parser")
trs = soup.find_all("tr")[1:] trs = soup.find_all("tr")[1:]

View File

@ -0,0 +1,64 @@
from json import JSONDecodeError
import cqwu
from cqwu.types.exam import ExamType
class GetExamCalendarAction:
async def get_exam_calendar_action(
self: "cqwu.Client",
xue_nian: int = None,
xue_qi: int = None,
) -> ExamType:
"""获取考试安排可用类型"""
xue_nian = xue_nian or self.xue_nian
xue_qi = xue_qi or self.xue_qi
jw_html = await self.login_jwmis()
jw_host = self.get_web_vpn_host(jw_html.url, https=True)
jw_url = f"{jw_host}/cqwljw/frame/droplist/getDropLists.action"
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 = {
"comboBoxName": "Ms_KSSW_FBKSLC",
"paramValue": f"xtdm=jw&zxtdm=7&kgmc=kw_fbksap&xnxq={xue_nian}{xue_qi}",
"isYXB": "0",
"isCDDW": "0",
"isXQ": "0",
"isDJKSLB": "0",
"isZY": "0",
}
jw_html = await self.request.post(
jw_url, data=data, headers=headers, timeout=60, follow_redirects=True
)
data = ExamType()
try:
jw_data = jw_html.json()
for i in jw_data:
if "开学补缓考" in i["name"]:
data.supplementation = i["code"]
elif "毕业年级考试" in i["name"]:
data.graduate = i["code"]
elif "分散考试" in i["name"]:
data.scattered = i["code"]
elif "集中考试" in i["name"]:
data.concentration = i["code"]
except JSONDecodeError:
pass
return data

View File

@ -16,7 +16,7 @@ class GetScoreDetail:
xue_qi: int = None, xue_qi: int = None,
use_model: bool = False, use_model: bool = False,
) -> Union[str, ScoreDetail]: ) -> Union[str, ScoreDetail]:
""" 获取学业成绩 """ """获取学业成绩"""
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
search_type = ScoreSearchType(search_type) search_type = ScoreSearchType(search_type)
@ -24,23 +24,23 @@ class GetScoreDetail:
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/xscj.stuckcj_data.jsp" jw_url = f"{jw_host}/cqwljw/student/xscj.stuckcj_data.jsp"
headers = { 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": "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', "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', "Cache-Control": "no-cache",
'Connection': 'keep-alive', "Connection": "keep-alive",
'Content-Type': 'application/x-www-form-urlencoded', "Content-Type": "application/x-www-form-urlencoded",
'DNT': '1', "DNT": "1",
'Pragma': 'no-cache', "Pragma": "no-cache",
'Referer': f'{jw_host}/cqwljw/student/ksap.ksapb.html?menucode=S20403', "Referer": f"{jw_host}/cqwljw/student/ksap.ksapb.html?menucode=S20403",
'Sec-Fetch-Dest': 'iframe', "Sec-Fetch-Dest": "iframe",
'Sec-Fetch-Mode': 'navigate', "Sec-Fetch-Mode": "navigate",
'Sec-Fetch-Site': 'same-origin', "Sec-Fetch-Site": "same-origin",
'Sec-Fetch-User': '?1', "Sec-Fetch-User": "?1",
'Upgrade-Insecure-Requests': '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', "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": '"Chromium";v="112", "Not:A-Brand";v="99"',
'sec-ch-ua-mobile': '?0', "sec-ch-ua-mobile": "?0",
'sec-ch-ua-platform': '"Windows"', "sec-ch-ua-platform": '"Windows"',
} }
data = { data = {
"sjxz": f"sjxz{search_type.value}", "sjxz": f"sjxz{search_type.value}",
@ -50,7 +50,7 @@ class GetScoreDetail:
"btnExport": "%B5%BC%B3%F6", "btnExport": "%B5%BC%B3%F6",
"rxnj": str(xue_nian), "rxnj": str(xue_nian),
"xn": str(xue_nian), "xn": str(xue_nian),
'xn1': str(xue_nian + 1), "xn1": str(xue_nian + 1),
"xq": str(xue_qi), "xq": str(xue_qi),
"ysyxS": "on", "ysyxS": "on",
"sjxzS": "on", "sjxzS": "on",
@ -58,12 +58,20 @@ class GetScoreDetail:
"fxC": "on", "fxC": "on",
"xsjd": "1", "xsjd": "1",
} }
jw_html = await self.request.post(jw_url, data=data, headers=headers, timeout=60, follow_redirects=True) jw_html = await self.request.post(
jw_url, data=data, headers=headers, timeout=60, follow_redirects=True
)
if "没有检索到记录!" in jw_html.text: if "没有检索到记录!" in jw_html.text:
raise NoScoreDetailData("没有检索到记录!") raise NoScoreDetailData("没有检索到记录!")
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.text.replace(
jw_html = jw_html.replace("""<script language='javascript' type='text/javascript' src='../js/Print.js'></script>""", "") """<script type="text/javascript" src="//clientvpn.cqwu.edu.cn/webvpn/bundle.debug.js" charset="utf-8"></script>""",
jw_html = jw_html.replace("charset=GBK", 'charset=UTF-8') "",
)
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: if not use_model:
return jw_html return jw_html
return parse_html(jw_html) return parse_html(jw_html)

View File

@ -12,27 +12,32 @@ class GetSelectedCourses:
self: "cqwu.Client", self: "cqwu.Client",
use_model: bool = False, use_model: bool = False,
) -> Union[str, List[AiCourse]]: ) -> Union[str, List[AiCourse]]:
""" 获取选课结果 """ """获取选课结果"""
jw_html = await self.login_jwmis() jw_html = await self.login_jwmis()
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.zxjg.jsp" jw_url = f"{jw_host}/cqwljw/student/wsxk.zxjg.jsp"
headers = { headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
'Accept-Language': 'zh-CN,zh;q=0.9', "Accept-Language": "zh-CN,zh;q=0.9",
'Connection': 'keep-alive', "Connection": "keep-alive",
'Referer': f'{jw_host}/cqwljw/frame/homes.html', "Referer": f"{jw_host}/cqwljw/frame/homes.html",
'Sec-Fetch-Dest': 'iframe', "Sec-Fetch-Dest": "iframe",
'Sec-Fetch-Mode': 'navigate', "Sec-Fetch-Mode": "navigate",
'Sec-Fetch-Site': 'same-origin', "Sec-Fetch-Site": "same-origin",
'Sec-Fetch-User': '?1', "Sec-Fetch-User": "?1",
'Upgrade-Insecure-Requests': '1', "Upgrade-Insecure-Requests": "1",
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.41', "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.41",
'sec-ch-ua': '"Microsoft Edge";v="111", "Not(A:Brand";v="8", "Chromium";v="111"', "sec-ch-ua": '"Microsoft Edge";v="111", "Not(A:Brand";v="8", "Chromium";v="111"',
'sec-ch-ua-mobile': '?0', "sec-ch-ua-mobile": "?0",
'sec-ch-ua-platform': '"Windows"', "sec-ch-ua-platform": '"Windows"',
} }
jw_html = await self.request.get(jw_url, headers=headers, timeout=60, follow_redirects=True) jw_html = await self.request.get(
jw_html = jw_html.text.replace("""<script type="text/javascript" src="//clientvpn.cqwu.edu.cn/webvpn/bundle.debug.js" charset="utf-8"></script>""", "") jw_url, headers=headers, timeout=60, follow_redirects=True
)
jw_html = jw_html.text.replace(
"""<script type="text/javascript" src="//clientvpn.cqwu.edu.cn/webvpn/bundle.debug.js" charset="utf-8"></script>""",
"",
)
return ( return (
parse_courses(jw_html) parse_courses(jw_html)
if use_model if use_model
@ -57,7 +62,9 @@ def parse_courses(jw_html: str) -> List[AiCourse]:
for calendar in calendars: for calendar in calendars:
text = (BeautifulSoup(calendar, "lxml")).text.strip() text = (BeautifulSoup(calendar, "lxml")).text.strip()
try: try:
position, weeks, day, start_num, sections = parse_weeks_and_sections(text) position, weeks, day, start_num, sections = parse_weeks_and_sections(
text
)
except Exception: except Exception:
continue continue
item = AiCourse( item = AiCourse(

View File

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

View File

@ -13,7 +13,9 @@ class LoginWebVPN:
url = "https://webvpn.cqwu.edu.cn" url = "https://webvpn.cqwu.edu.cn"
ehall_html = await self.request.get(url, follow_redirects=True) ehall_html = await self.request.get(url, follow_redirects=True)
self.web_ehall_path = self.get_web_vpn_host(ehall_html.url) # noqa self.web_ehall_path = self.get_web_vpn_host(ehall_html.url) # noqa
await self.oauth("https://authserver.cqwu.edu.cn/authserver/login?service=https://clientvpn.cqwu.edu.cn/enlink/api/client/callback/cas") await self.oauth(
"https://authserver.cqwu.edu.cn/authserver/login?service=https://clientvpn.cqwu.edu.cn/enlink/api/client/callback/cas"
)
auth_html = await self.request.get( auth_html = await self.request.get(
f"{self.web_ehall_path}/login", follow_redirects=True f"{self.web_ehall_path}/login", follow_redirects=True
) )

View File

@ -22,15 +22,15 @@ class XG(
raise CookieError() raise CookieError()
url = "http://xg.cqwu.edu.cn/xsfw/sys/swpubapp/indexmenu/getAppConfig.do" url = "http://xg.cqwu.edu.cn/xsfw/sys/swpubapp/indexmenu/getAppConfig.do"
headers = { headers = {
'Accept': '*/*', "Accept": "*/*",
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,hu;q=0.5', "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,hu;q=0.5",
'Connection': 'keep-alive', "Connection": "keep-alive",
'Referer': 'http://xg.cqwu.edu.cn/xsfw/sys/zhcptybbapp/*default/index.do', "Referer": "http://xg.cqwu.edu.cn/xsfw/sys/zhcptybbapp/*default/index.do",
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.51', "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.51",
} }
params = { params = {
'appId': '5275772372599202', "appId": "5275772372599202",
'appName': 'zhcptybbapp', "appName": "zhcptybbapp",
'v': '021534151969418724', "v": "021534151969418724",
} }
await self.request.get(url, headers=headers, params=params) await self.request.get(url, headers=headers, params=params)

View File

@ -13,33 +13,33 @@ class GetCP:
year: int = None, year: int = None,
semester: int = None, semester: int = None,
) -> CP: ) -> CP:
""" 获取综合测评结果 """ """获取综合测评结果"""
xue_nian = year or self.xue_nian xue_nian = year or self.xue_nian
xue_qi = semester or self.xue_qi xue_qi = semester or self.xue_qi
await self.oauth_xg() await self.oauth_xg()
url = "http://xg.cqwu.edu.cn/xsfw/sys/emapcomponent/imexport/export.do" url = "http://xg.cqwu.edu.cn/xsfw/sys/emapcomponent/imexport/export.do"
headers = { headers = {
'Accept': 'application/json, text/javascript, */*; q=0.01', "Accept": "application/json, text/javascript, */*; q=0.01",
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,hu;q=0.5', "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,hu;q=0.5",
'Connection': 'keep-alive', "Connection": "keep-alive",
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
'Origin': 'http://xg.cqwu.edu.cn', "Origin": "http://xg.cqwu.edu.cn",
'Referer': 'http://xg.cqwu.edu.cn/xsfw/sys/zhcptybbapp/*default/index.do', "Referer": "http://xg.cqwu.edu.cn/xsfw/sys/zhcptybbapp/*default/index.do",
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.51', "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.51",
'X-Requested-With': 'XMLHttpRequest', "X-Requested-With": "XMLHttpRequest",
} }
data = { data = {
'app': 'zhcptybbapp', "app": "zhcptybbapp",
'contextPath': 'http://xg.cqwu.edu.cn/xsfw', "contextPath": "http://xg.cqwu.edu.cn/xsfw",
'module': 'modules', "module": "modules",
'page': 'cpjgcx', "page": "cpjgcx",
'action': 'cpjgcxbgdz', "action": "cpjgcxbgdz",
'containerId': 'cpjg_grid', "containerId": "cpjg_grid",
'CPXN': str(xue_nian), "CPXN": str(xue_nian),
'CPXQ': str(xue_qi), "CPXQ": str(xue_qi),
'filename': '综合测评结果', "filename": "综合测评结果",
'colnames': 'XH,XM,CPXN,CPXQ,DWDM,DZ_ZYFX,BJDM,ZCJ,BJPM,BJRS,ZYNJPM,ZYNJRS,FS1,FS10,FS11,FS12,' "colnames": "XH,XM,CPXN,CPXQ,DWDM,DZ_ZYFX,BJDM,ZCJ,BJPM,BJRS,ZYNJPM,ZYNJRS,FS1,FS10,FS11,FS12,"
'XZNJ,DZ_BJPM,DZ_ZYPM', "XZNJ,DZ_BJPM,DZ_ZYPM",
} }
html = await self.request.post(url, headers=headers, data=data) html = await self.request.post(url, headers=headers, data=data)
if html.status_code != 200: if html.status_code != 200:

View File

@ -12,25 +12,27 @@ class GetPublicCP:
page_number: int = 1, page_number: int = 1,
total: bool = True, total: bool = True,
) -> List[PublicCPRaw]: ) -> List[PublicCPRaw]:
""" 获取综合测评公示结果 """ """获取综合测评公示结果"""
await self.oauth_xg() await self.oauth_xg()
async def get_public_cp_raw(page_size_: int, page_number_: int) -> CPGS: async def get_public_cp_raw(page_size_: int, page_number_: int) -> CPGS:
url = "http://xg.cqwu.edu.cn/xsfw/sys/zhcptybbapp/modules/cpgs/cpgs_cpgsbg.do" url = (
"http://xg.cqwu.edu.cn/xsfw/sys/zhcptybbapp/modules/cpgs/cpgs_cpgsbg.do"
)
headers = { headers = {
'Accept': 'application/json, text/javascript, */*; q=0.01', "Accept": "application/json, text/javascript, */*; q=0.01",
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,hu;q=0.5', "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,hu;q=0.5",
'Connection': 'keep-alive', "Connection": "keep-alive",
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
'Origin': 'http://xg.cqwu.edu.cn', "Origin": "http://xg.cqwu.edu.cn",
'Referer': 'http://xg.cqwu.edu.cn/xsfw/sys/zhcptybbapp/*default/index.do', "Referer": "http://xg.cqwu.edu.cn/xsfw/sys/zhcptybbapp/*default/index.do",
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.51', "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.51",
'X-Requested-With': 'XMLHttpRequest', "X-Requested-With": "XMLHttpRequest",
} }
data = { data = {
'querySetting': '[]', "querySetting": "[]",
'pageSize': str(page_size_), "pageSize": str(page_size_),
'pageNumber': str(page_number_), "pageNumber": str(page_number_),
} }
html = await self.request.post(url, headers=headers, data=data) html = await self.request.post(url, headers=headers, data=data)
if html.status_code != 200: if html.status_code != 200:
@ -59,7 +61,10 @@ class GetPublicCP:
return_datas.extend(html_raw_datas.rows) return_datas.extend(html_raw_datas.rows)
if html_raw_datas.totalSize == 0: if html_raw_datas.totalSize == 0:
break break
elif html_raw_datas.pageNumber * html_raw_datas.pageSize >= html_raw_datas.totalSize: elif (
html_raw_datas.pageNumber * html_raw_datas.pageSize
>= html_raw_datas.totalSize
):
break break
else: else:
page_number += 1 page_number += 1

View File

@ -7,9 +7,9 @@ from cqwu import types
class GetScore: class GetScore:
async def get_score( async def get_score(
self: "cqwu.Client", self: "cqwu.Client",
year: int = None, year: int = None,
semester: int = None, semester: int = None,
) -> List["types.Score"]: ) -> List["types.Score"]:
""" """
获取期末成绩 获取期末成绩
@ -20,16 +20,18 @@ class GetScore:
year = year or self.xue_nian year = year or self.xue_nian
semester = semester or (self.xue_qi + 1) semester = semester or (self.xue_qi + 1)
await self.oauth_xg() await self.oauth_xg()
query_url = "http://xg.cqwu.edu.cn/xsfw/sys/zhcptybbapp/modules/cjcx/cjcxbgdz.do" query_url = (
"http://xg.cqwu.edu.cn/xsfw/sys/zhcptybbapp/modules/cjcx/cjcxbgdz.do"
)
headers = { headers = {
'Accept': 'application/json, text/javascript, */*; q=0.01', "Accept": "application/json, text/javascript, */*; q=0.01",
'Accept-Language': 'zh-CN,zh;q=0.9', "Accept-Language": "zh-CN,zh;q=0.9",
'Connection': 'keep-alive', "Connection": "keep-alive",
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
'Origin': 'http://xg.cqwu.edu.cn', "Origin": "http://xg.cqwu.edu.cn",
'Referer': 'http://xg.cqwu.edu.cn/xsfw/sys/zhcptybbapp/*default/index.do', "Referer": "http://xg.cqwu.edu.cn/xsfw/sys/zhcptybbapp/*default/index.do",
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63', "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63",
'X-Requested-With': 'XMLHttpRequest', "X-Requested-With": "XMLHttpRequest",
} }
query_data = [ query_data = [
{ {
@ -47,12 +49,12 @@ class GetScore:
"builderList": "cbl_m_List", "builderList": "cbl_m_List",
"builder": "m_value_equal", "builder": "m_value_equal",
"value": str(semester), "value": str(semester),
} },
] ]
data = { data = {
'querySetting': json.dumps(query_data), "querySetting": json.dumps(query_data),
'pageSize': '100', "pageSize": "100",
'pageNumber': '1', "pageNumber": "1",
} }
html = await self.request.post(query_url, headers=headers, data=data) html = await self.request.post(query_url, headers=headers, data=data)
data = [types.Score(**i) for i in html.json()["datas"]["cjcxbgdz"]["rows"]] data = [types.Score(**i) for i in html.json()["datas"]["cjcxbgdz"]["rows"]]

View File

@ -4,5 +4,10 @@ from .epay import PayBill, PayBillPage
from .exam import AiExam from .exam import AiExam
from .pay import PayProject, PayProjectDetail, PayUser from .pay import PayProject, PayProjectDetail, PayUser
from .score import Score from .score import Score
from .score_detail import ScoreDetail, ScoreDetailInfo, ScoreDetailCourse, ScoreDetailTotal from .score_detail import (
ScoreDetail,
ScoreDetailInfo,
ScoreDetailCourse,
ScoreDetailTotal,
)
from .user import User from .user import User

View File

@ -29,5 +29,11 @@ class AiCourse(BaseModel):
"weeks": ",".join(list(map(str, self.weeks))), "weeks": ",".join(list(map(str, self.weeks))),
"day": self.day, "day": self.day,
"style": "", "style": "",
"sections": ",".join(list(map(str, list(range(self.start_num, self.start_num + self.sections))))) "sections": ",".join(
list(
map(
str, list(range(self.start_num, self.start_num + self.sections))
)
)
),
} }

View File

@ -40,77 +40,77 @@ class PublicCPRaw(BaseModel):
@property @property
def id(self) -> int: def id(self) -> int:
""" 学号 """ """学号"""
return int(self.XH) return int(self.XH)
@property @property
def name(self) -> str: def name(self) -> str:
""" 姓名 """ """姓名"""
return self.XM return self.XM
@property @property
def yuan_xi(self) -> str: def yuan_xi(self) -> str:
""" 院系 """ """院系"""
return self.DWDM_DISPLAY return self.DWDM_DISPLAY
@property @property
def zhuan_ye(self) -> str: def zhuan_ye(self) -> str:
""" 专业 """ """专业"""
return self.ZYDM_DISPLAY return self.ZYDM_DISPLAY
@property @property
def class_name(self) -> str: def class_name(self) -> str:
""" 班级 """ """班级"""
return self.BJDM_DISPLAY return self.BJDM_DISPLAY
@property @property
def total_score(self) -> float: def total_score(self) -> float:
""" 总成绩 """ """总成绩"""
return float(self.ZCJ) return float(self.ZCJ)
@property @property
def class_rank(self) -> int: def class_rank(self) -> int:
""" 班级排名 """ """班级排名"""
return int(self.BJPM) return int(self.BJPM)
@property @property
def grade_rank(self) -> int: def grade_rank(self) -> int:
""" 专业年级排名 """ """专业年级排名"""
return int(self.ZYNJPM) return int(self.ZYNJPM)
@property @property
def dysz(self) -> float: def dysz(self) -> float:
""" 德育素质分 """ """德育素质分"""
return float(self.FS1) return float(self.FS1)
@property @property
def zysz(self) -> float: def zysz(self) -> float:
""" 智育素质测评 """ """智育素质测评"""
return float(self.FS10) return float(self.FS10)
@property @property
def cxsz(self) -> float: def cxsz(self) -> float:
""" 创新素质测评 """ """创新素质测评"""
return float(self.FS11) return float(self.FS11)
@property @property
def wtsz(self) -> float: def wtsz(self) -> float:
""" 文体素质 """ """文体素质"""
return float(self.FS12) return float(self.FS12)
@property @property
def dysz_raw(self) -> float: def dysz_raw(self) -> float:
""" 德育原始成绩 """ """德育原始成绩"""
return float(self.DYYSCJ) return float(self.DYYSCJ)
@property @property
def wtsz_raw(self) -> float: def wtsz_raw(self) -> float:
""" 文体原始成绩 """ """文体原始成绩"""
return float(self.WTYSCJ) return float(self.WTYSCJ)
@property @property
def cxsz_raw(self) -> float: def cxsz_raw(self) -> float:
""" 创新原始成绩 """ """创新原始成绩"""
return float(self.CXYSCJ) return float(self.CXYSCJ)

View File

@ -3,7 +3,28 @@ from typing import Tuple
from pydantic import BaseModel from pydantic import BaseModel
from cqwu.enums import ExamRound
class ExamType:
supplementation: str
""" 开学补缓考 """
graduate: str
""" 毕业年级考试 """
scattered: str
""" 分散考试 """
concentration: str
""" 集中考试 """
def __init__(
self,
supplementation: str = "",
graduate: str = "",
scattered: str = "",
concentration: str = "",
):
self.supplementation = supplementation
self.graduate = graduate
self.scattered = scattered
self.concentration = concentration
class AiExam(BaseModel): class AiExam(BaseModel):
@ -17,25 +38,33 @@ class AiExam(BaseModel):
""" 考试地点 """ """ 考试地点 """
seat: str seat: str
""" 座位号 """ """ 座位号 """
exam_round: ExamRound exam_round: str
""" 考试轮次 """ """ 考试轮次 """
@property @property
def name_no_id(self) -> str: def name_no_id(self) -> str:
""" 获取课程名称(去除课程编号) """ """获取课程名称(去除课程编号)"""
return self.name.split("]")[-1] return self.name.split("]")[-1]
@property @property
def days_left(self) -> int: def days_left(self) -> int:
""" 获取距离考试的天数 """ """获取距离考试的天数"""
return (self.get_time()[0] - datetime.datetime.now()).days return (self.get_time()[0] - datetime.datetime.now()).days
def get_time(self) -> Tuple[datetime.datetime, datetime.datetime]: def get_time(self) -> Tuple[datetime.datetime, datetime.datetime]:
""" 获取格式化后的考试时间 """ """获取格式化后的考试时间"""
# 2023-06-25(18周 星期日)09:00-11:00 # 2023-06-25(18周 星期日)09:00-11:00
day = datetime.datetime.strptime(self.time.split("(")[0], "%Y-%m-%d") 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.strptime(
start_time = datetime.datetime(day.year, day.month, day.day, start_time.hour, start_time.minute) self.time.split(")")[1].split("-")[0], "%H:%M"
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) 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 return start_time, end_time

View File

@ -2,7 +2,8 @@ from pydantic import BaseModel
class Score(BaseModel): class Score(BaseModel):
""" 成绩类 """ """成绩类"""
KCMC: str KCMC: str
XF: float XF: float
ZCJ: float ZCJ: float
@ -14,30 +15,30 @@ class Score(BaseModel):
@property @property
def name(self) -> str: def name(self) -> str:
""" 课程名称 """ """课程名称"""
return self.KCMC return self.KCMC
@property @property
def credit(self) -> float: def credit(self) -> float:
""" 学分 """ """学分"""
return self.XF return self.XF
@property @property
def score(self) -> float: def score(self) -> float:
""" 成绩 """ """成绩"""
return self.ZCJ return self.ZCJ
@property @property
def grade_point(self) -> float: def grade_point(self) -> float:
""" 绩点 """ """绩点"""
return float(self.JD) return float(self.JD)
@property @property
def year(self) -> int: def year(self) -> int:
""" 学年 """ """学年"""
return int(self.XN) return int(self.XN)
@property @property
def semester(self) -> int: def semester(self) -> int:
""" 学期 """ """学期"""
return int(self.XQ) return int(self.XQ)

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.17", # 包版本号,便于维护版本 version="0.0.18", # 包版本号,便于维护版本
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.", # 包的简述
@ -18,7 +18,7 @@ setuptools.setup(
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",
"Operating System :: OS Independent", "Operating System :: OS Independent",
], ],
python_requires='>=3.8', # 对python的最低版本要求 python_requires=">=3.8", # 对python的最低版本要求
install_requires=[ install_requires=[
"httpx", "httpx",
"lxml", "lxml",