增加更多任务
This commit is contained in:
parent
4b342ff9c2
commit
73e61cab4e
25
findings.md
25
findings.md
@ -476,6 +476,31 @@
|
|||||||
- 端到端验证后 19+291 测试回归无新增失败。诊断体系(JSONL 探针/
|
- 端到端验证后 19+291 测试回归无新增失败。诊断体系(JSONL 探针/
|
||||||
strE notes)保留, 供后续问题定位。
|
strE notes)保留, 供后续问题定位。
|
||||||
|
|
||||||
|
## 2026-08-27 - HAR 完整度审计: 补漏上报 + seed/counter 细节
|
||||||
|
|
||||||
|
- 逐请求对比 07_10 HAR 的真机广告周期发现缺口(金币变少/风控画像来源):
|
||||||
|
1. **`/rest/r/ad/exposure/report`(ad_exit 完成事件)缺失** —— 真机在
|
||||||
|
task/report 前数百毫秒发出, bizStr 为明文 JSON(pageId/subPageId/
|
||||||
|
llsid/creativeId/uid/taskId/eventType/sessionId/materialDuration/
|
||||||
|
viewDuration/isCompleted/items), 全字段可从素材推导。已实现并在
|
||||||
|
线验证: exposure result=1 + task report result=1(+611 金币)。
|
||||||
|
2. **adlog 播放事件流缺失**(photo/action, 每次观看 15~25 条): `log`
|
||||||
|
参数为自定义 "gzip2" 二进制编码, base64 后非 gzip/zlib, 项目无
|
||||||
|
既有逆向 —— 短期不可复现, 已用 exposure ad_exit 作主要完成信号,
|
||||||
|
脚本 TODO 标注。这是剩余最大的播放信号缺口。
|
||||||
|
3. 页面加载 H5 调用补齐: signIn/resource + retain/show + todo/tasks
|
||||||
|
(真机赚钱页每次加载必发)。
|
||||||
|
4. misc2/ulog 埋点未发送(show_event 任务曝光流等), 已知缺口未实现。
|
||||||
|
- seed/counter 细节审查结论:
|
||||||
|
- 10418 seed=srand(启动秒);rand()+1 ✓(findings 已有 1785049292→
|
||||||
|
0x6f5d0faa 精确复现记录); counter 起步 0x5F+1 ✓(真机首见 ~0x60)。
|
||||||
|
- **修正**: 真机进程内 fetch/exposure/report 之间还有日志等原生请求
|
||||||
|
推进共享 counter(连续 sig3 有间隙); 我们原实现严格 +1 是机器特征,
|
||||||
|
已改为每周期结束推进 2~6 随机间隙。
|
||||||
|
- H5 sig3 counter(独立 envelope)按抓包递增 ✓; elapsed=(uptime<<16)|ms、
|
||||||
|
state=0x10000|16bit、seed 每会话随机, 均按抓包字段格式。
|
||||||
|
- 在线端到端再验证: fetch→观看→exposure(result=1)→report(+611 金币)。
|
||||||
|
|
||||||
---
|
---
|
||||||
*每执行2次查看/浏览器/搜索操作后更新此文件*
|
*每执行2次查看/浏览器/搜索操作后更新此文件*
|
||||||
*防止视觉信息丢失*
|
*防止视觉信息丢失*
|
||||||
|
|||||||
245
ksjsb.py
245
ksjsb.py
@ -36,8 +36,12 @@
|
|||||||
T1/T2 密码表 base64 内嵌(提取自 libkwsgmain.so)
|
T1/T2 密码表 base64 内嵌(提取自 libkwsgmain.so)
|
||||||
|
|
||||||
TODO(抓包无法覆盖):
|
TODO(抓包无法覆盖):
|
||||||
- 广告「打开详情/落地页」属第三方广告 SDK 行为, 不在脚本范围;
|
- adlog 播放事件流(POST adlog.e.kuaishou.com/rest/nebula/log/ad/photo/action,
|
||||||
奖励由 ad/task/report 直接发放。
|
真机每次观看发 15~25 条, `log` 为自定义 "gzip2" 二进制编码, 未逆向):
|
||||||
|
这是最大的缺失播放信号; 已用 exposure/report(ad_exit, 明文 bizStr)
|
||||||
|
补完成事件作为主要替代。
|
||||||
|
- misc2 埋点(任务曝光/点击 show_event 流)未发送; ulog 加密日志未发送。
|
||||||
|
- 广告「打开详情/落地页」属第三方广告 SDK 行为, 不在脚本范围。
|
||||||
- API 请求用 requests(HTTP/1.1); 真机为 OkHttp/Cronet(H2/QUIC),
|
- API 请求用 requests(HTTP/1.1); 真机为 OkHttp/Cronet(H2/QUIC),
|
||||||
传输指纹差异已知, 未在本脚本处理。
|
传输指纹差异已知, 未在本脚本处理。
|
||||||
|
|
||||||
@ -5197,11 +5201,23 @@ def signed_api_url(
|
|||||||
|
|
||||||
REWARD_SDK = "95147564-9763-4413-a937-6f0e3c12caf1"
|
REWARD_SDK = "95147564-9763-4413-a937-6f0e3c12caf1"
|
||||||
APP_NAME = "快手极速版"
|
APP_NAME = "快手极速版"
|
||||||
AD_FETCH_FALLBACK_BUSINESS = {"sign_in": 605, "treasure": 606, "normal": 672}
|
AD_FETCH_FALLBACK_BUSINESS = {"sign_in": 605, "treasure": 606, "normal": 672, "goods": 7038}
|
||||||
AD_FETCH_FALLBACK_SCENE = {
|
AD_FETCH_FALLBACK_SCENE = {
|
||||||
"sign_in": (11101, 100024058, 20342),
|
"sign_in": (11101, 100024058, 20342),
|
||||||
"treasure": (11101, 100024064, 20346),
|
"treasure": (11101, 100024064, 20346),
|
||||||
"normal": (11101, 100026367, 24067),
|
"normal": (11101, 100026367, 24067),
|
||||||
|
"goods": (11101, 100074584, 96134),
|
||||||
|
}
|
||||||
|
# 高频任务的任务位 materialKey 前缀 —— 2026-08-27 抓包 + 实况任务池
|
||||||
|
# (任务按账号/日期轮换, 前缀集合覆盖已观察到的全部变体)
|
||||||
|
HF_TASK_PREFIXES = {
|
||||||
|
"TASK_LIST_20048_": "走路赚金币",
|
||||||
|
"TASK_LIST_60151_": "搜好货看广告",
|
||||||
|
"TASK_LIST_60006_": "搜索看广告",
|
||||||
|
"TASK_LIST_176_": "搜索赚金币",
|
||||||
|
"TASK_LIST_148_": "逛街领金币",
|
||||||
|
"TASK_LIST_60230_": "搜短剧看广告",
|
||||||
|
"TASK_LIST_20505_": "刷广告视频赚金币",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -5832,6 +5848,9 @@ class KsJsB:
|
|||||||
self.ad_task_countdown_ms: dict[str, int] = {}
|
self.ad_task_countdown_ms: dict[str, int] = {}
|
||||||
self.task_ad_slot: dict[str, Any] | None = None
|
self.task_ad_slot: dict[str, Any] | None = None
|
||||||
self.task_treasure_slot: dict[str, Any] | None = None
|
self.task_treasure_slot: dict[str, Any] | None = None
|
||||||
|
self.hf_task_slots: dict[str, dict[str, Any]] = {} # 前缀 -> 任务位(走路/搜好货/搜索等)
|
||||||
|
self.dsp_tasks: list[dict[str, Any]] = [] # extParam 带 reportUrl 的外部广告任务
|
||||||
|
self.goods_task_name: str = "搜好货看广告" # 泛化后实际命中的任务名
|
||||||
self.last_task_list_json: Any = None
|
self.last_task_list_json: Any = None
|
||||||
# 点击上报为运营分析事件, 非必需(宝箱开箱已验证无点击也可领奖);
|
# 点击上报为运营分析事件, 非必需(宝箱开箱已验证无点击也可领奖);
|
||||||
# 首次失败后停发, 避免重复触发风控
|
# 首次失败后停发, 避免重复触发风控
|
||||||
@ -6094,6 +6113,16 @@ class KsJsB:
|
|||||||
def treasure_box_info(self) -> Record:
|
def treasure_box_info(self) -> Record:
|
||||||
return self._h5_get("treasure_info", "宝箱信息", "/rest/wd/encourage/unionTask/treasureBox/info", {"source": SOURCE})
|
return self._h5_get("treasure_info", "宝箱信息", "/rest/wd/encourage/unionTask/treasureBox/info", {"source": SOURCE})
|
||||||
|
|
||||||
|
def sign_in_resource(self) -> Record:
|
||||||
|
"""真机赚钱页每次加载必发(2026-08-27 抓包)。"""
|
||||||
|
return self._h5_get("sign_in_resource", "签到资源", "/rest/wd/encourage/unionTask/signIn/resource", {})
|
||||||
|
|
||||||
|
def retain_show(self) -> Record:
|
||||||
|
return self._h5_get("retain_show", "留存弹窗", "/rest/wd/encourage/unionTask/retain/show", {"refresh": "false"})
|
||||||
|
|
||||||
|
def todo_tasks(self) -> Record:
|
||||||
|
return self._h5_get("todo_tasks", "待办任务", "/rest/n/nebula/activity/earn/todo/tasks", {})
|
||||||
|
|
||||||
def treasure_box_report(self, tracking: dict[str, Any]) -> Record:
|
def treasure_box_report(self, tracking: dict[str, Any]) -> Record:
|
||||||
body_text = compact_json({
|
body_text = compact_json({
|
||||||
"imei": "",
|
"imei": "",
|
||||||
@ -6233,7 +6262,7 @@ class KsJsB:
|
|||||||
headers["Cookie"] = self._api_cookie_header()
|
headers["Cookie"] = self._api_cookie_header()
|
||||||
record = self._request(
|
record = self._request(
|
||||||
f"{kind}_ad_fetch",
|
f"{kind}_ad_fetch",
|
||||||
{"sign_in": "签到广告拉取", "treasure": "宝箱广告拉取", "normal": "看广告得金币拉取"}[kind],
|
{"sign_in": "签到广告拉取", "treasure": "宝箱广告拉取", "normal": "看广告得金币拉取", "goods": "搜好货广告拉取"}[kind],
|
||||||
"POST", url, headers, urllib.parse.urlencode(body_pairs).encode("utf-8"),
|
"POST", url, headers, urllib.parse.urlencode(body_pairs).encode("utf-8"),
|
||||||
notes={
|
notes={
|
||||||
"strE": str_e.decode("utf-8", "replace"),
|
"strE": str_e.decode("utf-8", "replace"),
|
||||||
@ -6251,7 +6280,7 @@ class KsJsB:
|
|||||||
material = self.ad_materials.get(kind)
|
material = self.ad_materials.get(kind)
|
||||||
if material is None:
|
if material is None:
|
||||||
material = fallback_ad_material(kind) if self.dry_run else None
|
material = fallback_ad_material(kind) if self.dry_run else None
|
||||||
title = {"sign_in": "签到广告上报", "treasure": "宝箱广告上报", "normal": "看广告得金币上报"}[kind]
|
title = {"sign_in": "签到广告上报", "treasure": "宝箱广告上报", "normal": "看广告得金币上报", "goods": "搜好货广告上报"}[kind]
|
||||||
if material is None:
|
if material is None:
|
||||||
record = Record(f"{kind}_ad_report", title)
|
record = Record(f"{kind}_ad_report", title)
|
||||||
record.error = "missing_ad_material"
|
record.error = "missing_ad_material"
|
||||||
@ -6265,6 +6294,44 @@ class KsJsB:
|
|||||||
headers["Cookie"] = self._api_cookie_header()
|
headers["Cookie"] = self._api_cookie_header()
|
||||||
return self._request(f"{kind}_ad_report", title, "POST", url, headers, urllib.parse.urlencode(body_pairs).encode("utf-8"))
|
return self._request(f"{kind}_ad_report", title, "POST", url, headers, urllib.parse.urlencode(body_pairs).encode("utf-8"))
|
||||||
|
|
||||||
|
def exposure_report(self, kind: str) -> Record:
|
||||||
|
"""广告 ad_exit 完成事件(真机在 task/report 前数百毫秒发出, 明文 bizStr)。
|
||||||
|
|
||||||
|
风控用观看完成信号评估奖励质量; 缺失会压低广告质量分。字段全部
|
||||||
|
可从素材推导(2026-07-10 抓包实证)。
|
||||||
|
"""
|
||||||
|
material = self.ad_materials.get(kind)
|
||||||
|
if material is None:
|
||||||
|
material = fallback_ad_material(kind) if self.dry_run else None
|
||||||
|
if material is None:
|
||||||
|
record = Record(f"{kind}_ad_exposure", "广告完成上报")
|
||||||
|
record.error = "missing_ad_material"
|
||||||
|
return record
|
||||||
|
uid = self.cookie.get("userId") or self.cookie.get("ud") or ""
|
||||||
|
duration_s = max(1, material.material_time // 1000)
|
||||||
|
session_id = material.session_id or f"adNeo-{uid}-{material.sub_page_id}-{int(time.time() * 1000)}"
|
||||||
|
biz = {
|
||||||
|
"pageId": material.page_id,
|
||||||
|
"subPageId": material.sub_page_id,
|
||||||
|
"llsid": str(material.llsid or "0"),
|
||||||
|
"creativeId": _safe_int(material.creative_id),
|
||||||
|
"uid": uid,
|
||||||
|
"taskId": material.business_id,
|
||||||
|
"eventType": "ad_exit",
|
||||||
|
"sessionId": session_id,
|
||||||
|
"materialDuration": duration_s,
|
||||||
|
"isInflow": False,
|
||||||
|
"viewDuration": duration_s,
|
||||||
|
"isCompleted": True,
|
||||||
|
"extMap": {},
|
||||||
|
"items": [],
|
||||||
|
}
|
||||||
|
body_pairs = [("bizStr", compact_json(biz))] + self._api_body_common()
|
||||||
|
url = signed_api_url("/rest/r/ad/exposure/report", self._api_params(), body_pairs, self.client_salt, self._resolve_api_state())
|
||||||
|
headers = dict(API_HEADERS)
|
||||||
|
headers["Cookie"] = self._api_cookie_header()
|
||||||
|
return self._request(f"{kind}_ad_exposure", "广告完成上报", "POST", url, headers, urllib.parse.urlencode(body_pairs).encode("utf-8"))
|
||||||
|
|
||||||
def _update_ad_contexts(self, data: Any) -> None:
|
def _update_ad_contexts(self, data: Any) -> None:
|
||||||
if not isinstance(data, dict):
|
if not isinstance(data, dict):
|
||||||
return
|
return
|
||||||
@ -6297,21 +6364,48 @@ class KsJsB:
|
|||||||
ext_param = task.get("extParam")
|
ext_param = task.get("extParam")
|
||||||
if isinstance(ext_param, dict) and kind == "normal":
|
if isinstance(ext_param, dict) and kind == "normal":
|
||||||
self.ad_task_countdown_ms[kind] = max(0, _safe_int(ext_param.get("descCountDown"), 0))
|
self.ad_task_countdown_ms[kind] = max(0, _safe_int(ext_param.get("descCountDown"), 0))
|
||||||
# 任务位提取(看广告任务 + 宝箱挂件)
|
# 任务位提取(看广告任务/宝箱挂件/高频任务/外部广告任务)
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
if not isinstance(task, dict):
|
if not isinstance(task, dict):
|
||||||
continue
|
continue
|
||||||
|
ext_param = task.get("extParam")
|
||||||
|
ext_param = ext_param if isinstance(ext_param, dict) else {}
|
||||||
tracking = task.get("eventTrackingLogInfo")
|
tracking = task.get("eventTrackingLogInfo")
|
||||||
if not isinstance(tracking, dict):
|
if not isinstance(tracking, dict):
|
||||||
tracking = dig(task, "extParam.eventTrackingLogInfo")
|
tracking = dig(task, "extParam.eventTrackingLogInfo")
|
||||||
if not isinstance(tracking, dict) or not tracking:
|
if not isinstance(tracking, dict) or not tracking:
|
||||||
continue
|
tracking = {}
|
||||||
material_key = str(tracking.get("materialKey") or "")
|
material_key = str(tracking.get("materialKey") or "")
|
||||||
resource_id = str(tracking.get("resourceId") or "")
|
resource_id = str(tracking.get("resourceId") or "")
|
||||||
if material_key.startswith("TASK_LIST_17_") and self.task_ad_slot is None:
|
if material_key.startswith("TASK_LIST_17_") and self.task_ad_slot is None:
|
||||||
self.task_ad_slot = tracking
|
self.task_ad_slot = tracking
|
||||||
if resource_id == "externalFeed_externalTreasureChestWidget" and self.task_treasure_slot is None:
|
if resource_id == "externalFeed_externalTreasureChestWidget" and self.task_treasure_slot is None:
|
||||||
self.task_treasure_slot = tracking
|
self.task_treasure_slot = tracking
|
||||||
|
for prefix in HF_TASK_PREFIXES:
|
||||||
|
if material_key.startswith(prefix):
|
||||||
|
self.hf_task_slots.setdefault(prefix, tracking)
|
||||||
|
# "搜好货类"看广告任务泛化: linkType=101 且 linkUrl 解码为
|
||||||
|
# matrixMixedAd 场景(pageId=11101, 非基础 605/606/672 的 businessId,
|
||||||
|
# 如搜好货 7038 / 搜短剧 7052) -> 接入原生 reward 链 kind=goods
|
||||||
|
decoded = decode_neo_params(str(task.get("linkUrl") or ""))
|
||||||
|
if (
|
||||||
|
_safe_int(task.get("linkType")) == 101
|
||||||
|
and _safe_int(decoded.get("pageId")) == 11101
|
||||||
|
and _safe_int(decoded.get("businessId")) not in (0, 605, 606, 672)
|
||||||
|
):
|
||||||
|
self.ad_fetch_neo_params["goods"] = str(task.get("linkUrl") or "")
|
||||||
|
self.ad_fetch_scenes["goods"] = (
|
||||||
|
_safe_int(decoded.get("pageId"), AD_FETCH_FALLBACK_SCENE["goods"][0]),
|
||||||
|
_safe_int(decoded.get("subPageId"), AD_FETCH_FALLBACK_SCENE["goods"][1]),
|
||||||
|
_safe_int(decoded.get("posId"), AD_FETCH_FALLBACK_SCENE["goods"][2]),
|
||||||
|
)
|
||||||
|
self.ad_fetch_business["goods"] = _safe_int(decoded.get("businessId"), AD_FETCH_FALLBACK_BUSINESS["goods"])
|
||||||
|
self.goods_task_name = str(task.get("name") or "搜好货看广告")
|
||||||
|
# 外部广告任务(extParam 带 reportUrl, 如"打开千问必得金币"): 全链客户端可复现
|
||||||
|
if ext_param.get("reportUrl") and tracking:
|
||||||
|
entry = {"tracking": tracking, "task": task, "ext": ext_param}
|
||||||
|
if not any(d["tracking"].get("eventTrackingTaskId") == tracking.get("eventTrackingTaskId") for d in self.dsp_tasks):
|
||||||
|
self.dsp_tasks.append(entry)
|
||||||
|
|
||||||
# ---------------- 任务步骤 ----------------
|
# ---------------- 任务步骤 ----------------
|
||||||
def _best_effort_click(self, slot: dict[str, Any] | None, *, threshold: bool = False) -> None:
|
def _best_effort_click(self, slot: dict[str, Any] | None, *, threshold: bool = False) -> None:
|
||||||
@ -6445,6 +6539,8 @@ class KsJsB:
|
|||||||
if wait_seconds > 0:
|
if wait_seconds > 0:
|
||||||
print(f"[看广告] 等待广告模拟观看 {wait_seconds:.1f}s ...")
|
print(f"[看广告] 等待广告模拟观看 {wait_seconds:.1f}s ...")
|
||||||
time.sleep(wait_seconds)
|
time.sleep(wait_seconds)
|
||||||
|
# 完成事件 -> 任务上报(真机顺序: exposure ad_exit 先于 task/report)
|
||||||
|
self._print(self.exposure_report("normal"))
|
||||||
report = self.report_ad("normal")
|
report = self.report_ad("normal")
|
||||||
self._print(report)
|
self._print(report)
|
||||||
if not report.ok:
|
if not report.ok:
|
||||||
@ -6452,6 +6548,10 @@ class KsJsB:
|
|||||||
break
|
break
|
||||||
done += 1
|
done += 1
|
||||||
print(f"[看广告] 第 {done}/{planned} 次完成")
|
print(f"[看广告] 第 {done}/{planned} 次完成")
|
||||||
|
# 真机进程内还有日志等原生请求推进共享 10418 counter,
|
||||||
|
# 我们的连续 API 请求之间补 2~6 的随机间隙避免严格 +1 的机器特征
|
||||||
|
state = self._resolve_api_state()
|
||||||
|
state.counter = (state.counter + random.randint(2, 6)) & 0xFFFFFFFF
|
||||||
if not self.dry_run and done < planned:
|
if not self.dry_run and done < planned:
|
||||||
time.sleep(random.uniform(2.0, 6.0))
|
time.sleep(random.uniform(2.0, 6.0))
|
||||||
if done % 5 == 0:
|
if done % 5 == 0:
|
||||||
@ -6459,6 +6559,113 @@ class KsJsB:
|
|||||||
print(f"[看广告] 完成 {done}/{planned} 次")
|
print(f"[看广告] 完成 {done}/{planned} 次")
|
||||||
return done
|
return done
|
||||||
|
|
||||||
|
# ---------------- 高频任务(走路/搜好货/搜索) + 外部广告任务 ----------------
|
||||||
|
@staticmethod
|
||||||
|
def build_dsp_body(entry: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
"""外部广告 dsp/report body: 全部来自任务列表下发(2026-08-27 抓包映射)。
|
||||||
|
|
||||||
|
reqId/impId/trackers 直拷 extParam; admInfo 客户端拼装
|
||||||
|
(title=任务名, subTitle=任务 desc, dpLink=link.linkUrl,
|
||||||
|
h5Link=extParam.downloadUrl); reportType=1。
|
||||||
|
"""
|
||||||
|
task, ext = entry["task"], entry["ext"]
|
||||||
|
adm_info = {
|
||||||
|
"adId": ext.get("adId"),
|
||||||
|
"title": task.get("name"),
|
||||||
|
"subTitle": task.get("desc"),
|
||||||
|
"dpLink": dig(task, "link.linkUrl") or "",
|
||||||
|
"h5Link": ext.get("downloadUrl") or "",
|
||||||
|
"appIdentifier": ext.get("appIdentifier") or "",
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"reqId": ext.get("reqId"),
|
||||||
|
"bidId": ext.get("bidId"),
|
||||||
|
"impId": ext.get("impId"),
|
||||||
|
"admInfo": adm_info,
|
||||||
|
"adBidType": ext.get("adBidType"),
|
||||||
|
"dspProvider": ext.get("dspProvider"),
|
||||||
|
"reportType": 1,
|
||||||
|
"downEndTrackersUrl": ext.get("downEndTrackersUrl"),
|
||||||
|
"dplkTrackersUrl": ext.get("dplkTrackersUrl"),
|
||||||
|
"implTrackersUrl": ext.get("implTrackersUrl"),
|
||||||
|
}
|
||||||
|
|
||||||
|
def dsp_report(self, entry: dict[str, Any]) -> Record:
|
||||||
|
"""外部广告曝光上报(POST 明文 JSON); 奖励需广告侧 S2S 激活回调。"""
|
||||||
|
body_text = compact_json(self.build_dsp_body(entry))
|
||||||
|
url = f"{NEBULA_BASE}{entry['ext'].get('reportUrl') or '/rest/n/nebula/exchange/dsp/report'}?{urllib.parse.urlencode({'__NS_sig3': self._h5_sig3(body=body_text, method='POST'), 'sigCatVer': '1'})}"
|
||||||
|
headers = self._h5_headers(method="POST", url=url, body=body_text, json_body=True)
|
||||||
|
name = str(entry["task"].get("name") or "外部广告")[:12]
|
||||||
|
return self._request("dsp_report", f"外部广告曝光上报({name})", "POST", url, headers, body_text.encode("utf-8"))
|
||||||
|
|
||||||
|
def hf_tasks_step(self) -> None:
|
||||||
|
"""走路/搜好货/搜索任务位点击 + 外部广告(dsp)任务曝光上报。
|
||||||
|
|
||||||
|
依据 2026-08-27 三份抓包: 这三类任务的奖励链主体在原生侧
|
||||||
|
(步数/搜索行为/外部 App 激活 S2S), 客户端可见动作为任务位点击
|
||||||
|
与 dsp 曝光上报, 此处如实复现。
|
||||||
|
"""
|
||||||
|
if not (self.hf_task_slots or self.dsp_tasks):
|
||||||
|
print("[高频任务] 任务列表未发现走路/搜好货/搜索/外部广告任务")
|
||||||
|
return
|
||||||
|
for prefix, name in HF_TASK_PREFIXES.items():
|
||||||
|
slot = self.hf_task_slots.get(prefix)
|
||||||
|
if slot is None:
|
||||||
|
continue
|
||||||
|
print(f"[高频任务] {name}: 点击任务位")
|
||||||
|
self._best_effort_click(slot)
|
||||||
|
for entry in self.dsp_tasks:
|
||||||
|
task = entry["task"]
|
||||||
|
print(f"[外部广告] {task.get('name')}: 曝光上报")
|
||||||
|
self._print(self.dsp_report(entry))
|
||||||
|
|
||||||
|
def goods_ad_step(self, *, ad_wait: float, count: int) -> int:
|
||||||
|
"""搜好货看广告(原生 reward 链, businessId 7038, scene 取任务 linkUrl)。
|
||||||
|
|
||||||
|
抓包中该任务完成度经搜索行为推进; 纯广告观看是否计次由服务端
|
||||||
|
判定, 失败即止不强刷。
|
||||||
|
"""
|
||||||
|
if self.dry_run:
|
||||||
|
# dry-run 下没有任务列表上下文, 用 fallback scene 演示一次
|
||||||
|
fetch = self.fetch_ad("goods")
|
||||||
|
self._print(fetch)
|
||||||
|
if fetch.ok:
|
||||||
|
self.ad_materials["goods"] = fallback_ad_material("goods")
|
||||||
|
self._print(self.exposure_report("goods"))
|
||||||
|
self._print(self.report_ad("goods"))
|
||||||
|
return 1
|
||||||
|
if "goods" not in self.ad_fetch_scenes and "goods" not in self.ad_fetch_business:
|
||||||
|
print(f"[{self.goods_task_name}] 任务列表未提供该类广告场景, 跳过")
|
||||||
|
return 0
|
||||||
|
done = 0
|
||||||
|
for i in range(max(1, count)):
|
||||||
|
fetch = self.fetch_ad("goods")
|
||||||
|
self._print(fetch)
|
||||||
|
if not fetch.ok:
|
||||||
|
print("[搜好货] 拉取失败, 提前结束")
|
||||||
|
break
|
||||||
|
material = self.ad_materials.get("goods")
|
||||||
|
if material is None:
|
||||||
|
break
|
||||||
|
wait_seconds = (material.material_time / 1000.0) if ad_wait < 0 else max(0.0, ad_wait)
|
||||||
|
if wait_seconds > 0:
|
||||||
|
print(f"[{self.goods_task_name}] 等待广告模拟观看 {wait_seconds:.1f}s ...")
|
||||||
|
time.sleep(wait_seconds)
|
||||||
|
self._print(self.exposure_report("goods"))
|
||||||
|
report = self.report_ad("goods")
|
||||||
|
self._print(report)
|
||||||
|
if not report.ok:
|
||||||
|
print(f"[{self.goods_task_name}] 上报失败, 提前结束")
|
||||||
|
break
|
||||||
|
done += 1
|
||||||
|
print(f"[{self.goods_task_name}] 第 {done} 次完成")
|
||||||
|
state = self._resolve_api_state()
|
||||||
|
state.counter = (state.counter + random.randint(2, 6)) & 0xFFFFFFFF
|
||||||
|
if i + 1 < count:
|
||||||
|
time.sleep(random.uniform(2.0, 6.0))
|
||||||
|
print(f"[{self.goods_task_name}] 完成 {done} 次")
|
||||||
|
return done
|
||||||
|
|
||||||
|
|
||||||
def _new_uuid4() -> str:
|
def _new_uuid4() -> str:
|
||||||
import uuid
|
import uuid
|
||||||
@ -6487,25 +6694,39 @@ def run() -> int:
|
|||||||
print(f"===== 快手极速版每日任务{' (dry-run)' if dry_run else ''} =====")
|
print(f"===== 快手极速版每日任务{' (dry-run)' if dry_run else ''} =====")
|
||||||
if client.record_path is not None:
|
if client.record_path is not None:
|
||||||
print(f"诊断日志: {client.record_path}")
|
print(f"诊断日志: {client.record_path}")
|
||||||
print("--- 1/5 查询金币/余额 ---")
|
print("--- 1/7 查询金币/余额 ---")
|
||||||
client._print(client.basic_info())
|
client._print(client.basic_info())
|
||||||
|
|
||||||
print("--- 2/5 任务列表(刷新广告上下文) ---")
|
print("--- 2/7 任务列表(刷新广告上下文) ---")
|
||||||
client._print(client.task_list())
|
client._print(client.task_list())
|
||||||
|
# 真机赚钱页每次加载必发的其余 H5 调用(2026-08-27 抓包),
|
||||||
|
# 保持页面加载行为完整性
|
||||||
|
client._print(client.sign_in_resource())
|
||||||
|
client._print(client.retain_show())
|
||||||
|
client._print(client.todo_tasks())
|
||||||
|
|
||||||
print("--- 3/5 签到 ---")
|
print("--- 3/7 签到 ---")
|
||||||
client.sign_in_step()
|
client.sign_in_step()
|
||||||
|
|
||||||
print("--- 4/5 开宝箱 ---")
|
print("--- 4/7 开宝箱 ---")
|
||||||
client.treasure_box_step()
|
client.treasure_box_step()
|
||||||
|
|
||||||
print("--- 5/5 看广告得金币 ---")
|
print("--- 5/7 高频任务(走路/搜好货/搜索/外部广告) ---")
|
||||||
|
client.hf_tasks_step()
|
||||||
|
|
||||||
|
print("--- 6/7 看广告得金币 ---")
|
||||||
client.watch_ads_step(
|
client.watch_ads_step(
|
||||||
ad_wait=float(os.environ.get("KS_AD_WAIT_SECONDS", "-1")),
|
ad_wait=float(os.environ.get("KS_AD_WAIT_SECONDS", "-1")),
|
||||||
low=int(os.environ.get("KS_AD_COUNT_MIN", "20")),
|
low=int(os.environ.get("KS_AD_COUNT_MIN", "20")),
|
||||||
high=int(os.environ.get("KS_AD_COUNT_MAX", "30")),
|
high=int(os.environ.get("KS_AD_COUNT_MAX", "30")),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
print(f"--- 7/7 {client.goods_task_name} ---")
|
||||||
|
client.goods_ad_step(
|
||||||
|
ad_wait=float(os.environ.get("KS_AD_WAIT_SECONDS", "-1")),
|
||||||
|
count=int(os.environ.get("KS_GOODS_AD_COUNT", "1")),
|
||||||
|
)
|
||||||
|
|
||||||
print("--- 最终余额 ---")
|
print("--- 最终余额 ---")
|
||||||
client._print(client.basic_info())
|
client._print(client.basic_info())
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@ -332,6 +332,89 @@ class TaskSlotTests(unittest.TestCase):
|
|||||||
self.assertEqual(client.ad_fetch_scenes["normal"], (11101, 100026367, 24067))
|
self.assertEqual(client.ad_fetch_scenes["normal"], (11101, 100026367, 24067))
|
||||||
self.assertEqual(client.ad_task_countdown_ms["normal"], 30000)
|
self.assertEqual(client.ad_task_countdown_ms["normal"], 30000)
|
||||||
|
|
||||||
|
def test_update_ad_contexts_extracts_hf_and_dsp_tasks(self):
|
||||||
|
import base64 as b64
|
||||||
|
|
||||||
|
goods_link = b64.b64encode(
|
||||||
|
compact_json({"pageId": 11101, "subPageId": 100074584, "posId": 96134, "businessId": 7038}).encode()
|
||||||
|
).decode()
|
||||||
|
drama_link = b64.b64encode(
|
||||||
|
compact_json({"pageId": 11101, "subPageId": 100134702, "posId": 175158, "businessId": 7052}).encode()
|
||||||
|
).decode()
|
||||||
|
walk_slot = {"materialKey": "TASK_LIST_20048_WALKING_NOT_REWARDED", "eventTrackingTaskId": 20048}
|
||||||
|
goods_slot = {"materialKey": "TASK_LIST_60151_PROGRESS", "eventTrackingTaskId": 60151}
|
||||||
|
search_slot = {"materialKey": "TASK_LIST_176_ACTIVE_PASSIVE_TASK_UNFINISH", "eventTrackingTaskId": 176}
|
||||||
|
dsp_slot = {"materialKey": "60494_TASK_LIST_EXCHANGE_DSP_1", "eventTrackingTaskId": 60494}
|
||||||
|
dsp_task = {
|
||||||
|
"name": "打开千问必得金币",
|
||||||
|
"desc": "千问智能解答,高效省时间",
|
||||||
|
"link": {"linkUrl": "tongyi://deeplink?x=1"},
|
||||||
|
"eventTrackingLogInfo": dsp_slot,
|
||||||
|
"extParam": {
|
||||||
|
"reportUrl": "/rest/n/nebula/exchange/dsp/report",
|
||||||
|
"adId": "2162", "appIdentifier": "com.aliyun.tongyi",
|
||||||
|
"downloadUrl": "https://aisite.wejianzhan.com/site/x",
|
||||||
|
"reqId": "dsctREQ", "impId": "imp-1", "bidId": None,
|
||||||
|
"adBidType": 1, "dspProvider": "happygame",
|
||||||
|
"implTrackersUrl": ["http://t/1"], "downEndTrackersUrl": None, "dplkTrackersUrl": None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client = make_client()
|
||||||
|
client._update_ad_contexts({
|
||||||
|
"data": {
|
||||||
|
"dailyTasks": [
|
||||||
|
{"eventTrackingLogInfo": walk_slot},
|
||||||
|
# linkType=101 + pageId=11101 + businessId 7038 -> goods 场景(搜好货)
|
||||||
|
{"eventTrackingLogInfo": goods_slot, "linkUrl": goods_link, "linkType": 101, "name": "搜好货看广告"},
|
||||||
|
# 后出现的同形态任务(搜短剧 7052)覆盖 goods 场景(取最新)
|
||||||
|
{"eventTrackingLogInfo": search_slot, "linkType": 101, "linkUrl": drama_link, "name": "搜短剧看广告"},
|
||||||
|
dsp_task,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.assertEqual(client.hf_task_slots["TASK_LIST_20048_"], walk_slot)
|
||||||
|
self.assertEqual(client.hf_task_slots["TASK_LIST_60151_"], goods_slot)
|
||||||
|
self.assertEqual(client.hf_task_slots["TASK_LIST_176_"], search_slot)
|
||||||
|
self.assertEqual(client.ad_fetch_scenes["goods"], (11101, 100134702, 175158))
|
||||||
|
self.assertEqual(client.ad_fetch_business["goods"], 7052)
|
||||||
|
self.assertEqual(client.goods_task_name, "搜短剧看广告")
|
||||||
|
self.assertEqual(len(client.dsp_tasks), 1)
|
||||||
|
self.assertEqual(client.dsp_tasks[0]["task"], dsp_task)
|
||||||
|
|
||||||
|
def test_dsp_body_assembly(self):
|
||||||
|
"""admInfo 拼装规则与 2026-08-27 抓包映射一致。"""
|
||||||
|
entry = {
|
||||||
|
"task": {
|
||||||
|
"name": "打开千问必得金币",
|
||||||
|
"desc": "千问智能解答,高效省时间",
|
||||||
|
"link": {"linkUrl": "tongyi://www.tongyi.cn/x?src_pkg=zslytylhpl"},
|
||||||
|
},
|
||||||
|
"ext": {
|
||||||
|
"adId": "2162", "appIdentifier": "com.aliyun.tongyi",
|
||||||
|
"downloadUrl": "https://aisite.wejianzhan.com/site/y",
|
||||||
|
"reqId": "dsctREQ", "impId": "imp-1", "bidId": None,
|
||||||
|
"adBidType": 1, "dspProvider": "happygame",
|
||||||
|
"implTrackersUrl": ["http://t/1"], "downEndTrackersUrl": None, "dplkTrackersUrl": None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
body = KsJsB.build_dsp_body(entry)
|
||||||
|
self.assertEqual(
|
||||||
|
list(body.keys()),
|
||||||
|
["reqId", "bidId", "impId", "admInfo", "adBidType", "dspProvider", "reportType", "downEndTrackersUrl", "dplkTrackersUrl", "implTrackersUrl"],
|
||||||
|
)
|
||||||
|
self.assertEqual(body["reportType"], 1)
|
||||||
|
self.assertEqual(
|
||||||
|
body["admInfo"],
|
||||||
|
{
|
||||||
|
"adId": "2162",
|
||||||
|
"title": "打开千问必得金币",
|
||||||
|
"subTitle": "千问智能解答,高效省时间",
|
||||||
|
"dpLink": "tongyi://www.tongyi.cn/x?src_pkg=zslytylhpl",
|
||||||
|
"h5Link": "https://aisite.wejianzhan.com/site/y",
|
||||||
|
"appIdentifier": "com.aliyun.tongyi",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SequentialRunTests(unittest.TestCase):
|
class SequentialRunTests(unittest.TestCase):
|
||||||
def test_run_dry_run_completes_all_steps_without_network(self):
|
def test_run_dry_run_completes_all_steps_without_network(self):
|
||||||
@ -352,11 +435,16 @@ class SequentialRunTests(unittest.TestCase):
|
|||||||
os.environ[k] = v
|
os.environ[k] = v
|
||||||
self.assertEqual(code, 0)
|
self.assertEqual(code, 0)
|
||||||
text = buf.getvalue()
|
text = buf.getvalue()
|
||||||
for marker in ("1/5 查询金币/余额", "3/5 签到", "4/5 开宝箱", "5/5 看广告得金币", "最终余额", "任务执行完成"):
|
for marker in (
|
||||||
|
"1/7 查询金币/余额", "3/7 签到", "4/7 开宝箱",
|
||||||
|
"5/7 高频任务", "6/7 看广告得金币", "7/7 搜好货看广告", # dry-run 下无任务列表, 标签为默认名
|
||||||
|
"最终余额", "任务执行完成",
|
||||||
|
):
|
||||||
self.assertIn(marker, text)
|
self.assertIn(marker, text)
|
||||||
self.assertIn("开宝箱 dry-run method=POST", text)
|
self.assertIn("开宝箱 dry-run method=POST", text)
|
||||||
self.assertIn("/rest/e/reward/mixed/ad", text)
|
self.assertIn("/rest/e/reward/mixed/ad", text)
|
||||||
self.assertIn("/rest/r/ad/task/report", text)
|
self.assertIn("/rest/r/ad/task/report", text)
|
||||||
|
self.assertIn("搜好货广告拉取", text)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user