from __future__ import annotations import base64 import time import uuid from dataclasses import asdict, dataclass from urllib.parse import quote_plus, urlencode from .device_profile import DeviceProfile from .dfp_knn import build_full_knn, build_lite_knn from .dfp_sign import DFP_SDK_ID, sign_dfp_form from .dfp_sq0 import encode_sq0_device_info from .enc_data import KWSG_10400_DEFAULT_CFG9, kwsg_10400_raw, load_kwsg_10400_tables UNIFIED_FETCH_URL = "https://gdfpsec.ksapisrv.com/rest/infra/unifiedId/fetch/android" UNIFIED_CHECK_REPAIR_URL = "https://gdfpsec.ksapisrv.com/rest/infra/unifiedId/checkRepair" GDFP_REPORT_URL = "https://gdfpsec.ksapisrv.com/rest/infra/gdfp/report/kuaishou/android" UNIFIED_FETCH_FORM_ORDER = [ "aegon", "appVersion", "deviceInfo", "did", "didTag", "hgidReportId", "platform", "productName", "rdid", "requestId", "sdkVersion", "sv", "ts", "sign", ] UNIFIED_CHECK_REPAIR_FORM_ORDER = [ "appVersion", "did", "didTag", "from", "lastDidTs", "platform", "productName", "sdkVersion", "ts", "sign", ] GDFP_REPORT_FORM_ORDER = [ "productName", "ts", "deviceInfo", "sign", "sv", "rdid", "didtag", ] @dataclass(frozen=True) class DfpRequestSpec: method: str url: str headers: dict[str, str] form_order: list[str] form: dict[str, str] body: str def to_dict(self) -> dict[str, object]: return asdict(self) def android_base64_default(data: bytes) -> str: text = base64.b64encode(data).decode() return "\n".join(text[i : i + 76] for i in range(0, len(text), 76)) + "\n" def java_urlencode(text: str) -> str: return quote_plus(text, safe="*-._") def _headers(url: str) -> dict[str, str]: host = url.split("/", 3)[2] return { "Content-Type": "application/x-www-form-urlencoded", "Host": host, "Connection": "Keep-Alive", "Accept-Encoding": "gzip", "User-Agent": "okhttp/3.12.13", } def _device_info_encoded( sq0_raw: bytes, *, epoch_seconds: int | None, sdk_id: str = DFP_SDK_ID, ) -> str: t1, t2 = load_kwsg_10400_tables() raw = kwsg_10400_raw( sq0_raw, sdk_id, t1, t2, epoch_seconds=epoch_seconds, cfg9=KWSG_10400_DEFAULT_CFG9, ) return java_urlencode(android_base64_default(raw)) def _request_from_order(url: str, form: dict[str, str], form_order: list[str]) -> DfpRequestSpec: ordered_pairs = [(key, form[key]) for key in form_order] body = urlencode(ordered_pairs) return DfpRequestSpec( method="POST", url=url, headers=_headers(url), form_order=list(form_order), form={key: value for key, value in ordered_pairs}, body=body, ) def _now_millis() -> str: return str(int(time.time() * 1000)) def _request_id() -> str: return uuid.uuid4().hex[:16] def build_unified_fetch_request( profile: DeviceProfile, *, counter: int, unix_time: int, session_seed: int, ts_millis: str | None = None, epoch_seconds: int | None = None, ) -> DfpRequestSpec: sq0_raw = encode_sq0_device_info(build_lite_knn(profile), mode="lite") device_info = _device_info_encoded(sq0_raw, epoch_seconds=epoch_seconds) unsigned = { "aegon": "false", "appVersion": "14.5.50.11631", "deviceInfo": device_info, "did": profile.did, "didTag": "-1", "hgidReportId": _request_id(), "platform": "1", "productName": "NEBULA", "rdid": profile.rdid, "requestId": _request_id(), "sdkVersion": "9.5.4lite.79.137a838b", "sv": "2", "ts": ts_millis or _now_millis(), } signed = sign_dfp_form( "unified_fetch", unsigned, counter, unix_time, session_seed=session_seed, ) form = {key: signed[key] for key in UNIFIED_FETCH_FORM_ORDER} return _request_from_order(UNIFIED_FETCH_URL, form, UNIFIED_FETCH_FORM_ORDER) def build_unified_check_repair_request( profile: DeviceProfile, *, counter: int, unix_time: int, session_seed: int, ts_millis: str | None = None, from_value: str = "1", last_did_ts: str | None = None, ) -> DfpRequestSpec: unsigned = { "appVersion": profile.app_version, "did": profile.did, "didTag": str(profile.cdid_tag), "from": str(from_value), "lastDidTs": str(last_did_ts if last_did_ts is not None else profile.install_time_ms), "platform": "1", "productName": "NEBULA", "sdkVersion": "9.5.4lite.79.137a838b", "ts": ts_millis or _now_millis(), } signed = sign_dfp_form( "unified_check_repair", unsigned, counter, unix_time, session_seed=session_seed, ) form = {key: signed[key] for key in UNIFIED_CHECK_REPAIR_FORM_ORDER} return _request_from_order(UNIFIED_CHECK_REPAIR_URL, form, UNIFIED_CHECK_REPAIR_FORM_ORDER) def build_gdfp_report_request( profile: DeviceProfile, *, counter: int, unix_time: int, session_seed: int, ts_millis: str | None = None, epoch_seconds: int | None = None, ) -> DfpRequestSpec: sq0_raw = encode_sq0_device_info(build_full_knn(profile), mode="full") device_info = _device_info_encoded(sq0_raw, epoch_seconds=epoch_seconds) unsigned = { "productName": "NEBULA", "ts": ts_millis or _now_millis(), "deviceInfo": device_info, "sv": "2", "rdid": profile.rdid, "didtag": "-1", } signed = sign_dfp_form( "gdfp_report", unsigned, counter, unix_time, session_seed=session_seed, ) form = {key: signed[key] for key in GDFP_REPORT_FORM_ORDER} return _request_from_order(GDFP_REPORT_URL, form, GDFP_REPORT_FORM_ORDER) __all__ = [ "DfpRequestSpec", "UNIFIED_CHECK_REPAIR_FORM_ORDER", "UNIFIED_CHECK_REPAIR_URL", "GDFP_REPORT_FORM_ORDER", "GDFP_REPORT_URL", "UNIFIED_FETCH_FORM_ORDER", "UNIFIED_FETCH_URL", "android_base64_default", "build_gdfp_report_request", "build_unified_check_repair_request", "build_unified_fetch_request", "java_urlencode", ]