#!/usr/bin/env python3 """对比通知标记前后状态;仅传 --apply 时发送一次 count=1 的已读标记请求。""" import argparse import json import subprocess from datetime import datetime from pathlib import Path from cdp_explicit import ( # pyright: ignore[reportMissingImports] add_cdp_args, configure, evaluate_script, ) from get_current_user import get_user_from_browser from get_notifications import get_notifications def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--apply", action="store_true", help="实际标记,可能影响返回范围以外的通知" ) parser.add_argument( "--output", type=Path, default=Path(__file__).with_name("notice_mark_verification.json"), ) add_cdp_args(parser) args = parser.parse_args() configure(args) user = get_user_from_browser() before = get_notifications() if any(str(n.get("user_id")) != str(user["uid"]) for n in before["notice_list_v2"]): raise RuntimeError("账号不匹配,停止") report = { "started_at": datetime.now().astimezone().isoformat(), "user": {k: user.get(k) for k in ("uid", "nickname")}, "before": before, "applied": False, } def save(): temporary = args.output.with_suffix(args.output.suffix + ".tmp") temporary.write_text( json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8" ) temporary.replace(args.output) save() # 写入失败时绝不发送标记请求。 if not args.apply: print(f"仅保存基线:{args.output};使用 --apply 才会修改远端状态") return if not any( isinstance(n.get("has_read"), bool) and not n["has_read"] for n in before["notice_list_v2"] ): print("基线没有未读通知,不执行标记") return expression = """(async () => { if (location.origin !== 'https://www.douyin.com') throw new Error('不是抖音标签页'); const r = await fetch('/aweme/v1/web/notice/?device_platform=webapp&aid=6383&channel=channel_pc_web&is_new_notice=1&is_mark_read=1¬ice_group=960&count=1&min_time=0&max_time=0', { credentials: 'include', signal: AbortSignal.timeout(20000) }); return JSON.stringify({status: r.status, body: await r.text()}); })()""" # 不自动重试:超时也可能已经产生远端副作用。 report["mark_status"] = "attempting" save() try: completed = evaluate_script(expression) response = json.loads(completed.stdout.strip().splitlines()[-1]) marked = json.loads(response["body"]) report["mark_response"] = marked if response["status"] != 200 or marked.get("status_code") != 0: raise RuntimeError("标记请求响应异常") report["applied"] = True report["mark_status"] = "responded_success" save() after = get_notifications() report["after"] = after returned = {str(n.get("nid_str") or n["nid"]) for n in marked["notice_list_v2"]} after_by_id = { str(n.get("nid_str") or n["nid"]): n for n in after["notice_list_v2"] } report["comparison"] = [ { "id": str(n.get("nid_str") or n["nid"]), "before": n.get("has_read"), "after": after_by_id.get(str(n.get("nid_str") or n["nid"]), {}).get( "has_read" ), "returned_in_mark_request": str(n.get("nid_str") or n["nid"]) in returned, } for n in before["notice_list_v2"] ] save() print(json.dumps(report["comparison"], ensure_ascii=False, indent=2)) print(f"标记请求返回 {len(returned)} 条;证据保存:{args.output}") except ( OSError, ValueError, KeyError, IndexError, TypeError, RuntimeError, subprocess.SubprocessError, ) as exc: report["error"] = str(exc) save() raise if __name__ == "__main__": main()