Support get calendar

Co-authored-by: brian <brian@xtaolabs.com>
This commit is contained in:
brian 2023-03-15 17:31:36 +08:00 committed by GitHub
parent e2a43b1c5d
commit ca794ea619
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 184 additions and 19 deletions

View File

@ -15,22 +15,21 @@ class Client(Methods):
password: str = None,
cookie: str = None,
cookie_file_path: str = "cookie.txt",
client_vpn: bool = False,
):
self.username = username
self.password = password
self.cookie = cookie
self.cookie_file_path = cookie_file_path
if client_vpn:
self.host = "https://clientvpn.cqwu.edu.cn:10443/http/webvpn507e990968de07079b0f10d16c49bdb1cb8d3ca3a4d14f557999e92cbdf19fcd"
self.auth_host = self.host
else:
self.host = "http://ehall.cqwu.edu.cn"
self.auth_host = "http://authserver.cqwu.edu.cn"
self.web_ehall_path = ""
self.cookies = Cookies()
self.request = AsyncClient()
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 为第二学期
@staticmethod
def get_input(word: str = "", is_int: bool = False):

View File

@ -1,6 +1,7 @@
from .auth import Auth
from .epay import EPay
from .users import Users
from .webvpn import WebVPN
from .xg import XG
@ -8,6 +9,7 @@ class Methods(
Auth,
EPay,
Users,
WebVPN,
XG,
):
pass

View File

@ -10,10 +10,12 @@ class LoginWithPassword:
self: "cqwu.Client",
captcha_code: str = None,
show_qrcode: bool = True,
auth_host: str = None,
):
"""
使用学号加密码登录
"""
auth_host = auth_host or self.auth_host
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-Language': 'zh-CN,zh;q=0.9',
@ -22,7 +24,7 @@ class LoginWithPassword:
'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',
}
html = await self.request.get(f"{self.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)
tree = etree.HTML(html.text)
pwd_default_encrypt_salt = tree.xpath('//*[@id="pwdDefaultEncryptSalt"]/@value')[0]
@ -46,18 +48,20 @@ class LoginWithPassword:
'Accept-Language': 'zh-CN,zh;q=0.9',
'Cache-Control': 'max-age=0',
'Connection': 'keep-alive',
'Origin': self.auth_host,
'Referer': f'{self.auth_host}/authserver/login',
'Origin': auth_host,
'Referer': f'{auth_host}/authserver/login',
'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',
}
html = await self.request.post(
f"{self.auth_host}/authserver/login",
f"{auth_host}/authserver/login",
headers=headers,
data=form_data,
follow_redirects=False,
)
if auth_host == self.auth_host:
if 'CASTGC' not in html.cookies.keys():
raise UsernameOrPasswordError
self.cookies.update(html.cookies)
self._use_password_login = True # noqa
self.me = await self.get_me() # noqa

View File

@ -0,0 +1,22 @@
from httpx import URL
from .get_calendar import GetCalendar
from .get_calendar_change import GetCalendarChange
from .login_webvpn import LoginWebVPN
class WebVPN(
GetCalendar,
GetCalendarChange,
LoginWebVPN,
):
@staticmethod
def get_web_vpn_host(url: URL, https: bool = False) -> str:
return next(
(
f"https://clientvpn.cqwu.edu.cn/{'https' if https else 'http'}/{i}"
for i in str(url).split("/")
if i.startswith("webvpn")
),
None,
)

View File

