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())