api 源代码

import random
import httpx
import json
import base64
import time
from typing_extensions import Unpack, Tuple

from zzupy.log import logger
from zzupy.typing import DeviceParams
from zzupy.utils import get_sign, _kget
from zzupy.supwisdom import Supwisdom
from zzupy.ecard import eCard
from zzupy.network import Network


[文档] class ZZUPy: def __init__(self, usercode: str, password: str, log: bool = False): """ 初始化一个 ZZUPy 对象 :param str usercode: 学号 :param str password: 密码 :param bool log: 是否启用日志 """ self._userToken = None self._dynamicSecret = "supwisdom_eams_app_secret" self._dynamicToken = None self._refreshToken = None self._name = None self._isLogged = False self._logEnabled = log self._DeviceParams = {} self._DeviceParams["deviceName"] = "" self._DeviceParams["deviceId"] = "" self._DeviceParams["deviceInfo"] = "" self._DeviceParams["deviceInfos"] = "" self._DeviceParams["userAgentPrecursor"] = "" self._userCode = usercode self._password = password # 初始化类 self.Network = Network(self) self.eCard = eCard(self) self.Supwisdom = Supwisdom(self) def _set_params_from_password_login(self, res: str): try: self._userToken = json.loads(res)["data"]["idToken"] # 我也不知道 refreshToken 有什么用,但先存着吧 self._refreshToken = json.loads(res)["data"]["refreshToken"] except: logger.error("LoginFailed") def _set_params_from_login_token(self, res: str): try: self._dynamicSecret = json.loads( base64.b64decode(json.loads(res)["business_data"]) )["secret"] self._dynamicToken = json.loads( base64.b64decode(json.loads(res)["business_data"]) )["token"] self._name = json.loads(base64.b64decode(json.loads(res)["business_data"]))[ "user_info" ]["user_name"] except: logger.error("LoginFailed")
[文档] def set_device_params(self, **kwargs: Unpack[DeviceParams]): """ 设置设备参数。这些参数都需要抓包获取,但其实可有可无,因为目前并没有观察到相关风控机制 :param str deviceName: 设备名 ,位于 "passwordLogin" 请求的 User-Agent 中,组成为 '{appVersion}({deviceName})' :param str deviceId: 设备 ID , :param str deviceInfo: 设备信息,位于名为 "X-Device-Info" 的请求头中 :param str deviceInfos: 设备信息,位于名为 "X-Device-Infos" 的请求头中 :param str userAgentPrecursor: 设备 UA 前体 ,只需要包含 "SuperApp" 或 "uni-app Html5Plus/1.0 (Immersed/38.666668)" 前面的部分 """ self._DeviceParams["deviceName"] = _kget(kwargs, "deviceName", "") self._DeviceParams["deviceId"] = _kget(kwargs, "deviceId", "") self._DeviceParams["deviceInfo"] = _kget(kwargs, "deviceInfo", "") self._DeviceParams["deviceInfos"] = _kget(kwargs, "deviceInfos", "") self._DeviceParams["userAgentPrecursor"] = _kget(kwargs, "deviceInfos", "") if self._DeviceParams["userAgentPrecursor"].endswith(" "): self._DeviceParams["userAgentPrecursor"] = self._DeviceParams[ "userAgentPrecursor" ] else: self._DeviceParams["userAgentPrecursor"] = ( self._DeviceParams["userAgentPrecursor"] + " " )
# self.DeviceParamsSet = True
[文档] def login( self, appVersion: str = "SWSuperApp/1.0.33", appId: str = "com.supwisdom.zzu", osType: str = "android", ) -> Tuple[str, str]: """ 通过学号和密码登录 :param str appVersion: APP 版本 ,一般类似 "SWSuperApp/1.0.33" ,可自行更新版本号,但详细数据需要抓包获取,位于 "passwordLogin" 请求的 User-Agent 中,也可随便填或空着,目前没有观察到相关风控机制。 :param str appId: APP 包名,一般不需要修改 :param str osType: 系统类型,一般不需要修改 :returns: Tuple[str, str] - **usercode** (str) – 学号 - **name** (str) – 姓名 :rtype: Tuple[str,str] """ headers = { "User-Agent": f'{appVersion}({self._DeviceParams["deviceName"]})', "Connection": "Keep-Alive", "Accept-Encoding": "gzip", } response = httpx.post( f'https://token.s.zzu.edu.cn/password/passwordLogin?username={self._userCode}&password={self._password}&appId={appId}&geo&deviceId={self._DeviceParams["deviceId"]}&osType={osType}&clientId&mfaState', headers=headers, ) self._set_params_from_password_login(response.text) cookies = { "userToken": self._userToken, "Domain": ".zzu.edu.cn", "Path": "/", "SVRNAME": "ws1", } headers = { "User-Agent": self._DeviceParams["userAgentPrecursor"] + "SuperApp", "Accept": "application/json, text/plain, */*", "Accept-Encoding": "gzip, deflate, br, zstd", "Content-Type": "application/x-www-form-urlencoded", "sec-ch-ua": '"Not/A)Brand";v="8", "Chromium";v="126", "Android WebView";v="126"', "sec-ch-ua-mobile": "?1", "sec-ch-ua-platform": '"Android"', "Origin": "https://jw.v.zzu.edu.cn", "X-Requested-With": "com.supwisdom.zzu", "Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Dest": "empty", "Referer": "https://jw.v.zzu.edu.cn/app-web/", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Cookie": f"userToken={self._userToken}; Domain=.zzu.edu.cn; Path=/; SVRNAME=ws1", } data = { "random": int(random.uniform(10000, 99999)), "timestamp": int(round(time.time() * 1000)), "userToken": self._userToken, } # 计算 sign 并将其加入 data params = "" for key in data.keys(): params += f"{key}={data[key]}&" params = params[:-1] sign = get_sign(self._dynamicSecret, params) data["sign"] = sign response = httpx.post( "https://jw.v.zzu.edu.cn/app-ws/ws/app-service/super/app/login-token", cookies=cookies, headers=headers, data=data, ) self._set_params_from_login_token(response.text) self._isLogged = True self.eCard._get_eacrd_access_token() return self._userCode, self._name