# -*- 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, ...]