Files
video/admin/91docs/login.py
T
2026-10-25 11:46:44 +08:00

193 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()