feat: add history replay and work filters

This commit is contained in:
2026-09-07 12:09:49 +08:00
parent dafa335bb5
commit 07524a36cd
16 changed files with 1739 additions and 142 deletions
+112 -3
View File
@@ -9,8 +9,14 @@ from urllib.parse import urlsplit
from account_store import decode
from douyin_im import EXPRESSION
from follow_user import validate_uid
from get_current_user import BROWSER_SCRIPT, compact_user, parse_user_response
from subscribe_notifications import INSTALL, WAIT, detail_request_script
from get_current_user import BROWSER_SCRIPT, business_user, parse_user_response
from subscribe_notifications import (
INSTALL,
WAIT,
detail_request_script,
history_request_script,
works_request_script,
)
class SessionError(RuntimeError):
@@ -139,7 +145,7 @@ class Session:
async def profile(self):
try:
user = compact_user(
user = business_user(
parse_user_response(
await self.evaluate(BROWSER_SCRIPT, main_world=True)
)
@@ -226,6 +232,109 @@ class Session:
raise SessionError("通知详情返回了未请求的 ID,已停止处理")
return notices
async def history_notices(self):
notices = {}
min_time = max_time = 0
seen_cursors = set()
for _ in range(1000):
response = await self.json(
history_request_script(min_time, max_time), main_world=True
)
try:
status = int(response["status"])
payload = json.loads(response["body"])
except (KeyError, TypeError, ValueError, json.JSONDecodeError) as exc:
raise SessionError("历史通知响应格式无效") from exc
if status != 200 or payload.get("status_code") != 0:
raise SessionError(
f"历史通知请求失败:HTTP {status},业务码 {payload.get('status_code')}"
)
rows = payload.get("notice_list_v2")
if rows is None:
rows = []
if not isinstance(rows, list) or any(
not isinstance(row, dict) for row in rows
):
raise SessionError("历史通知列表格式无效")
for row in rows:
if str(row.get("user_id")) != self.uid:
raise SessionError("历史通知所属身份不符")
nid = row.get("nid_str") or row.get("nid")
validate_uid(nid)
notices.setdefault(nid, row)
if not payload.get("has_more"):
return list(notices.values())
cursor = (payload.get("min_time"), payload.get("max_time"))
if cursor in seen_cursors or cursor == (min_time, max_time):
raise SessionError("历史通知分页游标未推进")
if any(type(value) not in (int, float) for value in cursor):
raise SessionError("历史通知分页游标格式无效")
seen_cursors.add(cursor)
min_time, max_time = cursor
raise SessionError("历史通知超过 50000 条,已停止以避免无限分页")
async def works(self, all_pages=False):
profile = await self.identity()
sec_uid = profile.get("sec_uid") or profile.get("secUid")
if not isinstance(sec_uid, str) or not sec_uid:
raise SessionError("当前账号缺少作品列表身份参数")
works = {}
cursor = 0
seen_cursors = set()
pages = 1000 if all_pages else 1
for _ in range(pages):
response = await self.json(
works_request_script(sec_uid, cursor), main_world=True
)
try:
status = int(response["status"])
payload = json.loads(response["body"])
except (KeyError, TypeError, ValueError, json.JSONDecodeError) as exc:
raise SessionError("作品列表响应格式无效") from exc
if status != 200 or payload.get("status_code") != 0:
raise SessionError(
f"作品列表请求失败:HTTP {status},业务码 {payload.get('status_code')}"
)
rows = payload.get("aweme_list")
if rows is None:
rows = []
if not isinstance(rows, list) or any(
not isinstance(row, dict) for row in rows
):
raise SessionError("作品列表格式无效")
for row in rows:
author = row.get("author") or {}
if str(author.get("uid")) != self.uid:
raise SessionError("作品列表所属身份不符")
ident = row.get("aweme_id") or row.get("awemeId")
validate_uid(ident)
cover = ((row.get("video") or {}).get("cover") or {}).get(
"url_list"
) or []
if not cover and row.get("images"):
cover = (row["images"][0] or {}).get("url_list") or []
works.setdefault(
ident,
{
"aweme_id": ident,
"desc": row.get("desc") or "",
"create_time": row.get("create_time") or "",
"statistics": row.get("statistics") or {},
"cover": cover[0] if cover else "",
"business": row,
},
)
if not all_pages or not payload.get("has_more"):
return list(works.values())
next_cursor = payload.get("max_cursor")
if type(next_cursor) not in (int, float) or next_cursor < 0:
raise SessionError("作品列表分页游标格式无效")
if next_cursor in seen_cursors or next_cursor == cursor:
raise SessionError("作品列表分页游标未推进")
seen_cursors.add(next_cursor)
cursor = next_cursor
raise SessionError("作品列表超过 18000 条,已停止以避免无限分页")
async def follow(self, target, check=False):
self.require_bound()
validate_uid(target)