From ca794ea6196508e31ec74c90b591fd3ba9144ff8 Mon Sep 17 00:00:00 2001 From: brian <90851827+wobeitaoleshigexuruo@users.noreply.github.com> Date: Wed, 15 Mar 2023 17:31:36 +0800 Subject: [PATCH] :sparkles: Support get calendar Co-authored-by: brian --- cqwu/client.py | 13 ++-- cqwu/methods/__init__.py | 2 + cqwu/methods/auth/login_with_password.py | 20 +++--- cqwu/methods/webvpn/__init__.py | 22 +++++++ cqwu/methods/webvpn/get_calendar.py | 42 +++++++++++++ cqwu/methods/webvpn/get_calendar_change.py | 73 ++++++++++++++++++++++ cqwu/methods/webvpn/login_webvpn.py | 21 +++++++ cqwu/methods/xg/get_score.py | 6 +- setup.py | 4 +- 9 files changed, 184 insertions(+), 19 deletions(-) create mode 100644 cqwu/methods/webvpn/__init__.py create mode 100644 cqwu/methods/webvpn/get_calendar.py create mode 100644 cqwu/methods/webvpn/get_calendar_change.py create mode 100644 cqwu/methods/webvpn/login_webvpn.py diff --git a/cqwu/client.py b/cqwu/client.py index 0ba624c..1f563e1 100644 --- a/cqwu/client.py +++ b/cqwu/client.py @@ -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.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): diff --git a/cqwu/methods/__init__.py b/cqwu/methods/__init__.py index 9509cbc..beb007d 100644 --- a/cqwu/methods/__init__.py +++ b/cqwu/methods/__init__.py @@ -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 diff --git a/cqwu/methods/auth/login_with_password.py b/cqwu/methods/auth/login_with_password.py index 1118638..61f9d77 100644 --- a/cqwu/methods/auth/login_with_password.py +++ b/cqwu/methods/auth/login_with_password.py @@ -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 'CASTGC' not in html.cookies.keys(): - raise UsernameOrPasswordError - self.cookies.update(html.cookies) - self.me = await self.get_me() # noqa + 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 diff --git a/cqwu/methods/webvpn/__init__.py b/cqwu/methods/webvpn/__init__.py new file mode 100644 index 0000000..0c9c088 --- /dev/null +++ b/cqwu/methods/webvpn/__init__.py @@ -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, + ) diff --git a/cqwu/methods/webvpn/get_calendar.py b/cqwu/methods/webvpn/get_calendar.py new file mode 100644 index 0000000..1b1eca3 --- /dev/null +++ b/cqwu/methods/webvpn/get_calendar.py @@ -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("""""", "") + return jw_html.replace("", '') diff --git a/cqwu/methods/webvpn/get_calendar_change.py b/cqwu/methods/webvpn/get_calendar_change.py new file mode 100644 index 0000000..69382ba --- /dev/null +++ b/cqwu/methods/webvpn/get_calendar_change.py @@ -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, '', ''), + '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("""""", "") + jw_html = jw_html.replace("""""", "") + return jw_html.replace("", '') diff --git a/cqwu/methods/webvpn/login_webvpn.py b/cqwu/methods/webvpn/login_webvpn.py new file mode 100644 index 0000000..9aa3cde --- /dev/null +++ b/cqwu/methods/webvpn/login_webvpn.py @@ -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) diff --git a/cqwu/methods/xg/get_score.py b/cqwu/methods/xg/get_score.py index dce5f51..3048b9f 100644 --- a/cqwu/methods/xg/get_score.py +++ b/cqwu/methods/xg/get_score.py @@ -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: diff --git a/setup.py b/setup.py index e568133..2ddaba0 100644 --- a/setup.py +++ b/setup.py @@ -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",