Files
douyin-pc/src/verify_notification_mark_read.py
T

117 lines
4.1 KiB
Python

#!/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&notice_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()