117 lines
4.1 KiB
Python
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¬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()
|