181 lines
6.4 KiB
Python
181 lines
6.4 KiB
Python
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from core.device_profile import DeviceProfileGenerator, load_device_profile, save_device_profile
|
|
from core.dfp_client import apply_bootstrap_identity, extract_bootstrap_identity, post_request
|
|
from core.dfp_forms import (
|
|
build_gdfp_report_request,
|
|
build_unified_check_repair_request,
|
|
build_unified_fetch_request,
|
|
)
|
|
|
|
|
|
DFP_SESSION_SEED = 0x5D7E742B
|
|
|
|
|
|
def build_dfp_request_bundle(profile):
|
|
unix_time = int(profile.cold_launch_time_ms // 1000)
|
|
return {
|
|
"unified_fetch": build_unified_fetch_request(
|
|
profile,
|
|
counter=1,
|
|
unix_time=unix_time,
|
|
session_seed=DFP_SESSION_SEED,
|
|
ts_millis=str(profile.cold_launch_time_ms),
|
|
epoch_seconds=unix_time,
|
|
),
|
|
"unified_check_repair": build_unified_check_repair_request(
|
|
profile,
|
|
counter=2,
|
|
unix_time=unix_time,
|
|
session_seed=DFP_SESSION_SEED,
|
|
ts_millis=str(profile.cold_launch_time_ms),
|
|
last_did_ts=str(profile.install_time_ms),
|
|
),
|
|
"gdfp_report": build_gdfp_report_request(
|
|
profile,
|
|
counter=3,
|
|
unix_time=unix_time,
|
|
session_seed=DFP_SESSION_SEED,
|
|
ts_millis=str(profile.cold_launch_time_ms),
|
|
epoch_seconds=unix_time,
|
|
),
|
|
}
|
|
|
|
|
|
def run_online_bootstrap(profile, *, timeout=20, post_func=None):
|
|
unix_time = int(profile.cold_launch_time_ms // 1000)
|
|
requests = {}
|
|
responses = {}
|
|
|
|
requests["unified_fetch"] = build_unified_fetch_request(
|
|
profile,
|
|
counter=1,
|
|
unix_time=unix_time,
|
|
session_seed=DFP_SESSION_SEED,
|
|
ts_millis=str(profile.cold_launch_time_ms),
|
|
epoch_seconds=unix_time,
|
|
)
|
|
fetch_response = post_request(
|
|
requests["unified_fetch"],
|
|
timeout=timeout,
|
|
post_func=post_func,
|
|
)
|
|
responses["unified_fetch"] = fetch_response
|
|
|
|
fetch_identity = extract_bootstrap_identity(fetch_response.data, None)
|
|
apply_bootstrap_identity(profile, fetch_identity)
|
|
|
|
requests["unified_check_repair"] = build_unified_check_repair_request(
|
|
profile,
|
|
counter=2,
|
|
unix_time=unix_time,
|
|
session_seed=DFP_SESSION_SEED,
|
|
ts_millis=str(profile.cold_launch_time_ms),
|
|
last_did_ts=str(profile.install_time_ms),
|
|
)
|
|
check_repair_response = post_request(
|
|
requests["unified_check_repair"],
|
|
timeout=timeout,
|
|
post_func=post_func,
|
|
)
|
|
responses["unified_check_repair"] = check_repair_response
|
|
|
|
requests["gdfp_report"] = build_gdfp_report_request(
|
|
profile,
|
|
counter=3,
|
|
unix_time=unix_time,
|
|
session_seed=DFP_SESSION_SEED,
|
|
ts_millis=str(profile.cold_launch_time_ms),
|
|
epoch_seconds=unix_time,
|
|
)
|
|
report_response = post_request(
|
|
requests["gdfp_report"],
|
|
timeout=timeout,
|
|
post_func=post_func,
|
|
)
|
|
responses["gdfp_report"] = report_response
|
|
|
|
identity = extract_bootstrap_identity(fetch_response.data, report_response.data)
|
|
apply_bootstrap_identity(profile, identity)
|
|
return {
|
|
"requests": {key: value.to_dict() for key, value in requests.items()},
|
|
"responses": {key: value.to_dict() for key, value in responses.items()},
|
|
"identity": identity.to_dict(),
|
|
}
|
|
|
|
|
|
def _write_json(path: Path, data) -> None:
|
|
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
|
|
|
|
|
|
def build_parser() -> argparse.ArgumentParser:
|
|
parser = argparse.ArgumentParser(description="Generate local Kuaishou device profiles")
|
|
parser.add_argument("--count", type=int, default=1)
|
|
parser.add_argument("--out-dir", default="out/devices")
|
|
parser.add_argument("--prefix", default="device")
|
|
parser.add_argument("--seed", type=int, default=None)
|
|
parser.add_argument("--profile", default="", help="load an existing profile JSON")
|
|
parser.add_argument("--env", action="store_true", help="also write .env files")
|
|
parser.add_argument("--dfp-dry-run", action="store_true", help="write DFP request material")
|
|
parser.add_argument("--online", action="store_true", help="send DFP bootstrap requests and update profile")
|
|
parser.add_argument("--force", action="store_true", help="overwrite existing files")
|
|
return parser
|
|
|
|
|
|
def run_generation(args: argparse.Namespace, *, post_func=None) -> int:
|
|
if args.count < 1:
|
|
raise SystemExit("--count must be >= 1")
|
|
|
|
out_dir = Path(args.out_dir)
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
generator = DeviceProfileGenerator(seed=args.seed)
|
|
|
|
for index in range(1, args.count + 1):
|
|
profile = load_device_profile(args.profile) if args.profile else generator.new_profile()
|
|
stem = f"{args.prefix}_{index:03d}"
|
|
json_path = out_dir / f"{stem}.json"
|
|
env_path = out_dir / f"{stem}.env"
|
|
dfp_path = out_dir / f"{stem}_dfp_requests.json"
|
|
online_path = out_dir / f"{stem}_dfp_online.json"
|
|
|
|
if not args.force and json_path.exists():
|
|
raise SystemExit(f"refusing to overwrite existing file: {json_path}")
|
|
if args.env and not args.force and env_path.exists():
|
|
raise SystemExit(f"refusing to overwrite existing file: {env_path}")
|
|
if args.dfp_dry_run and not args.force and dfp_path.exists():
|
|
raise SystemExit(f"refusing to overwrite existing file: {dfp_path}")
|
|
if args.online and not args.force and online_path.exists():
|
|
raise SystemExit(f"refusing to overwrite existing file: {online_path}")
|
|
|
|
if args.dfp_dry_run:
|
|
dfp_requests = {key: value.to_dict() for key, value in build_dfp_request_bundle(profile).items()}
|
|
_write_json(dfp_path, dfp_requests)
|
|
if args.online:
|
|
online_result = run_online_bootstrap(profile, post_func=post_func)
|
|
_write_json(online_path, online_result)
|
|
|
|
save_device_profile(profile, json_path)
|
|
if args.env:
|
|
env_path.write_text(profile.to_env(), encoding="utf-8")
|
|
|
|
print(f"[OK] {json_path} did={profile.did} odid={profile.o_did} rdid={profile.rdid}")
|
|
|
|
return 0
|
|
|
|
|
|
def main() -> int:
|
|
return run_generation(build_parser().parse_args())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|