@ -0,0 +1,42 @@
import base64
import cqwu
from cqwu.errors import CookieError
class GetCalendar:
async def get_calendar(
self: "cqwu.Client",
xue_nian: int = None,
xue_qi: int = None,
) -> str:
""" 获取课程表 """
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_host = self.get_web_vpn_host(jw_html.url)
jw_url = f"{jw_host}/cqwljw/student/wsxk.xskcb10319.jsp"
params = {
"params": base64.b64encode(f"xn={xue_nian}&xq={xue_qi}".encode()).decode(),
}
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-Language': 'zh-CN,zh;q=0.9',
'Connection': 'keep-alive',
'Referer': f'{jw_host}/cqwljw/student/xkjg.wdkb.jsp?menucode=S20301',
'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/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-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
jw_html = await self.request.get(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 jw_html.replace("<title></title>", '<meta charset="UTF-8">')

View File

@ -0,0 +1,73 @@
import cqwu
from cqwu.errors import CookieError
def get_in_middle(text: str, start: str, end: str) -> str:
return text.split(start, 1)[1].split(end, 1)[0]
class GetCalendarChange:
async def get_calendar_change(
self: "cqwu.Client",
xue_nian: int = None,
xue_qi: int = None,
) -> str:
""" 获取课程表 """
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_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"
headers = {
'Accept': '*/*',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Connection': 'keep-alive',
'Origin': 'https://clientvpn.cqwu.edu.cn',
'Referer': f'{jw_host}/cqwljw/student/jxap.jxaptzxx.html?menucode=S20302',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'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',
'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-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
params = {
"classPath": "C73E288D0DEA8D7F772BBD7F8FDC7E66F44C9E3992261989ECBAC5A3D722B306C6354658E0F25121E24CED075326C19885F263F369E5CD668E2EEE7CFB7EB5788F202FC6FD7DB0C96FB6995C1DD96ADE84BE3E72CFFBE9EC74FA044498BD2D21EA0439F9DC625F0EF61B7159924C542D577F814848F27128"
}
res = await self.request.post(jw_sg_url, params=params, headers=headers, timeout=60, follow_redirects=True)
data = {
'xh': get_in_middle(res.text, '<xh>', '</xh>'),
'xn': str(xue_nian),
'xq': str(xue_qi),
'xnxq': f'{xue_nian},{xue_qi}',
'menucode_current': 'S20302',
}
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-Language': 'zh-CN,zh;q=0.9',
'Cache-Control': 'max-age=0',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'https://clientvpn.cqwu.edu.cn',
'Referer': f'{jw_host}/cqwljw/student/jxap.jxaptzxx.html?menucode=S20302',
'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/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-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
}
jw_html = await self.request.post(jw_url, data=data, 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>""", "")
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">')

View File

@ -0,0 +1,21 @@
import cqwu
class LoginWebVPN:
async def login_web_vpn(
self: "cqwu.Client",
):
"""
登录 WebVPN
"""
if not self._use_password_login:
return
url = "https://webvpn.cqwu.edu.cn"
ehall_html = await self.request.get(url, follow_redirects=True)
self.web_ehall_path = self.get_web_vpn_host(ehall_html.url) # noqa
await self.oauth("http://authserver.cqwu.edu.cn/authserver/login?service=https://clientvpn.cqwu.edu.cn/enlink/api/client/callback/cas")
auth_html = await self.request.get(
f"{self.web_ehall_path}/login", follow_redirects=True
)
if web_auth_path := self.get_web_vpn_host(auth_html.url):
await self.login_with_password(auth_host=web_auth_path)

View File

@ -9,8 +9,8 @@ from cqwu.errors.auth import CookieError
class GetScore:
async def get_score(
self: "cqwu.Client",
year: int = 2022,
semester: int = 1,
year: int = None,
semester: int = None,
) -> List["types.Score"]:
"""
获取期末成绩
@ -18,6 +18,8 @@ class GetScore:
Returns:
List[types.Score]: 成绩列表
"""
year = year or self.xue_nian
semester = semester or self.xue_qi
url = "http://xg.cqwu.edu.cn/xsfw/sys/zhcptybbapp/*default/index.do#/cjcx"
html = await self.oauth(url)
if not html:

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.3", # 包版本号,便于维护版本
version="0.0.4", # 包版本号,便于维护版本
author="omg-xtao", # 作者,可以写自己的姓名
author_email="xtao@xtaolink.cn", # 作者联系方式,可写自己的邮箱地址
description="A cqwu ehall client.", # 包的简述
@ -18,7 +18,7 @@ setuptools.setup(
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires='>=3.6', # 对python的最低版本要求
python_requires='>=3.8', # 对python的最低版本要求
install_requires=[
"httpx",
"lxml",