增加更多任务

This commit is contained in:
youfak 2026-08-27 17:14:08 +08:00
parent 4b342ff9c2
commit 73e61cab4e
3 changed files with 347 additions and 13 deletions

View File

@ -476,6 +476,31 @@
- 端到端验证后 19+291 测试回归无新增失败。诊断体系(JSONL 探针/
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次查看/浏览器/搜索操作后更新此文件*
*防止视觉信息丢失*

245
ksjsb.py
View File

@ -36,8 +36,12 @@
T1/T2 密码表 base64 内嵌(提取自 libkwsgmain.so)
TODO(抓包无法覆盖):
- 广告打开详情/落地页属第三方广告 SDK 行为, 不在脚本范围;
奖励由 ad/task/report 直接发放
- adlog 播放事件流(POST adlog.e.kuaishou.com/rest/nebula/log/ad/photo/action,
真机每次观看发 15~25 , `log` 为自定义 "gzip2" 二进制编码, 未逆向):
这是最大的缺失播放信号; 已用 exposure/report(ad_exit, 明文 bizStr)
补完成事件作为主要替代
- misc2 埋点(任务曝光/点击 show_event )未发送; ulog 加密日志未发送
- 广告打开详情/落地页属第三方广告 SDK 行为, 不在脚本范围
- API 请求用 requests(HTTP/1.1); 真机为 OkHttp/Cronet(H2/QUIC),
传输指纹差异已知, 未在本脚本处理
@ -5197,11 +5201,23 @@ def signed_api_url(
REWARD_SDK = "95147564-9763-4413-a937-6f0e3c12caf1"
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 = {
"sign_in": (11101, 100024058, 20342),
"treasure": (11101, 100024064, 20346),
"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.task_ad_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
# 点击上报为运营分析事件, 非必需(宝箱开箱已验证无点击也可领奖);
# 首次失败后停发, 避免重复触发风控
@ -6094,6 +6113,16 @@ class KsJsB:
def treasure_box_info(self) -> Record:
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:
body_text = compact_json({
"imei": "",
@ -6233,7 +6262,7 @@ class KsJsB:
headers["Cookie"] = self._api_cookie_header()
record = self._request(
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"),
notes={
"strE": str_e.decode("utf-8", "replace"),
@ -6251,7 +6280,7 @@ class KsJsB:
material = self.ad_materials.get(kind)
if material is 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:
record = Record(f"{kind}_ad_report", title)
record.error = "missing_ad_material"
@ -6265,6 +6294,44 @@ class KsJsB:
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"))
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:
if not isinstance(data, dict):
return
@ -6297,21 +6364,48 @@ class KsJsB:
ext_param = task.get("extParam")
if isinstance(ext_param, dict) and kind == "normal":
self.ad_task_countdown_ms[kind] = max(0, _safe_int(ext_param.get("descCountDown"), 0))
# 任务位提取(看广告任务 + 宝箱挂件)
# 任务位提取(看广告任务/宝箱挂件/高频任务/外部广告任务)
for task in tasks:
if not isinstance(task, dict):
continue
ext_param = task.get("extParam")
ext_param = ext_param if isinstance(ext_param, dict) else {}
tracking = task.get("eventTrackingLogInfo")
if not isinstance(tracking, dict):
tracking = dig(task, "extParam.eventTrackingLogInfo")
if not isinstance(tracking, dict) or not tracking:
continue
tracking = {}
material_key = str(tracking.get("materialKey") or "")
resource_id = str(tracking.get("resourceId") or "")
if material_key.startswith("TASK_LIST_17_") and self.task_ad_slot is None:
self.task_ad_slot = tracking
if resource_id == "externalFeed_externalTreasureChestWidget" and self.task_treasure_slot is None:
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:
@ -6445,6 +6539,8 @@ class KsJsB:
if wait_seconds > 0:
print(f"[看广告] 等待广告模拟观看 {wait_seconds:.1f}s ...")
time.sleep(wait_seconds)
# 完成事件 -> 任务上报(真机顺序: exposure ad_exit 先于 task/report)
self._print(self.exposure_report("normal"))
report = self.report_ad("normal")
self._print(report)
if not report.ok:
@ -6452,6 +6548,10 @@ class KsJsB:
break
done += 1
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:
time.sleep(random.uniform(2.0, 6.0))
if done % 5 == 0:
@ -6459,6 +6559,113 @@ class KsJsB:
print(f"[看广告] 完成 {done}/{planned}")
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:
import uuid
@ -6487,25 +6694,39 @@ def run() -> int:
print(f"===== 快手极速版每日任务{' (dry-run)' if dry_run else ''} =====")
if client.record_path is not None:
print(f"诊断日志: {client.record_path}")
print("--- 1/5 查询金币/余额 ---")
print("--- 1/7 查询金币/余额 ---")
client._print(client.basic_info())
print("--- 2/5 任务列表(刷新广告上下文) ---")
print("--- 2/7 任务列表(刷新广告上下文) ---")
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()
print("--- 4/5 开宝箱 ---")
print("--- 4/7 开宝箱 ---")
client.treasure_box_step()
print("--- 5/5 看广告得金币 ---")
print("--- 5/7 高频任务(走路/搜好货/搜索/外部广告) ---")
client.hf_tasks_step()
print("--- 6/7 看广告得金币 ---")
client.watch_ads_step(
ad_wait=float(os.environ.get("KS_AD_WAIT_SECONDS", "-1")),
low=int(os.environ.get("KS_AD_COUNT_MIN", "20")),
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("--- 最终余额 ---")
client._print(client.basic_info())
finally:

View File

@ -332,6 +332,89 @@ class TaskSlotTests(unittest.TestCase):
self.assertEqual(client.ad_fetch_scenes["normal"], (11101, 100026367, 24067))
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):
def test_run_dry_run_completes_all_steps_without_network(self):
@ -352,11 +435,16 @@ class SequentialRunTests(unittest.TestCase):
os.environ[k] = v
self.assertEqual(code, 0)
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("开宝箱 dry-run method=POST", text)
self.assertIn("/rest/e/reward/mixed/ad", text)
self.assertIn("/rest/r/ad/task/report", text)
self.assertIn("搜好货广告拉取", text)
if __name__ == "__main__":