This commit is contained in:
lz_db
2026-10-25 11:46:44 +08:00
parent 893dfed0ed
commit 494f667719
63 changed files with 5213 additions and 239 deletions
+192
View File
@@ -0,0 +1,192 @@
#!/usr/bin/env python3
"""91Video PWA 登录脚本
通过逆向 pwa4.jl8v0p5t.cc 的加密协议,调用登录接口获取 token。
依赖: pip install requests pycryptodome
"""
import hashlib
import json
import random
import string
import time
import requests
from Crypto.Cipher import AES
# ===== 协议常量 (从前端 JS 逆向得到) =====
API_URL = "https://api4.zqpnd5a7.cc/pwa.php"
# AES-256 密钥与 IV (请求加密 / 响应解密共用)
AES_KEY = b"cc88ddc9357ff461e08f047aedee692b" # 32 bytes
AES_IV = b"e89225cfbbimgkcu" # 16 bytes
# 签名用的密钥 (即 AES 密钥)
SIGN_KEY = "cc88ddc9357ff461e08f047aedee692b"
# oauth_id 随机字符集
OAUTH_CHARSET = (
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
)
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Linux; Android 10; SM-G973F) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/120.0.0.0 Mobile Safari/537.36"
),
"Origin": "https://pwa4.jl8v0p5t.cc",
"Referer": "https://pwa4.jl8v0p5t.cc/",
"Accept": "application/json, text/plain, */*",
"Content-Type": "application/x-www-form-urlencoded",
}
def _aes_encrypt(plaintext: str) -> str:
"""AES-256-CFB (segment_size=128) 加密, 返回大写十六进制字符串。"""
cipher = AES.new(AES_KEY, AES.MODE_CFB, AES_IV, segment_size=128)
return cipher.encrypt(plaintext.encode("utf-8")).hex().upper()
def _aes_decrypt(hex_data: str) -> str:
"""AES-256-CFB (segment_size=128) 解密。"""
cipher = AES.new(AES_KEY, AES.MODE_CFB, AES_IV, segment_size=128)
return cipher.decrypt(bytes.fromhex(hex_data)).decode("utf-8", "replace")
def _make_sign(data_hex: str, timestamp_sec: int) -> str:
"""生成 sign: MD5(SHA256('client=pwa&data=<data>&timestamp=<ts><sign_key>'))"""
base = f"client=pwa&data={data_hex}&timestamp={timestamp_sec}{SIGN_KEY}"
sha = hashlib.sha256(base.encode("utf-8")).hexdigest()
return hashlib.md5(sha.encode("utf-8")).hexdigest()
def _gen_oauth_id() -> str:
"""生成 oauth_id: 16 位随机字符 + 毫秒时间戳。"""
rand_part = "".join(random.choices(OAUTH_CHARSET, k=16))
return rand_part + str(int(time.time() * 1000))
def _post(session: requests.Session, payload: dict) -> dict:
"""加密 payload 并发送请求,返回解密后的 JSON 响应。"""
ts_ms = int(time.time() * 1000)
ts_sec = ts_ms // 1000
payload["timestamp"] = ts_ms
plaintext = json.dumps(payload, separators=(",", ":"))
data_hex = _aes_encrypt(plaintext)
sign = _make_sign(data_hex, ts_sec)
body = f"client=pwa&timestamp={ts_sec}&data={data_hex}&sign={sign}"
resp = session.post(API_URL, data=body, headers=HEADERS, timeout=30)
if resp.status_code == 503:
raise RuntimeError(
"服务端返回 503。若 oauth_id 未经 system/index 注册会触发此错误;"
"也可能是 CloudFront 限流,请稍后重试。"
)
resp.raise_for_status()
result = resp.json()
if result.get("errcode") != 0:
raise RuntimeError(f"接口返回错误: {result}")
encrypted_data = result.get("data", "")
if not encrypted_data:
return result
decrypted = _aes_decrypt(encrypted_data)
return json.loads(decrypted)
def login(account: str, password: str):
"""登录并返回 (session, oauth_id, 解析后的登录响应)。
登录前必须先调用 system/index 注册 oauth_id,
否则服务端会对未注册的 oauth_id 返回 503。
"""
session = requests.Session()
oauth_id = _gen_oauth_id()
base = {
"oauth_type": "pwa",
"oauth_id": oauth_id,
"trace_id": "",
"aff_x_code": "",
"version": "7.5.0",
}
# 1) 先调用 system/index 注册 oauth_id
_post(session, dict(base, mod="system", code="index", data={}, token=""))
time.sleep(0.3)
# 2) 再调用登录接口
payload = dict(
base,
mod="user",
code="regOrLogin",
account=account,
password=password,
type="login",
)
result = _post(session, payload)
return session, oauth_id, result
def fetch_home(session: requests.Session, oauth_id: str, token: str, page: int) -> dict:
"""请求首页列表 (mod=index, code=home) 并返回解密后的数据。"""
payload = {
"oauth_type": "pwa",
"oauth_id": oauth_id,
"trace_id": "",
"aff_x_code": "",
"version": "7.5.0",
"mod": "index",
"code": "home",
"page": page,
"token": token,
}
return _post(session, payload)
def main():
account = "16774351679"
password = "admin123456"
print(f"正在登录账号: {account}")
session, oauth_id, result = login(account, password)
inner = result.get("data", result)
if not inner.get("success"):
print("登录失败:", inner.get("msg", result))
return
token = inner.get("token")
print(f"登录成功! token: {token}")
# 请求首页第 1~10 页并解密保存
for page in range(1, 2):
try:
data = fetch_home(session, oauth_id, token, page)
except Exception as e:
print(f"第 {page} 页请求失败: {e}")
continue
out_file = f"home_page_{page}.json"
with open(out_file, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# 打印摘要:data 直接就是视频列表
items = data.get("data") if isinstance(data.get("data"), list) else []
print(f"第 {page} 页: 已解密并保存到 {out_file} (共 {len(items)} 条)")
time.sleep(0.5)
if __name__ == "__main__":
# data_str = '46CF9369809931B4D43024E4A689EADDF078D83A100348DB8CB1B386522B6F5811D57BAD96801571597E550A83F20B865F34F7CEA7BCA0ED2CE780257CE0D818F2E36A04941BC58B0767C3263377995656FC9C251C58BB8C3C5BF36DBFD0B65348FF1031D9E381377593BB8B32F39C8F0AF2DFF7579BA2B260E6073B9B6A6EB1FC309116F38380C7EA555684C9BF4464D172B1DA3513461AB5E1A1865FF4E7AE146D980129EFFE464773B03212543462D1CBB31A79EDE0B9402A17677CCF1526213B4E787BAF7F6252A0C445412C519DCF663CA477D6CC9F1E7C8586F2D486EADE8A12A16FB215BCBCB9D4128DB568FEC5D572863A61AE0D55B36D00'
# decrypted = json.loads(_aes_decrypt(data_str))
# print("-----")
# print(json.dumps(decrypted, ensure_ascii=False, indent=2))
main()