ksjsb/tools/new_device.py
2026-07-30 20:25:56 +08:00

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