feat(ingest): parse video refs and fetch detail by bvid
This commit is contained in:
1 parent
e903c49921
commit
d2adb65e08
6 files changed
+410
No files matched your search
@@ -27,6 +27,7 @@ from .models import (
|
||||
FolderInfo,
|
||||
InfosResult,
|
||||
LoginState,
|
||||
VideoDetail,
|
||||
ViewStat,
|
||||
)
|
||||
|
||||
@@ -463,6 +464,53 @@ class BilibiliClient:
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise BilibiliProtocolError(f"view 解析失败: {exc}") from exc
|
||||
|
||||
def view_detail(
|
||||
self, *, aid: int | None = None, bvid: str | None = None
|
||||
) -> VideoDetail:
|
||||
"""按 aid 或 bvid 取视频完整详情;两者必须且只能提供一个。"""
|
||||
if (aid is None) == (bvid is None):
|
||||
raise ValueError("view_detail 必须且只能提供 aid 或 bvid")
|
||||
if bvid is not None:
|
||||
url = f"{BASE}/x/web-interface/view?bvid={urllib.parse.quote(str(bvid))}"
|
||||
else:
|
||||
url = f"{BASE}/x/web-interface/view?aid={int(aid)}"
|
||||
payload = self.get_json(url)
|
||||
data = payload.get("data")
|
||||
if not isinstance(data, Mapping):
|
||||
raise BilibiliProtocolError("view data 不是对象")
|
||||
try:
|
||||
return VideoDetail.from_api(data)
|
||||
except (TypeError, ValueError) as exc:
|
||||
raise BilibiliProtocolError(f"view 解析失败: {exc}") from exc
|
||||
|
||||
def resolve_short_link(self, url: str, *, max_hops: int = 5) -> str:
|
||||
"""跟随 ``b23.tv`` 短链重定向,返回最终 URL。
|
||||
|
||||
只用于把短链还原成含 bvid/aid 的 bilibili 视频链接;不做分页或登录。
|
||||
"""
|
||||
current = str(url)
|
||||
for _ in range(max(1, int(max_hops))):
|
||||
host = (urllib.parse.urlparse(current).hostname or "").lower()
|
||||
if not host.endswith("b23.tv"):
|
||||
# 已经不是短链域名(例如已还原到 bilibili.com/video/BV…)直接返回。
|
||||
return current
|
||||
response = self.transport.request(
|
||||
"GET", current, self._headers(False), data=None, timeout=self.timeout
|
||||
)
|
||||
headers = {
|
||||
str(key).lower(): value for key, value in response.headers.items()
|
||||
}
|
||||
location = headers.get("location")
|
||||
if response.status >= 400:
|
||||
raise BilibiliHTTPError(
|
||||
response.status, response.body.decode("utf-8", "replace")[:200]
|
||||
)
|
||||
if response.status in {301, 302, 303, 307, 308} and location:
|
||||
current = urllib.parse.urljoin(current, location)
|
||||
continue
|
||||
return current
|
||||
return current
|
||||
|
||||
def relation_stat(self, mid: int) -> FanStat:
|
||||
url = f"{BASE}/x/relation/stat?vmid={int(mid)}"
|
||||
payload = self.get_json(url)
|
||||
|
||||
Reference in new issue
Block a user