feat: add history replay and work filters
This commit is contained in:
+112
-3
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user