import json import os import tempfile import unittest import urllib.parse from pathlib import Path from types import SimpleNamespace from unittest.mock import patch from core.device_profile import DeviceProfileGenerator, save_device_profile from core.h5_jsbridge import build_h5_sign_input from core.h5_sig3 import h5_sig3_crc32_from_sign_input, parse_h5_sig3 from main import ( AD_FETCH_PAYLOADS, DEFAULT_OAID, KsNebulaClient, ResponseRecord, args_to_device_options, build_parser, cookie_from_env, describe_local_algorithm_stack, is_business_ok, ) class MainDeviceProfileTests(unittest.TestCase): def test_cookie_from_env_overlays_device_profile(self): profile = DeviceProfileGenerator(seed=20260711).new_profile() profile.apply_cloud_identity( "ANDROID_69646c9107f45c63", 2, "DFPB39C1B79D15E65AA00E29C724073B1578784BDC532027CF94846A0C145787", ) with tempfile.TemporaryDirectory() as tmp: profile_path = Path(tmp) / "device.json" save_device_profile(profile, profile_path) with patch.dict( os.environ, { "KS_COOKIE": "userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", "KS_CLIENT_SALT": "salt", }, clear=True, ): full_cookie, client_salt, cookie_dict = cookie_from_env(device_profile_path=profile_path) self.assertEqual(client_salt, "salt") self.assertIn(f"did={profile.did}", full_cookie) self.assertEqual(cookie_dict["did"], profile.did) self.assertEqual(cookie_dict["oDid"], profile.o_did) self.assertEqual(cookie_dict["rdid"], profile.rdid) self.assertEqual(cookie_dict["egid"], profile.egid) self.assertEqual(cookie_dict["oaid"], profile.runtime_hints.oaid) self.assertEqual(cookie_dict["cdid_tag"], "2") self.assertEqual(cookie_dict["token"], "TOKEN") def test_parser_accepts_device_profile(self): args = build_parser().parse_args(["--device-profile", "out/device.json"]) self.assertEqual(args.device_profile, "out/device.json") def test_parser_accepts_in_memory_device_options(self): args = build_parser().parse_args( [ "--memory-device", "--device-low-coin-threshold", "20", "--device-max-switches", "2", ] ) self.assertTrue(args.memory_device) self.assertEqual(args.device_low_coin_threshold, 20) self.assertEqual(args.device_max_switches, 2) def test_parser_accepts_normal_ad_count(self): args = build_parser().parse_args(["--normal-ad-count", "3"]) self.assertEqual(args.normal_ad_count, 3) def test_parser_accepts_skip_ad_reward_threshold(self): args = build_parser().parse_args(["--skip-ad-reward-leq", "1"]) self.assertEqual(args.skip_ad_reward_leq, 1) def test_parser_accepts_print_local_algorithms(self): args = build_parser().parse_args(["--print-algorithms"]) self.assertTrue(args.print_algorithms) def test_local_algorithm_stack_contains_no_remote_signer(self): lines = describe_local_algorithm_stack( h5_sig3_mode="pure", h5_kww_mode="pure", h5_kws_mode="default", ) text = "\n".join(lines) self.assertIn("sig=core.sig", text) self.assertIn("api_sig3=core.sig3", text) self.assertIn("reward_fetch=core.reward_request", text) self.assertIn("h5_sig3=core.h5_sig3", text) self.assertNotIn("http://", text.lower()) self.assertNotIn("https://", text.lower()) self.assertNotIn("youfak.dev", text.lower()) def test_parser_accepts_server_device_alias(self): args = build_parser().parse_args(["--server-device"]) self.assertTrue(args.server_device) def test_server_device_conflicts_with_no_device_online(self): args = build_parser().parse_args(["--server-device", "--no-device-online"]) with self.assertRaises(SystemExit): args_to_device_options(args) def test_cookie_from_env_reads_first_ksck_account(self): """任务运行环境支持单个 ksck 变量,多个账号按换行分隔。""" with patch.dict( os.environ, { "ksck": ( "一号#userId=1; ud=1; did=DID_ONE; " "kuaishou.api_st=TOKEN_ONE; token=TOKEN_ONE#salt_one\r\n" "二号#userId=2; ud=2; did=DID_TWO; " "kuaishou.api_st=TOKEN_TWO; token=TOKEN_TWO#salt_two" ), "KS_COOKIE": "userId=OLD; did=OLD_DID; kuaishou.api_st=OLD_TOKEN", "KS_CLIENT_SALT": "old_salt", }, clear=True, ): full_cookie, client_salt, cookie_dict = cookie_from_env() self.assertEqual(client_salt, "salt_one") self.assertIn("did=DID_ONE", full_cookie) self.assertEqual(cookie_dict["userId"], "1") self.assertEqual(cookie_dict["did"], "DID_ONE") self.assertEqual(cookie_dict["kuaishou.api_st"], "TOKEN_ONE") def test_memory_device_overlays_cookie_without_writing_profile_file(self): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=True, memory_device=True, device_online=False, device_seed=1234, ) self.assertIsNotNone(client.device_profile) self.assertEqual(client.cookie_dict["did"], client.device_profile.did) self.assertEqual(client.cookie_dict["token"], "TOKEN") self.assertIn(f"did={client.device_profile.did}", client.full_cookie) self.assertEqual(client._api_params()["egid"], client.device_profile.egid) self.assertEqual(client._device_oaid(), client.device_profile.runtime_hints.oaid) self.assertEqual(client._api_params()["socName"], client.device_profile.soc_name) self.assertEqual(client._api_params()["boardPlatform"], client.device_profile.board_platform) self.assertEqual(client._api_params()["max_memory"], str(client.device_profile.max_memory)) self.assertEqual(client._api_params()["deviceBit"], client.device_profile.device_bit) def test_memory_device_online_bootstrap_failure_falls_back_to_local_profile(self): with patch( "main.run_online_bootstrap", side_effect=RuntimeError("dfp unavailable"), ): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=False, memory_device=True, device_online=True, device_seed=97531, ) self.assertIsNotNone(client.device_profile) self.assertEqual(client.cookie_dict["did"], client.device_profile.did) self.assertEqual(client.cookie_dict["egid"], client.device_profile.egid) self.assertNotEqual(client.cookie_dict["did"], "ANDROID_oldoldoldold") def test_memory_device_online_partial_failure_restores_local_profile(self): def mutate_then_fail(profile, timeout): del timeout profile.apply_cloud_identity("ANDROID_f05497e9cef09a7f", 2) raise RuntimeError("gdfp report unavailable") with patch("main.run_online_bootstrap", side_effect=mutate_then_fail): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=False, memory_device=True, device_online=True, device_seed=97531, ) self.assertNotEqual(client.device_profile.did, "ANDROID_f05497e9cef09a7f") self.assertEqual(client.device_profile.did, client.device_profile.local_did) self.assertEqual(client.cookie_dict["did"], client.device_profile.local_did) def test_memory_device_online_did_without_egid_restores_local_profile(self): def did_only_bootstrap(profile, timeout): del timeout profile.apply_cloud_identity("ANDROID_f05497e9cef09a7f", 2) return {"identity": {"did": "ANDROID_f05497e9cef09a7f", "cdid_tag": 2, "egid": ""}} with patch("main.run_online_bootstrap", side_effect=did_only_bootstrap): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=False, memory_device=True, device_online=True, device_seed=97531, ) self.assertNotEqual(client.device_profile.did, "ANDROID_f05497e9cef09a7f") self.assertEqual(client.device_profile.did, client.device_profile.local_did) self.assertEqual(client.cookie_dict["egid"], client.device_profile.egid) def test_server_device_mode_accepts_online_bootstrap_with_egid(self): def successful_bootstrap(profile, timeout): del timeout profile.apply_cloud_identity( "ANDROID_f05497e9cef09a7f", 2, "DFP" + "A" * 61, ) return { "identity": { "did": "ANDROID_f05497e9cef09a7f", "cdid_tag": 2, "egid": "DFP" + "A" * 61, } } with patch("main.run_online_bootstrap", side_effect=successful_bootstrap): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=False, memory_device=True, device_online=True, device_seed=97531, ) self.assertEqual(client.device_profile.did, "ANDROID_f05497e9cef09a7f") self.assertEqual(client.cookie_dict["did"], "ANDROID_f05497e9cef09a7f") self.assertEqual(client.cookie_dict["egid"], "DFP" + "A" * 61) def test_memory_device_keeps_h5_oaid_bound_to_original_cookie(self): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=True, memory_device=True, device_online=False, device_seed=2468, ) oaid = client.device_profile.runtime_hints.oaid task_params = client._task_list_params() treasure_body = json.loads(client._treasure_open_body()) self.assertEqual(client._device_oaid(), oaid) self.assertEqual(task_params["oaid"], DEFAULT_OAID) self.assertEqual(treasure_body["oaid"], DEFAULT_OAID) self.assertEqual(treasure_body["imei"], "") self.assertEqual(treasure_body["idfa"], "") def test_memory_device_preserves_h5_cookie_for_sig3_and_headers(self): original_cookie = { "kpn": "NEBULA", "kpf": "ANDROID_PHONE", "userId": "1579452490", "did": "ANDROID_oldoldoldold", "egid": "DFPOLD", "oDid": "ANDROID_OLD_ODID", "rdid": "ANDROID_OLD_RDID", "token": "TOKEN", "kuaishou.api_st": "API_ST", } client = KsNebulaClient( full_cookie="; ".join(f"{k}={v}" for k, v in original_cookie.items()), client_salt="salt", cookie_dict=dict(original_cookie), timeout=30, dry_run=True, memory_device=True, device_online=False, device_seed=1357, ) query = {"source": "bottom_guide_first"} generated = client._next_h5_sig3("treasure_info", query=query) parsed = parse_h5_sig3(generated) expected_input = build_h5_sign_input(original_cookie, query=query) self.assertNotEqual(client.cookie_dict["did"], original_cookie["did"]) self.assertEqual(client.h5_cookie_dict["did"], original_cookie["did"]) self.assertEqual(parsed.crc32, h5_sig3_crc32_from_sign_input(expected_input)) captured = {} def fake_request(label, title, request_id, method, url, headers, body=None): captured["headers"] = headers return ResponseRecord(label, title, request_id, 200, True, url, {"result": 1}, "") client._request = fake_request client.sign_in_resource() self.assertIn("did=ANDROID_oldoldoldold", captured["headers"]["Cookie"]) self.assertNotIn(client.cookie_dict["did"], captured["headers"]["Cookie"]) def test_low_coin_report_rotates_memory_device(self): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=True, memory_device=True, device_online=False, device_seed=5678, device_low_coin_threshold=20, device_max_switches=1, ) first_did = client.cookie_dict["did"] low_coin_record = ResponseRecord( label="normal_ad_report", title="看广告得金币上报", request_id=2700, status=200, ok=True, url="", response_json={"result": 1, "data": {"neoAmount": 5}}, response_text="", ) rotated = client.maybe_rotate_device_for_record(low_coin_record) self.assertTrue(rotated) self.assertNotEqual(client.cookie_dict["did"], first_did) def test_low_expected_reward_skip_rotates_memory_device(self): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=True, memory_device=True, device_online=False, device_seed=6789, device_low_coin_threshold=20, device_max_switches=1, ) first_did = client.cookie_dict["did"] skipped_record = ResponseRecord( label="normal_ad_skip", title="看广告得金币跳过", request_id=2637, status=None, ok=True, url="", response_json={"skipped": True, "reason": "low_expected_reward", "expectedReward": 1}, response_text="", ) rotated = client.maybe_rotate_device_for_record(skipped_record) self.assertTrue(rotated) self.assertNotEqual(client.cookie_dict["did"], first_did) def test_task_countdown_skip_does_not_rotate_memory_device(self): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=True, memory_device=True, device_online=False, device_seed=7890, device_low_coin_threshold=20, device_max_switches=1, ) first_did = client.cookie_dict["did"] skipped_record = ResponseRecord( label="normal_ad_skip", title="看广告得金币跳过", request_id=2637, status=None, ok=True, url="", response_json={"skipped": True, "reason": "task_countdown", "countdownMs": 2411231}, response_text="", ) rotated = client.maybe_rotate_device_for_record(skipped_record) self.assertFalse(rotated) self.assertEqual(client.cookie_dict["did"], first_did) def test_antispam_and_signature_failure_are_not_business_ok(self): self.assertFalse(is_business_ok(200, {"result": 50, "error_msg": "签名验证失败"})) self.assertFalse(is_business_ok(200, {"result": 1, "errorMsg": "ANTISPAM_REQUEST"})) self.assertFalse(is_business_ok(200, {"result": 1, "errorMsg": "INVALID_REQUEST"})) def test_fetch_ad_builds_dynamic_reward_body(self): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=False, memory_device=True, device_online=False, device_seed=4321, ) captured = {} def fake_request(label, title, request_id, method, url, headers, body=None): captured["body"] = body.decode("utf-8") captured["url"] = url return ResponseRecord(label, title, request_id, 200, False, url, {"result": 1, "feeds": []}, "") client._request = fake_request client.fetch_ad("normal") body = dict(urllib.parse.parse_qsl(captured["body"], keep_blank_values=True)) self.assertIn("encData", body) self.assertIn("sign", body) self.assertNotEqual(body["encData"], AD_FETCH_PAYLOADS["normal"]["encData"]) self.assertNotEqual(body["sign"], AD_FETCH_PAYLOADS["normal"]["sign"]) def test_fetch_ad_passes_profile_oaid_to_reward_str_e(self): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=False, memory_device=True, device_online=False, device_seed=8642, ) captured = {} def fake_request(label, title, request_id, method, url, headers, body=None): return ResponseRecord(label, title, request_id, 200, False, url, {"result": 1, "feeds": []}, "") def fake_build_reward_str_e(**kwargs): captured.update(kwargs) return b"{}" client._request = fake_request with patch("main.build_reward_str_e", side_effect=fake_build_reward_str_e), patch( "main.build_reward_body", return_value=SimpleNamespace(enc_data="ENC", sign="SIGN", sign_counter=1), ): client.fetch_ad("normal") self.assertEqual(captured["oaid"], client.device_profile.runtime_hints.oaid) def test_h5_sig3_defaults_to_pure_python_digest(self): cookie_dict = { "kpn": "NEBULA", "kpf": "ANDROID_PHONE", "userId": "1579452490", "did": "ANDROID_aaaaaaaaaaaaaaaa", "egid": "DFPBBBB", "oDid": "ANDROID_cccccccccccccccc", "rdid": "ANDROID_dddddddddddddddd", } with patch.dict(os.environ, {}, clear=True), patch( "main.H5JsBridgeEncoder", side_effect=AssertionError("pure mode must not start JS bridge"), ): client = KsNebulaClient( full_cookie="; ".join(f"{k}={v}" for k, v in cookie_dict.items()), client_salt="salt", cookie_dict=dict(cookie_dict), timeout=30, dry_run=True, ) query = {"source": "bottom_guide_first"} generated = client._next_h5_sig3("treasure_info", query=query) sign_input = build_h5_sign_input(cookie_dict, query=query) parsed = parse_h5_sig3(generated) self.assertTrue(parsed.ok) self.assertEqual(parsed.crc32, h5_sig3_crc32_from_sign_input(sign_input)) self.assertEqual(client.h5_sig3_mode, "pure") def test_h5_kww_pure_mode_does_not_start_node_bridge(self): with patch.dict(os.environ, {"KS_H5_KWW_MODE": "pure"}, clear=True), patch( "main.H5KwwGenerator", side_effect=AssertionError("pure kww mode must not start Node bridge"), ) as node_bridge: client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=True, ) headers = client._h5_headers( "sign_in_report", url="https://nebula.kuaishou.com/rest/wd/encourage/unionTask/signIn/report", method="GET", ) node_bridge.assert_not_called() self.assertEqual(client.h5_kww_mode, "pure") self.assertTrue(headers["kww"].startswith("PnGU+9+Y8008S+nH")) self.assertEqual(len(headers["kww"]), 174) def test_h5_kww_pure_mode_syncs_storage_fields_into_cookie(self): fake_result = SimpleNamespace(kww="PnGU" + "A" * 170, kwfcv1="3", kwfv1="PnGU" + "A" * 170) fake_generator = SimpleNamespace(get=lambda **_: fake_result, close=lambda: None) with patch.dict(os.environ, {"KS_H5_KWW_MODE": "pure"}, clear=True), patch( "main.PureH5KwwGenerator", return_value=fake_generator, ): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=True, ) headers = client._h5_headers( "sign_in_report", url="https://nebula.kuaishou.com/rest/wd/encourage/unionTask/signIn/report", method="GET", ) self.assertEqual(headers["kww"], fake_result.kww) self.assertEqual(client.h5_cookie_dict["kwfv1"], fake_result.kwfv1) self.assertEqual(client.h5_cookie_dict["kwfcv1"], fake_result.kwfcv1) self.assertEqual(client.h5_cookie_dict["kwpsecproductname"], "kuaishou-growth") self.assertIn(f"kwfv1={fake_result.kwfv1}", headers["Cookie"]) self.assertIn("kwfcv1=3", client.h5_full_cookie) def test_h5_headers_fills_kws_default_ticket_when_missing(self): fake_kww = SimpleNamespace(kww="PnGU" + "B" * 170, kwfcv1="2", kwfv1="PnGU" + "B" * 170) fake_kww_generator = SimpleNamespace(get=lambda **_: fake_kww, close=lambda: None) fake_kws_ticket = SimpleNamespace( kwpsecproductname="kuaishou-growth", kwssectoken="S" * 64, kwscode="K" + "C" * 217 + "==", kwfv1="K" + "F" * 217 + "==", kww="K" + "F" * 217 + "==", cookie_fields=lambda include_fingerprint=True: { "kwpsecproductname": "kuaishou-growth", "kwssectoken": "S" * 64, "kwscode": "K" + "C" * 217 + "==", **({"kwfv1": "K" + "F" * 217 + "=="} if include_fingerprint else {}), }, ) with patch.dict(os.environ, {"KS_H5_KWW_MODE": "pure"}, clear=True), patch( "main.PureH5KwwGenerator", return_value=fake_kww_generator, ), patch("main.build_h5_kws_default_ticket", return_value=fake_kws_ticket) as build_kws: client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=True, ) headers = client._h5_headers( "sign_in_report", url="https://nebula.kuaishou.com/rest/wd/encourage/unionTask/signIn/report", method="GET", ) build_kws.assert_called_once() self.assertEqual(client.h5_cookie_dict["kwssectoken"], "S" * 64) self.assertEqual(client.h5_cookie_dict["kwscode"], "K" + "C" * 217 + "==") self.assertEqual(client.h5_cookie_dict["kwfv1"], fake_kww.kwfv1) self.assertIn("kwssectoken=", headers["Cookie"]) self.assertIn("kwscode=", headers["Cookie"]) def test_h5_headers_can_fill_kws_server_script_ticket_from_env(self): fake_kww = SimpleNamespace(kww="PnGU" + "E" * 170, kwfcv1="2", kwfv1="PnGU" + "E" * 170) fake_kww_generator = SimpleNamespace(get=lambda **_: fake_kww, close=lambda: None) fake_script_ticket = { "kwpsecproductname": "kuaishou-growth", "kwssectoken": "SERVER_SEC_TOKEN", "kwscode": "04dd303d63222chdfec0087d058fb1c4c5f2eb16eefgce98b2a503578e5f8433", } with patch.dict( os.environ, { "KS_H5_KWW_MODE": "pure", "KS_H5_KWS_MODE": "script", "KS_H5_KWS_SEC_TOKEN": "SERVER_SEC_TOKEN", }, clear=True, ), patch( "main.PureH5KwwGenerator", return_value=fake_kww_generator, ), patch( "main.build_h5_kws_script_ticket", return_value=fake_script_ticket, ) as build_script, patch( "main.build_h5_kws_default_ticket", side_effect=AssertionError("script mode should use secToken + sign script"), ): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=True, ) headers = client._h5_headers( "sign_in_report", url="https://nebula.kuaishou.com/rest/wd/encourage/unionTask/signIn/report", method="GET", ) build_script.assert_called_once_with( sec_token="SERVER_SEC_TOKEN", product_name="kuaishou-growth", ) self.assertEqual(client.h5_cookie_dict["kwssectoken"], "SERVER_SEC_TOKEN") self.assertEqual( client.h5_cookie_dict["kwscode"], "04dd303d63222chdfec0087d058fb1c4c5f2eb16eefgce98b2a503578e5f8433", ) self.assertIn("kwssectoken=SERVER_SEC_TOKEN", headers["Cookie"]) self.assertIn("kwscode=04dd303d63222chdfec0087d058fb1c4c5f2eb16eefgce98b2a503578e5f8433", headers["Cookie"]) def test_h5_headers_keeps_existing_kws_ticket(self): fake_kww = SimpleNamespace(kww="PnGU" + "D" * 170, kwfcv1="2", kwfv1="PnGU" + "D" * 170) fake_kww_generator = SimpleNamespace(get=lambda **_: fake_kww, close=lambda: None) with patch.dict(os.environ, {"KS_H5_KWW_MODE": "pure"}, clear=True), patch( "main.PureH5KwwGenerator", return_value=fake_kww_generator, ), patch( "main.build_h5_kws_default_ticket", side_effect=AssertionError("existing KWS ticket must be preserved"), ): client = KsNebulaClient( full_cookie=( "userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; " "kuaishou.api_st=API_ST; kwssectoken=OLD_SEC; kwscode=OLD_CODE" ), client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", "kwssectoken": "OLD_SEC", "kwscode": "OLD_CODE", }, timeout=30, dry_run=True, ) headers = client._h5_headers( "sign_in_report", url="https://nebula.kuaishou.com/rest/wd/encourage/unionTask/signIn/report", method="GET", ) self.assertEqual(client.h5_cookie_dict["kwssectoken"], "OLD_SEC") self.assertEqual(client.h5_cookie_dict["kwscode"], "OLD_CODE") self.assertIn("kwssectoken=OLD_SEC", headers["Cookie"]) self.assertIn("kwscode=OLD_CODE", headers["Cookie"]) def test_ad_sequence_skips_report_when_fetch_fails(self): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=False, ) client.fetch_ad = lambda kind: ResponseRecord( f"{kind}_ad_fetch", "看广告得金币拉取", 2637, 200, False, "", {"result": 1, "errorMsg": "INVALID_REQUEST"}, "", ) def fail_report(kind, ad_wait): raise AssertionError("report should not run after failed ad fetch") client._wait_then_report = fail_report records = client.run_ad_sequence("normal", 0) self.assertEqual(len(records), 1) self.assertEqual(records[0].label, "normal_ad_fetch") def test_ad_sequence_skips_report_when_expected_reward_is_too_low(self): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=False, ) client.ad_materials["normal"] = object() client.fetch_ad = lambda kind: ResponseRecord( f"{kind}_ad_fetch", "看广告得金币拉取", 2637, 200, True, "", { "result": 1, "feeds": [ { "ad": { "adDataV2": { "templateDatas": [ {"data": json.dumps({"neoCount": 1})}, ] } } } ], }, "", ) def fail_report(kind, ad_wait): raise AssertionError("report should not run for skipped low reward ad") client._wait_then_report = fail_report records = client.run_ad_sequence("normal", 0, skip_reward_leq=1) self.assertEqual(len(records), 2) self.assertEqual(records[1].label, "normal_ad_skip") self.assertTrue(records[1].ok) self.assertEqual(records[1].response_json["expectedReward"], 1) def test_normal_ad_sequence_skips_fetch_during_task_countdown(self): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=False, ) client._update_ad_fetch_contexts( { "data": { "dailyTasks": [ { "name": "看广告得金币", "linkUrl": "eyJidXNpbmVzc0lkIjo2NzIsInBhZ2VJZCI6MTExMDEsInN1YlBhZ2VJZCI6MTAwMDI2MzY3LCJwb3NJZCI6MjQwNjd9", "extParam": { "descCountDown": 2411231, "buttonCountDownToast": "休息一下,稍后再来领取金币", }, } ] } } ) def fail_fetch(kind): raise AssertionError("normal ad fetch should not run during countdown") client.fetch_ad = fail_fetch records = client.run_ad_sequence("normal", 0) self.assertEqual(len(records), 1) self.assertEqual(records[0].label, "normal_ad_skip") self.assertEqual(records[0].response_json["reason"], "task_countdown") self.assertEqual(records[0].response_json["countdownMs"], 2411231) def test_run_all_once_repeats_normal_ad_count(self): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=False, ) called = [] def ok_record(label, title, request_id): return ResponseRecord(label, title, request_id, 200, True, "", {"result": 1}, "") client.query_balance = lambda: ok_record("login_check", "登录态/余额检查", 1286) client.task_list = lambda: ok_record("task_list", "任务列表", 1383) client.sign_in_resource = lambda: ok_record("sign_in_resource", "签到资源", 1476) client.sign_in = lambda: ok_record("sign_in_report", "签到上报", 1633) client.treasure_box_info = lambda: ok_record("treasure_info", "宝箱信息", 1376) client.open_treasure_box = lambda: ok_record("treasure_open", "开宝箱", 2105) client.final_balance = lambda: ok_record("final_balance", "最终余额", 2709) def fake_ad_sequence(kind, ad_wait): called.append((kind, ad_wait)) return [ ok_record(f"{kind}_ad_fetch", f"{kind} 拉取", 100), ok_record(f"{kind}_ad_report", f"{kind} 上报", 101), ] client.run_ad_sequence = fake_ad_sequence with tempfile.TemporaryDirectory() as tmp: exit_code = client.run_all_once(ad_wait=0, out_dir=Path(tmp), normal_ad_count=3) self.assertEqual(exit_code, 0) self.assertEqual([kind for kind, _ in called], ["sign_in", "treasure", "normal", "normal", "normal"]) def test_parser_accepts_rotate_h5_and_strict_device_flags(self): args = build_parser().parse_args(["--rotate-h5-device", "--strict-device-online"]) self.assertTrue(args.rotate_h5_device) self.assertTrue(args.strict_device_online) def test_args_to_device_options_propagates_rotate_and_strict(self): args = build_parser().parse_args( ["--memory-device", "--rotate-h5-device", "--strict-device-online"] ) options = args_to_device_options(args) self.assertTrue(options.rotate_h5_device) self.assertTrue(options.strict_device_online) def test_rotate_h5_device_syncs_h5_cookie_with_memory_device(self): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=True, memory_device=True, device_online=False, device_seed=1357, rotate_h5_device=True, ) self.assertEqual(client.h5_cookie_dict["did"], client.device_profile.did) self.assertEqual(client.h5_cookie_dict["egid"], client.device_profile.egid) self.assertEqual(client.h5_cookie_dict["oaid"], client.device_profile.runtime_hints.oaid) self.assertIn(f"did={client.device_profile.did}", client.h5_full_cookie) self.assertNotIn("did=ANDROID_oldoldoldold", client.h5_full_cookie) def test_rotate_h5_device_carries_new_device_in_h5_request(self): original_cookie = { "kpn": "NEBULA", "kpf": "ANDROID_PHONE", "userId": "1579452490", "did": "ANDROID_oldoldoldold", "egid": "DFPOLD", "oDid": "ANDROID_OLD_ODID", "rdid": "ANDROID_OLD_RDID", "token": "TOKEN", "kuaishou.api_st": "API_ST", } client = KsNebulaClient( full_cookie="; ".join(f"{k}={v}" for k, v in original_cookie.items()), client_salt="salt", cookie_dict=dict(original_cookie), timeout=30, dry_run=True, memory_device=True, device_online=False, device_seed=1357, rotate_h5_device=True, ) captured = {} def fake_request(label, title, request_id, method, url, headers, body=None): captured["headers"] = headers return ResponseRecord(label, title, request_id, 200, True, url, {"result": 1}, "") client._request = fake_request client.sign_in_resource() cookie_header = captured["headers"]["Cookie"] self.assertIn(f"did={client.device_profile.did}", cookie_header) self.assertNotIn("did=ANDROID_oldoldoldold", cookie_header) def test_strict_device_online_raises_on_bootstrap_failure(self): with patch("main.run_online_bootstrap", side_effect=RuntimeError("dfp unavailable")): with self.assertRaises(SystemExit): KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=False, memory_device=True, device_online=True, device_seed=97531, strict_device_online=True, ) def test_strict_device_online_raises_when_bootstrap_returns_no_egid(self): def did_only_bootstrap(profile, timeout): del timeout profile.apply_cloud_identity("ANDROID_f05497e9cef09a7f", 2) return {"identity": {"did": "ANDROID_f05497e9cef09a7f", "cdid_tag": 2, "egid": ""}} with patch("main.run_online_bootstrap", side_effect=did_only_bootstrap): with self.assertRaises(SystemExit): KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=False, memory_device=True, device_online=True, device_seed=97531, strict_device_online=True, ) def test_strict_device_online_midrun_failure_keeps_current_device(self): calls = {"n": 0} def bootstrap(profile, timeout): del timeout calls["n"] += 1 if calls["n"] == 1: profile.apply_cloud_identity( "ANDROID_f05497e9cef09a7f", 2, "DFP" + "A" * 61, ) return { "identity": { "did": "ANDROID_f05497e9cef09a7f", "cdid_tag": 2, "egid": "DFP" + "A" * 61, } } raise RuntimeError("gdfp report unavailable") with patch("main.run_online_bootstrap", side_effect=bootstrap): client = KsNebulaClient( full_cookie="userId=1579452490; did=ANDROID_oldoldoldold; token=TOKEN; kuaishou.api_st=API_ST", client_salt="salt", cookie_dict={ "userId": "1579452490", "did": "ANDROID_oldoldoldold", "token": "TOKEN", "kuaishou.api_st": "API_ST", }, timeout=30, dry_run=False, memory_device=True, device_online=True, device_seed=97531, strict_device_online=True, device_low_coin_threshold=20, device_max_switches=1, ) first_did = client.cookie_dict["did"] low_coin_record = ResponseRecord( label="normal_ad_report", title="看广告得金币上报", request_id=2700, status=200, ok=True, url="", response_json={"result": 1, "data": {"neoAmount": 5}}, response_text="", ) rotated = client.maybe_rotate_device_for_record(low_coin_record) self.assertTrue(rotated) self.assertEqual(client.cookie_dict["did"], first_did) self.assertEqual(calls["n"], 2) if __name__ == "__main__": unittest.main()