# -*- coding: utf-8 -*- """B 站收藏夹接口的返回模型。""" from __future__ import annotations from dataclasses import dataclass from typing import Any, Mapping @dataclass(frozen=True) class FavId: """``fav/resource/ids`` 的一条资源。""" aid: int type: int bvid: str | None = None @dataclass(frozen=True) class FavResource: """``fav/resource/infos`` 的一条视频元数据。""" aid: int bvid: str | None title: str up_mid: int | None up_name: str | None pubdate: int duration: int | None cover: str | None attr: int fav_time: int | None @property def invalid(self) -> bool: # 实测 attr=9 表示"已失效视频"。 return self.attr == 9 @classmethod def from_api(cls, item: Mapping[str, Any]) -> "FavResource": aid = item.get("id") if isinstance(aid, bool) or not isinstance(aid, int): raise ValueError("infos item.id 不是整数") upper = item.get("upper") or {} if not isinstance(upper, Mapping): upper = {} up_mid = upper.get("mid") if isinstance(up_mid, bool) or not isinstance(up_mid, int): up_mid = None raw_duration = item.get("duration") duration = raw_duration if isinstance(raw_duration, int) and not isinstance( raw_duration, bool ) else None raw_attr = item.get("attr", 0) attr = raw_attr if isinstance(raw_attr, int) and not isinstance(raw_attr, bool) else 0 fav_time = item.get("fav_time") if isinstance(fav_time, bool) or not isinstance(fav_time, int) or fav_time <= 0: fav_time = None bvid = item.get("bvid") or item.get("bv_id") if not isinstance(bvid, str) or not bvid: bvid = None return cls( aid=aid, bvid=bvid, title=str(item.get("title") or ""), up_mid=up_mid, up_name=str(upper.get("name") or "") or None, pubdate=int(item.get("pubtime") or item.get("ctime") or 0), duration=duration, cover=str(item.get("cover") or "") or None, attr=attr, fav_time=fav_time, ) @dataclass(frozen=True) class FolderInfo: media_id: int title: str media_count: int @dataclass(frozen=True) class InfosResult: requested: tuple[int, ...] items: dict[int, FavResource] missing: tuple[int, ...] @dataclass(frozen=True) class ViewStat: """``x/web-interface/view`` 的七项统计。""" aid: int bvid: str | None title: str view: int like: int favorite: int coin: int share: int reply: int danmaku: int def metrics(self) -> dict[str, int]: return { "view": self.view, "like": self.like, "favorite": self.favorite, "coin": self.coin, "share": self.share, "reply": self.reply, "danmaku": self.danmaku, } @classmethod def from_api(cls, item: Mapping[str, Any]) -> "ViewStat": aid = item.get("aid") if isinstance(aid, bool) or not isinstance(aid, int): raise ValueError("view data.aid 不是整数") stat = item.get("stat") if not isinstance(stat, Mapping): raise ValueError("view data.stat 不是对象") metrics: dict[str, int] = {} for key in ("view", "like", "favorite", "coin", "share", "reply", "danmaku"): value = stat.get(key) if isinstance(value, bool) or not isinstance(value, int) or value < 0: raise ValueError(f"view stat.{key} 不是非负整数") metrics[key] = value bvid = item.get("bvid") if not isinstance(bvid, str) or not bvid: bvid = None return cls( aid=aid, bvid=bvid, title=str(item.get("title") or ""), **metrics, ) @dataclass(frozen=True) class FanStat: mid: int follower: int following: int @classmethod def from_api(cls, item: Mapping[str, Any]) -> "FanStat": mid = item.get("mid") follower = item.get("follower") following = item.get("following", 0) if isinstance(mid, bool) or not isinstance(mid, int): raise ValueError("relation/stat data.mid 不是整数") if isinstance(follower, bool) or not isinstance(follower, int) or follower < 0: raise ValueError("relation/stat data.follower 不是非负整数") if isinstance(following, bool) or not isinstance(following, int) or following < 0: raise ValueError("relation/stat data.following 不是非负整数") return cls(mid=mid, follower=follower, following=following) @dataclass(frozen=True) class LoginState: is_login: bool mid: int | None uname: str | None code: int message: str