1
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
首页-默认
|
||||
|
||||
```JSON
|
||||
{
|
||||
"oauth_type": "pwa",
|
||||
"oauth_id": "cXNQe5FQXIkaW8Ny1792802204704",
|
||||
"trace_id": "",
|
||||
"aff_x_code": "",
|
||||
"version": "7.5.0",
|
||||
"mod": "index",
|
||||
"code": "home",
|
||||
"page": 3,
|
||||
"token": "3fba65f06e1e308c819e343474075461",
|
||||
"timestamp": 1792811390633
|
||||
}
|
||||
```
|
||||
|
||||
首页-精选-最新
|
||||
|
||||
```JSON
|
||||
{
|
||||
"oauth_type": "pwa",
|
||||
"oauth_id": "cXNQe5FQXIkaW8Ny1792802204704",
|
||||
"trace_id": "",
|
||||
"aff_x_code": "",
|
||||
"version": "7.5.0",
|
||||
"mod": "choice",
|
||||
"code": "vlog",
|
||||
"type": "new",
|
||||
"page": 1,
|
||||
"limit": 30,
|
||||
"token": "3fba65f06e1e308c819e343474075461",
|
||||
"timestamp": 1792810190406
|
||||
}
|
||||
```
|
||||
|
||||
短剧-原创-全部
|
||||
|
||||
```JSON
|
||||
{
|
||||
"oauth_type": "pwa",
|
||||
"oauth_id": "cXNQe5FQXIkaW8Ny1792802204704",
|
||||
"trace_id": "",
|
||||
"aff_x_code": "",
|
||||
"version": "7.5.0",
|
||||
"mod": "aidrama",
|
||||
"code": "home",
|
||||
"page": 1,
|
||||
"limit": 10,
|
||||
"pay_type": "all",
|
||||
"sort": "all",
|
||||
"category_ids": [
|
||||
4
|
||||
],
|
||||
"token": "3fba65f06e1e308c819e343474075461",
|
||||
"timestamp": 1792811903280
|
||||
}
|
||||
```
|
||||
|
||||
短剧-详情页(请求)
|
||||
|
||||
```json
|
||||
{
|
||||
"oauth_type": "pwa",
|
||||
"oauth_id": "cXNQe5FQXIkaW8Ny1792802204704",
|
||||
"trace_id": "",
|
||||
"aff_x_code": "",
|
||||
"version": "7.5.0",
|
||||
"mod": "aidrama",
|
||||
"code": "detail",
|
||||
"drama_id": 1008,
|
||||
"token": "3fba65f06e1e308c819e343474075461",
|
||||
"timestamp": 1792812296047
|
||||
}
|
||||
```
|
||||
|
||||
短剧-请求所有集数
|
||||
|
||||
```json
|
||||
{
|
||||
"oauth_type": "pwa",
|
||||
"oauth_id": "cXNQe5FQXIkaW8Ny1792802204704",
|
||||
"trace_id": "",
|
||||
"aff_x_code": "",
|
||||
"version": "7.5.0",
|
||||
"mod": "aidrama",
|
||||
"code": "episodes",
|
||||
"drama_id": 1008,
|
||||
"page": 1,
|
||||
"limit": 100,
|
||||
"token": "3fba65f06e1e308c819e343474075461",
|
||||
"timestamp": 1792812296047
|
||||
}
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
短剧-获得所有集数
|
||||
|
||||
```json
|
||||
aaa
|
||||
aaa
|
||||
```
|
||||
|
||||
@@ -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>×tamp=<ts><sign_key>'))"""
|
||||
base = f"client=pwa&data={data_hex}×tamp={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×tamp={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()
|
||||
Reference in New Issue
Block a user