Spaces:
Sleeping
Sleeping
""" | |
bilibili_api.bangumi | |
番剧相关 | |
概念: | |
+ media_id: 番剧本身的 ID,有时候也是每季度的 ID,如 https://www.bilibili.com/bangumi/media/md28231846/ | |
+ season_id: 每季度的 ID | |
+ episode_id: 每集的 ID,如 https://www.bilibili.com/bangumi/play/ep374717 | |
""" | |
import datetime | |
from enum import Enum | |
from typing import Any, List, Tuple, Union, Optional | |
from bilibili_api.utils.danmaku import Danmaku | |
from . import settings | |
from .video import Video | |
from .utils.utils import get_api | |
from .utils.credential import Credential | |
from .exceptions.ApiException import ApiException | |
from .utils.network import Api, get_session, HEADERS | |
from .utils.initial_state import ( | |
get_initial_state, | |
get_initial_state_sync, | |
InitialDataType, | |
) | |
API = get_api("bangumi") | |
episode_data_cache = {} | |
class BangumiCommentOrder(Enum): | |
""" | |
短评 / 长评 排序方式 | |
+ DEFAULT: 默认 | |
+ CTIME: 发布时间倒序 | |
""" | |
DEFAULT = 0 | |
CTIME = 1 | |
class BangumiType(Enum): | |
""" | |
番剧类型 | |
+ BANGUMI: 番剧 | |
+ FT: 影视 | |
+ GUOCHUANG: 国创 | |
""" | |
BANGUMI = 1 | |
FT = 3 | |
GUOCHUANG = 4 | |
async def get_timeline(type_: BangumiType, before: int = 7, after: int = 0) -> dict: | |
""" | |
获取番剧时间线 | |
Args: | |
type_(BangumiType): 番剧类型 | |
before(int) : 几天前开始(0~7), defaults to 7 | |
after(int) : 几天后结束(0~7), defaults to 0 | |
""" | |
api = API["info"]["timeline"] | |
params = {"types": type_.value, "before": before, "after": after} | |
return await Api(**api).update_params(**params).result | |
class IndexFilter: | |
""" | |
番剧索引相关固定参数以及值 | |
""" | |
class Type(Enum): | |
""" | |
索引类型 | |
+ ANIME: 番剧 | |
+ MOVIE: 电影 | |
+ DOCUMENTARY: 纪录片 | |
+ GUOCHUANG: 国创 | |
+ TV: 电视剧 | |
+ VARIETY: 综艺 | |
""" | |
ANIME = 1 | |
MOVIE = 2 | |
DOCUMENTARY = 3 | |
GUOCHUANG = 4 | |
TV = 5 | |
VARIETY = 7 | |
class Version(Enum): | |
""" | |
番剧版本 | |
+ ALL: 全部 | |
+ MAIN: 正片 | |
+ FILM: 电影 | |
+ OTHER: 其他 | |
""" | |
ALL = -1 | |
MAIN = 1 | |
FILM = 2 | |
OTHER = 3 | |
class Spoken_Language(Enum): | |
""" | |
配音 | |
+ ALL: 全部 | |
+ ORIGINAL: 原声 | |
+ CHINESE: 中配 | |
""" | |
ALL = -1 | |
ORIGINAL = 1 | |
CHINESE = 2 | |
class Finish_Status(Enum): | |
""" | |
完结状态 | |
+ ALL: 全部 | |
+ FINISHED: 完结 | |
+ UNFINISHED: 连载 | |
""" | |
ALL = -1 | |
FINISHED = 1 | |
UNFINISHED = 0 | |
class Copyright(Enum): | |
""" | |
版权方 | |
+ ALL: 全部 | |
+ EXCLUSIVE: 独家 | |
+ OTHER: 其他 | |
""" | |
ALL = -1 | |
EXCLUSIVE = 3 | |
OTHER = "1,2,4" | |
class Season(Enum): | |
""" | |
季度 | |
+ ALL: 全部 | |
+ SPRING: 春季 | |
+ SUMMER: 夏季 | |
+ AUTUMN: 秋季 | |
+ WINTER: 冬季 | |
""" | |
ALL = -1 | |
WINTER = 1 | |
SPRING = 4 | |
SUMMER = 7 | |
AUTUMN = 10 | |
def make_time_filter( | |
start: Optional[Union[datetime.datetime, str, int]] = None, | |
end: Optional[Union[datetime.datetime, str, int]] = None, | |
include_start: bool = True, | |
include_end: bool = False, | |
) -> str: | |
""" | |
生成番剧索引所需的时间条件 | |
番剧、国创直接传入年份,为 int 或者 str 类型,如 `make_time_filter(start=2019, end=2020)` | |
影视、纪录片、电视剧传入 datetime.datetime,如 `make_time_filter(start=datetime.datetime(2019, 1, 1), end=datetime.datetime(2020, 1, 1))` | |
start 或 end 为 None 时则表示不设置开始或结尾 | |
Args: | |
start (datetime, str, int): 开始时间. 如果是 None 则不设置开头. | |
end (datetime, str, int): 结束时间. 如果是 None 则不设置结尾. | |
include_start (bool): 是否包含开始时间. 默认为 True. | |
include_end (bool): 是否包含结束时间. 默认为 False. | |
Returns: | |
str: 年代条件 | |
""" | |
start_str = "" | |
end_str = "" | |
if start != None: | |
if isinstance(start, datetime.datetime): | |
start_str = start.strftime("%Y-%m-%d %H:%M:%S") | |
else: | |
start_str = start | |
if end != None: | |
if isinstance(end, datetime.datetime): | |
end_str = end.strftime("%Y-%m-%d %H:%M:%S") | |
else: | |
end_str = end | |
# 是否包含边界 | |
if include_start: | |
start_str = f"[{start_str}" | |
else: | |
start_str = f"({start_str}" | |
if include_end: | |
end_str = f"{end_str}]" | |
else: | |
end_str = f"{end_str})" | |
return f"{start_str},{end_str}" | |
class Producer(Enum): | |
""" | |
制作方 | |
+ ALL: 全部 | |
+ CCTV: CCTV | |
+ BBC: BBC | |
+ DISCOVERY: 探索频道 | |
+ NATIONAL_GEOGRAPHIC: 国家地理 | |
+ NHK: NHK | |
+ HISTORY: 历史频道 | |
+ SATELLITE: 卫视 | |
+ SELF: 自制 | |
+ ITV: ITV | |
+ SKY: SKY | |
+ ZDF: ZDF | |
+ PARTNER: 合作机构 | |
+ SONY: 索尼 | |
+ GLOBAL_NEWS: 环球 | |
+ PARAMOUNT: 派拉蒙 | |
+ WARNER: 华纳 | |
+ DISNEY: 迪士尼 | |
+ DOMESTIC_OTHER: 国内其他 | |
+ FOREIGN_OTHER: 国外其他 | |
""" | |
ALL = -1 | |
CCTV = 4 | |
BBC = 1 | |
DISCOVERY = 7 | |
NATIONAL_GEOGRAPHIC = 14 | |
NHK = 2 | |
HISTORY = 6 | |
SATELLITE = 8 | |
SELF = 9 | |
ITV = 5 | |
SKY = 3 | |
ZDF = 10 | |
PARTNER = 11 | |
DOMESTIC_OTHER = 12 | |
FOREIGN_OTHER = 13 | |
SONY = 15 | |
GLOBAL_NEWS = 16 | |
PARAMOUNT = 17 | |
WARNER = 18 | |
DISNEY = 19 | |
class Payment(Enum): | |
""" | |
观看条件 | |
+ ALL: 全部 | |
+ FREE: 免费 | |
+ PAID: 付费 | |
+ VIP: 大会员 | |
""" | |
ALL = -1 | |
FREE = 1 | |
PAID = "2,6" | |
VIP = "4,6" | |
class Area(Enum): | |
""" | |
地区 | |
+ ALL: 全部 | |
+ CHINA: 中国 | |
+ CHINA_MAINLAND: 中国大陆 | |
+ CHINA_HONGKONG_AND_TAIWAN: 中国港台 | |
+ JAPAN: 日本 | |
+ USA: 美国 | |
+ UK: 英国 | |
+ SOUTH_KOREA: 韩国 | |
+ FRANCE: 法国 | |
+ THAILAND: 泰国 | |
+ GERMANY: 德国 | |
+ ITALY: 意大利 | |
+ SPAIN: 西班牙 | |
+ ANIME_OTHER: 番剧其他 | |
+ MOVIE_OTHER: 影视其他 | |
+ DOCUMENTARY_OTHER: 纪录片其他 | |
注意:各索引的 其他 表示的地区都不同 | |
""" | |
ALL = "-1" | |
CHINA = "1,6,7" | |
CHINA_MAINLAND = "1" | |
CHINA_HONGKONG_AND_TAIWAN = "6,7" | |
JAPAN = "2" | |
USA = "3" | |
UK = "4" | |
SOUTH_KOREA = "8" | |
FRANCE = "9" | |
THAILAND = "10" | |
GERMANY = "15" | |
ITALY = "35" | |
SPAIN = "13" | |
ANIME_OTHER = "1,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70" | |
TV_OTHER = "5,8,9,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70" | |
MOVIE_OTHER = "5,11,12,14,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70" | |
class Style: | |
""" | |
风格,根据索引不同,可选的风格也不同 | |
""" | |
class Anime(Enum): | |
""" | |
番剧风格 | |
+ ALL: 全部 | |
+ ORIGINAL: 原创 | |
+ COMIC: 漫画改 | |
+ NOVEL: 小说改 | |
+ GAME: 游戏改 | |
+ TOKUSATSU: 特摄 | |
+ BUDAIXI: 布袋戏 | |
+ WARM: 热血 | |
+ TIMEBACK: 穿越 | |
+ IMAGING: 奇幻 | |
+ WAR: 战斗 | |
+ FUNNY: 搞笑 | |
+ DAILY: 日常 | |
+ SCIENCE_FICTION: 科幻 | |
+ MOE: 萌系 | |
+ HEAL: 治愈 | |
+ SCHOOL: 校园 | |
+ CHILDREN: 儿童 | |
+ NOODLES: 泡面 | |
+ LOVE: 恋爱 | |
+ GIRLISH: 少女 | |
+ MAGIC: 魔法 | |
+ ADVENTURE: 冒险 | |
+ HISTORY: 历史 | |
+ ALTERNATE: 架空 | |
+ MACHINE_BATTLE: 机战 | |
+ GODS_DEM: 神魔 | |
+ VOICE: 声控 | |
+ SPORT: 运动 | |
+ INSPIRATION: 励志 | |
+ MUSIC: 音乐 | |
+ ILLATION: 推理 | |
+ SOCIEITES: 社团 | |
+ OUTWIT: 智斗 | |
+ TEAR: 催泪 | |
+ FOOD: 美食 | |
+ IDOL: 偶像 | |
+ OTOME: 乙女 | |
+ WORK: 职场 | |
""" | |
ALL = -1 | |
ORIGINAL = 10010 | |
COMIC = 10011 | |
NOVEL = 10012 | |
GAME = 10013 | |
TOKUSATSU = 10102 | |
BUDAIXI = 10015 | |
WARM = 10016 | |
TIMEBACK = 10017 | |
IMAGING = 10018 | |
WAR = 10020 | |
FUNNY = 10021 | |
DAILY = 10022 | |
SCIENCE_FICTION = 10023 | |
MOE = 10024 | |
HEAL = 10025 | |
SCHOOL = 10026 | |
CHILDREN = 10027 | |
NOODLES = 10028 | |
LOVE = 10029 | |
GIRLISH = 10030 | |
MAGIC = 10031 | |
ADVENTURE = 10032 | |
HISTORY = 10033 | |
ALTERNATE = 10034 | |
MACHINE_BATTLE = 10035 | |
GODS_DEMONS = 10036 | |
VOICE = 10037 | |
SPORTS = 10038 | |
INSPIRATIONAL = 10039 | |
MUSIC = 10040 | |
ILLATION = 10041 | |
SOCIETIES = 10042 | |
OUTWIT = 10043 | |
TEAR = 10044 | |
FOODS = 10045 | |
IDOL = 10046 | |
OTOME = 10047 | |
WORK = 10048 | |
class Movie(Enum): | |
""" | |
电影风格 | |
+ ALL: 全部 | |
+ SKETCH: 短片 | |
+ PLOT: 剧情 | |
+ COMEDY: 喜剧 | |
+ ROMANTIC: 爱情 | |
+ ACTION: 动作 | |
+ SCAIRIER: 恐怖 | |
+ SCIENCE_FICTION: 科幻 | |
+ CRIME: 犯罪 | |
+ TIRILLER: 惊悚 | |
+ SUSPENSE: 悬疑 | |
+ IMAGING: 奇幻 | |
+ WAR: 战争 | |
+ ANIME: 动画 | |
+ BIOAGRAPHY: 传记 | |
+ FAMILY: 家庭 | |
+ SING_DANCE: 歌舞 | |
+ HISTORY: 历史 | |
+ DISCOVER: 探险 | |
+ DOCUMENTARY: 纪录片 | |
+ DISATER: 灾难 | |
+ COMIC: 漫画改 | |
+ NOVEL: 小说改 | |
""" | |
ALL = -1 | |
SKETCH = 10104 | |
PLOT = 10050 | |
COMEDY = 10051 | |
ROMANTIC = 10052 | |
ACTION = 10053 | |
SCAIRIER = 10054 | |
SCIENCE_FICTION = 10023 | |
CRIME = 10055 | |
TIRILLER = 10056 | |
SUSPENSE = 10057 | |
IMAGING = 10018 | |
WAR = 10058 | |
ANIME = 10059 | |
BIOAGRAPHY = 10060 | |
FAMILY = 10061 | |
SING_DANCE = 10062 | |
HISTORY = 10033 | |
DISCOVER = 10032 | |
DOCUMENTARY = 10063 | |
DISATER = 10064 | |
COMIC = 10011 | |
NOVEL = 10012 | |
class GuoChuang(Enum): | |
""" | |
国创风格 | |
+ ALL: 全部 | |
+ ORIGINAL: 原创 | |
+ COMIC: 漫画改 | |
+ NOVEL: 小说改 | |
+ GAME: 游戏改 | |
+ DYNAMIC: 动态漫 | |
+ BUDAIXI: 布袋戏 | |
+ WARM: 热血 | |
+ IMAGING: 奇幻 | |
+ FANTASY: 玄幻 | |
+ WAR: 战斗 | |
+ FUNNY: 搞笑 | |
+ WUXIA: 武侠 | |
+ DAILY: 日常 | |
+ SCIENCE_FICTION: 科幻 | |
+ MOE: 萌系 | |
+ HEAL: 治愈 | |
+ SUSPENSE: 悬疑 | |
+ SCHOOL: 校园 | |
+ CHILDREN: 少儿 | |
+ NOODLES: 泡面 | |
+ LOVE: 恋爱 | |
+ GIRLISH: 少女 | |
+ MAGIC: 魔法 | |
+ HISTORY: 历史 | |
+ MACHINE_BATTLE: 机战 | |
+ GODS_DEMONS: 神魔 | |
+ VOICE: 声控 | |
+ SPORT: 运动 | |
+ INSPIRATION: 励志 | |
+ MUSIC: 音乐 | |
+ ILLATION: 推理 | |
+ SOCIEITES: 社团 | |
+ OUTWIT: 智斗 | |
+ TEAR: 催泪 | |
+ FOOD: 美食 | |
+ IDOL: 偶像 | |
+ OTOME: 乙女 | |
+ WORK: 职场 | |
+ ANCIENT: 古风 | |
""" | |
ALL = -1 | |
ORIGINAL = 10010 | |
COMIC = 10011 | |
NOVEL = 10012 | |
GAME = 10013 | |
DYNAMIC = 10014 | |
BUDAIXI = 10015 | |
WARM = 10016 | |
IMAGING = 10018 | |
FANTASY = 10019 | |
WAR = 10020 | |
FUNNY = 10021 | |
WUXIA = 10078 | |
DAILY = 10022 | |
SCIENCE_FICTION = 10023 | |
MOE = 10024 | |
HEAL = 10025 | |
SUSPENSE = 10057 | |
SCHOOL = 10026 | |
CHILDREN = 10027 | |
NOODLES = 10028 | |
LOVE = 10029 | |
GIRLISH = 10030 | |
MAGIC = 10031 | |
HISTORY = 10033 | |
MACHINE_BATTLE = 10035 | |
GODS_DEMONS = 10036 | |
VOICE = 10037 | |
SPORTS = 10038 | |
INSPIRATIONAL = 10039 | |
MUSIC = 10040 | |
ILLATION = 10041 | |
SOCIETIES = 10042 | |
OUTWIT = 10043 | |
TEAR = 10044 | |
FOODS = 10045 | |
IDOL = 10046 | |
OTOME = 10047 | |
WORK = 10048 | |
ANCIENT = 10049 | |
class TV(Enum): | |
""" | |
电视剧风格 | |
+ ALL: 全部 | |
+ FUNNY: 搞笑 | |
+ IMAGING: 奇幻 | |
+ WAR: 战争 | |
+ WUXIA: 武侠 | |
+ YOUTH: 青春 | |
+ SKETCH: 短剧 | |
+ CITY: 都市 | |
+ ANCIENT: 古装 | |
+ SPY: 谍战 | |
+ CLASSIC: 经典 | |
+ EMOTION: 情感 | |
+ SUSPENSE: 悬疑 | |
+ INSPIRATION: 励志 | |
+ MYTH: 神话 | |
+ TIMEBACK: 穿越 | |
+ YEAR: 年代 | |
+ COUNTRYSIDE: 乡村 | |
+ INVESTIGATION: 刑侦 | |
+ PLOT: 剧情 | |
+ FAMILY: 家庭 | |
+ HISTORY: 历史 | |
+ ARMY: 军旅 | |
""" | |
ALL = -1 | |
FUNNY = 10021 | |
IMAGING = 10018 | |
WAR = 10058 | |
WUXIA = 10078 | |
YOUTH = 10079 | |
SKETCH = 10103 | |
CITY = 10080 | |
COSTUME = 10081 | |
SPY = 10082 | |
CLASSIC = 10083 | |
EMOTION = 10084 | |
SUSPENSE = 10057 | |
INSPIRATIONAL = 10039 | |
MYTH = 10085 | |
TIMEBACK = 10017 | |
YEAR = 10086 | |
COUNTRYSIDE = 10087 | |
INVESTIGATION = 10088 | |
PLOT = 10050 | |
FAMILY = 10061 | |
HISTORY = 10033 | |
ARMY = 10089 | |
class Documentary(Enum): | |
""" | |
纪录片风格 | |
+ ALL: 全部 | |
+ HISTORY: 历史 | |
+ FOODS: 美食 | |
+ HUMANITIES: 人文 | |
+ TECHNOLOGY: 科技 | |
+ DISCOVER: 探险 | |
+ UNIVERSE: 宇宙 | |
+ PETS: 萌宠 | |
+ SOCIAL: 社会 | |
+ ANIMALS: 动物 | |
+ NATURE: 自然 | |
+ MEDICAL: 医疗 | |
+ WAR: 战争 | |
+ DISATER: 灾难 | |
+ INVESTIGATIONS: 罪案 | |
+ MYSTERIOUS: 神秘 | |
+ TRAVEL: 旅行 | |
+ SPORTS: 运动 | |
+ MOVIES: 电影 | |
""" | |
ALL = -1 | |
HISTORY = 10033 | |
FOODS = 10045 | |
HUMANITIES = 10065 | |
TECHNOLOGY = 10066 | |
DISCOVER = 10067 | |
UNIVERSE = 10068 | |
PETS = 10069 | |
SOCIAL = 10070 | |
ANIMALS = 10071 | |
NATURE = 10072 | |
MEDICAL = 10073 | |
WAR = 10074 | |
DISATER = 10064 | |
INVESTIGATIONS = 10075 | |
MYSTERIOUS = 10076 | |
TRAVEL = 10077 | |
SPORTS = 10038 | |
MOVIES = -10 | |
class Variety(Enum): | |
""" | |
综艺风格 | |
+ ALL: 全部 | |
+ MUSIC: 音乐 | |
+ TALK: 访谈 | |
+ TALK_SHOW: 脱口秀 | |
+ REALITY_SHOW: 真人秀 | |
+ TALENT_SHOW: 选秀 | |
+ FOOD: 美食 | |
+ TRAVEL: 旅行 | |
+ SOIREE: 晚会 | |
+ CONCERT: 演唱会 | |
+ EMOTION: 情感 | |
+ COMEDY: 喜剧 | |
+ PARENT_CHILD: 亲子 | |
+ CULTURE: 文化 | |
+ OFFICE: 职场 | |
+ PET: 萌宠 | |
+ CULTIVATE: 养成 | |
""" | |
ALL = -1 | |
MUSIC = 10040 | |
TALK = 10091 | |
TALK_SHOW = 10081 | |
REALITY_SHOW = 10092 | |
TALENT_SHOW = 10094 | |
FOOD = 10045 | |
TRAVEL = 10095 | |
SOIREE = 10098 | |
CONCERT = 10096 | |
EMOTION = 10084 | |
COMEDY = 10051 | |
PARENT_CHILD = 10097 | |
CULTURE = 10100 | |
OFFICE = 10048 | |
PET = 10069 | |
CULTIVATE = 10099 | |
class Sort(Enum): | |
""" | |
排序方式 | |
+ DESC: 降序 | |
+ ASC: 升序 | |
""" | |
DESC = "0" | |
ASC = "1" | |
class Order(Enum): | |
""" | |
排序字段 | |
+ UPDATE: 更新时间 | |
+ DANMAKU: 弹幕数量 | |
+ PLAY: 播放数量 | |
+ FOLLOWER: 追番人数 | |
+ SOCRE: 最高评分 | |
+ ANIME_RELEASE: 番剧开播日期 | |
+ MOVIE_RELEASE: 电影上映日期 | |
""" | |
UPDATE = "0" | |
DANMAKU = "1" | |
PLAY = "2" | |
FOLLOWER = "3" | |
SCORE = "4" | |
ANIME_RELEASE = "5" | |
MOVIE_RELEASE = "6" | |
class IndexFilterMeta: | |
""" | |
IndexFilter 元数据 | |
用于传入 get_index_info 方法 | |
""" | |
class Anime: | |
def __init__( | |
self, | |
version: IndexFilter.Version = IndexFilter.Version.ALL, | |
spoken_language: IndexFilter.Spoken_Language = IndexFilter.Spoken_Language.ALL, | |
area: IndexFilter.Area = IndexFilter.Area.ALL, | |
finish_status: IndexFilter.Finish_Status = IndexFilter.Finish_Status.ALL, | |
copyright: IndexFilter.Copyright = IndexFilter.Copyright.ALL, | |
payment: IndexFilter.Payment = IndexFilter.Payment.ALL, | |
season: IndexFilter.Season = IndexFilter.Season.ALL, | |
year: str = -1, | |
style: IndexFilter.Style.Anime = IndexFilter.Style.Anime.ALL, | |
) -> None: | |
""" | |
Anime Meta | |
Args: | |
version (Index_Filter.Version): 类型,如正片、电影等 | |
spoken_language (Index_Filter.Spoken_Language): 配音 | |
area (Index_Filter.Area): 地区 | |
finish_status (Index_Filter.Finish_Status): 是否完结 | |
copyright (Index_Filter.Copryright): 版权 | |
payment (Index_Filter.Payment): 付费门槛 | |
season (Index_Filter.Season): 季度 | |
year (str): 年份,调用 Index_Filter.make_time_filter() 传入年份 (int, str) 获取 | |
style (Index_Filter.Style.Anime): 风格 | |
""" | |
self.season_type = IndexFilter.Type.ANIME | |
self.season_version = version | |
self.spoken_language_type = spoken_language | |
self.area = area | |
self.is_finish = finish_status | |
self.copyright = copyright | |
self.season_status = payment | |
self.season_month = season | |
self.year = year | |
self.style_id = style | |
class Movie: | |
def __init__( | |
self, | |
area: IndexFilter.Area = IndexFilter.Area.ALL, | |
release_date: str = -1, | |
style: IndexFilter.Style.Movie = IndexFilter.Style.Movie.ALL, | |
payment: IndexFilter.Payment = IndexFilter.Payment.ALL, | |
) -> None: | |
""" | |
Movie Meta | |
Args: | |
area (Index_Filter.Area): 地区 | |
payment (Index_Filter.Payment): 付费门槛 | |
season (Index_Filter.Season): 季度 | |
release_date (str): 上映时间,调用 Index_Filter.make_time_filter() 传入年份 (datetime.datetime) 获取 | |
style (Index_Filter.Style.Movie): 风格 | |
""" | |
self.season_type = IndexFilter.Type.MOVIE | |
self.area = area | |
self.release_date = release_date | |
self.style_id = style | |
self.season_status = payment | |
class Documentary: | |
def __init__( | |
self, | |
release_date: str = -1, | |
style: IndexFilter.Style.Documentary = IndexFilter.Style.Documentary.ALL, | |
payment: IndexFilter.Payment = IndexFilter.Payment.ALL, | |
producer: IndexFilter.Producer = IndexFilter.Producer.ALL, | |
) -> None: | |
""" | |
Documentary Meta | |
Args: | |
area (Index_Filter.Area): 地区 | |
release_date (str): 上映时间,调用 Index_Filter.make_time_filter() 传入年份 (datetime.datetime) 获取 | |
style (Index_Filter.Style.Documentary): 风格 | |
producer (Index_Filter.Producer): 制作方 | |
""" | |
self.season_type = IndexFilter.Type.DOCUMENTARY | |
self.release_date = release_date | |
self.style_id = style | |
self.season_status = payment | |
self.producer_id = producer | |
class TV: | |
def __init__( | |
self, | |
area: IndexFilter.Area = IndexFilter.Area.ALL, | |
release_date: str = -1, | |
style: IndexFilter.Style.TV = IndexFilter.Style.TV.ALL, | |
payment: IndexFilter.Payment = IndexFilter.Payment.ALL, | |
) -> None: | |
""" | |
TV Meta | |
Args: | |
area (Index_Filter.Area): 地区 | |
payment (Index_Filter.Payment): 付费门槛 | |
release_date (str): 上映时间,调用 Index_Filter.make_time_filter() 传入年份 (datetime.datetime) 获取 | |
style (Index_Filter.Style.TV): 风格 | |
""" | |
self.season_type = IndexFilter.Type.TV | |
self.area = area | |
self.release_date = release_date | |
self.style_id = style | |
self.season_status = payment | |
class GuoChuang: | |
def __init__( | |
self, | |
version: IndexFilter.Version = IndexFilter.Version.ALL, | |
finish_status: IndexFilter.Finish_Status = IndexFilter.Finish_Status.ALL, | |
copyright: IndexFilter.Copyright = IndexFilter.Copyright.ALL, | |
payment: IndexFilter.Payment = IndexFilter.Payment.ALL, | |
year: str = -1, | |
style: IndexFilter.Style.GuoChuang = IndexFilter.Style.GuoChuang.ALL, | |
) -> None: | |
""" | |
Guochuang Meta | |
Args: | |
version (Index_Filter.VERSION): 类型,如正片、电影等 | |
finish_status (Index_Filter.Finish_Status): 是否完结 | |
copyright (Index_Filter.Copyright): 版权 | |
payment (Index_Filter.Payment): 付费门槛 | |
year (str): 年份,调用 Index_Filter.make_time_filter() 传入年份 (int, str) 获取 | |
style (Index_Filter.Style.GuoChuang): 风格 | |
""" | |
self.season_type = IndexFilter.Type.GUOCHUANG | |
self.season_version = version | |
self.is_finish = finish_status | |
self.copyright = copyright | |
self.season_status = payment | |
self.year = year | |
self.style_id = style | |
class Variety: | |
def __init__( | |
self, | |
style: IndexFilter.Style.Variety = IndexFilter.Style.Variety.ALL, | |
payment: IndexFilter.Payment = IndexFilter.Payment.ALL, | |
) -> None: | |
""" | |
Variety Meta | |
Args: | |
payment (Index_Filter.Payment): 付费门槛 | |
style (Index_Filter.Style.Variety): 风格 | |
""" | |
self.season_type = IndexFilter.Type.VARIETY | |
self.season_status = payment | |
self.style_id = style | |
async def get_index_info( | |
filters: IndexFilterMeta = IndexFilterMeta.Anime(), | |
order: IndexFilter.Order = IndexFilter.Order.SCORE, | |
sort: IndexFilter.Sort = IndexFilter.Sort.DESC, | |
pn: int = 1, | |
ps: int = 20, | |
) -> dict: | |
""" | |
查询番剧索引,索引的详细参数信息见 `IndexFilterMeta` | |
请先通过 `IndexFilterMeta` 构造 filters | |
Args: | |
filters (Index_Filter_Meta, optional): 筛选条件元数据. Defaults to Anime. | |
order (BANGUMI_INDEX.ORDER, optional): 排序字段. Defaults to SCORE. | |
sort (BANGUMI_INDEX.SORT, optional): 排序方式. Defaults to DESC. | |
pn (int, optional): 页数. Defaults to 1. | |
ps (int, optional): 每页数量. Defaults to 20. | |
Returns: | |
dict: 调用 API 返回的结果 | |
""" | |
api = API["info"]["index"] | |
params = {} | |
for key, value in filters.__dict__.items(): | |
if value is not None: | |
if isinstance(value, Enum): | |
params[key] = value.value | |
else: | |
params[key] = value | |
if order in params: | |
if ( | |
order == IndexFilter.Order.SCORE.value | |
and sort == IndexFilter.Sort.ASC.value | |
): | |
raise ValueError( | |
"order 为 Index_Filter.ORDER.SCORE 时,sort 不能为 Index_Filter.SORT.ASC" | |
) | |
# 必要参数 season_type、type | |
# 常规参数 | |
params["order"] = order.value | |
params["sort"] = sort.value | |
params["page"] = pn | |
params["pagesize"] = ps | |
# params["st"] 未知参数,暂时不传 | |
# params["type"] 未知参数,为 1 | |
params["type"] = 1 | |
return await Api(**api).update_params(**params).result | |
class Bangumi: | |
""" | |
番剧类 | |
Attributes: | |
credential (Credential): 凭据类 | |
""" | |
def __init__( | |
self, | |
media_id: int = -1, | |
ssid: int = -1, | |
epid: int = -1, | |
oversea: bool = False, | |
credential: Union[Credential, None] = None, | |
) -> None: | |
""" | |
Args: | |
media_id (int, optional) : 番剧本身的 ID. Defaults to -1. | |
ssid (int, optional) : 每季度的 ID. Defaults to -1. | |
epid (int, optional) : 每集的 ID. Defaults to -1. | |
oversea (bool, optional) : 是否要采用兼容的港澳台Api,用于仅限港澳台地区番剧的信息请求. Defaults to False. | |
credential (Credential | None, optional): 凭据类. Defaults to None. | |
""" | |
if media_id == -1 and ssid == -1 and epid == -1: | |
raise ValueError("需要 Media_id 或 Season_id 或 epid 中的一个 !") | |
self.credential = credential if credential else Credential() | |
# 处理极端情况 | |
params = {} | |
self.__ssid = ssid | |
if self.__ssid == -1 and epid == -1: | |
api = API["info"]["meta"] | |
params = {"media_id": media_id} | |
meta = Api(**api, credential=credential).update_params(**params).result_sync | |
self.__ssid = meta["media"]["season_id"] | |
params["media_id"] = media_id | |
# 处理正常情况 | |
if self.__ssid != -1: | |
params["season_id"] = self.__ssid | |
if epid != -1: | |
params["ep_id"] = epid | |
self.oversea = oversea | |
if oversea: | |
api = API["info"]["collective_info_oversea"] | |
else: | |
api = API["info"]["collective_info"] | |
resp = Api(**api, credential=credential).update_params(**params).result_sync | |
self.__raw = resp | |
self.__epid = epid | |
# 确认有结果后,取出数据 | |
self.__ssid = resp["season_id"] | |
self.__media_id = resp["media_id"] | |
if "up_info" in resp: | |
self.__up_info = resp["up_info"] | |
else: | |
self.__up_info = {} | |
# 获取剧集相关 | |
self.ep_list = resp.get("episodes") | |
self.ep_item = [{}] | |
# 出海 Api 和国内的字段有些不同 | |
if self.ep_list: | |
if self.oversea: | |
self.ep_item = [ | |
item for item in self.ep_list if item["ep_id"] == self.__epid | |
] | |
else: | |
self.ep_item = [ | |
item for item in self.ep_list if item["id"] == self.__epid | |
] | |
def get_media_id(self) -> int: | |
return self.__media_id | |
def get_season_id(self) -> int: | |
return self.__ssid | |
def get_up_info(self) -> dict: | |
""" | |
番剧上传者信息 出差或者原版 | |
Returns: | |
Api 相关字段 | |
""" | |
return self.__up_info | |
def get_raw(self) -> Tuple[dict, bool]: | |
""" | |
原始初始化数据 | |
Returns: | |
Api 相关字段 | |
""" | |
return self.__raw, self.oversea | |
def set_media_id(self, media_id: int) -> None: | |
self.__init__(media_id=media_id, credential=self.credential) | |
def set_ssid(self, ssid: int) -> None: | |
self.__init__(ssid=ssid, credential=self.credential) | |
async def get_meta(self) -> dict: | |
""" | |
获取番剧元数据信息(评分,封面 URL,标题等) | |
Returns: | |
dict: 调用 API 返回的结果 | |
""" | |
credential = self.credential if self.credential is not None else Credential() | |
api = API["info"]["meta"] | |
params = {"media_id": self.__media_id} | |
return await Api(**api, credential=credential).update_params(**params).result | |
async def get_short_comment_list( | |
self, | |
order: BangumiCommentOrder = BangumiCommentOrder.DEFAULT, | |
next: Union[str, None] = None, | |
) -> dict: | |
""" | |
获取短评列表 | |
Args: | |
order (BangumiCommentOrder, optional): 排序方式。Defaults to BangumiCommentOrder.DEFAULT | |
next (str | None, optional) : 调用返回结果中的 next 键值,用于获取下一页数据。Defaults to None | |
Returns: | |
dict: 调用 API 返回的结果 | |
""" | |
credential = self.credential if self.credential is not None else Credential() | |
api = API["info"]["short_comment"] | |
params = {"media_id": self.__media_id, "ps": 20, "sort": order.value} | |
if next is not None: | |
params["cursor"] = next | |
return await Api(**api, credential=credential).update_params(**params).result | |
async def get_long_comment_list( | |
self, | |
order: BangumiCommentOrder = BangumiCommentOrder.DEFAULT, | |
next: Union[str, None] = None, | |
) -> dict: | |
""" | |
获取长评列表 | |
Args: | |
order (BangumiCommentOrder, optional): 排序方式。Defaults to BangumiCommentOrder.DEFAULT | |
next (str | None, optional) : 调用返回结果中的 next 键值,用于获取下一页数据。Defaults to None | |
Returns: | |
dict: 调用 API 返回的结果 | |
""" | |
credential = self.credential if self.credential is not None else Credential() | |
api = API["info"]["long_comment"] | |
params = {"media_id": self.__media_id, "ps": 20, "sort": order.value} | |
if next is not None: | |
params["cursor"] = next | |
return await Api(**api, credential=credential).update_params(**params).result | |
async def get_episode_list(self) -> dict: | |
""" | |
获取季度分集列表,自动转换出海Api的字段,适配部分,但是键还是有不同 | |
Returns: | |
dict: 调用 API 返回的结果 | |
""" | |
if self.oversea: | |
# 转换 ep_id->id ,index_title->longtitle ,index->title | |
fix_ep_list = [] | |
for item in self.ep_list: | |
item["id"] = item.get("ep_id") | |
item["longtitle"] = item.get("index_title") | |
item["title"] = item.get("index") | |
fix_ep_list.append(item) | |
return {"main_section": {"episodes": fix_ep_list}} | |
else: | |
credential = ( | |
self.credential if self.credential is not None else Credential() | |
) | |
api = API["info"]["episodes_list"] | |
params = {"season_id": self.__ssid} | |
return ( | |
await Api(**api, credential=credential).update_params(**params).result | |
) | |
async def get_episodes(self) -> List["Episode"]: | |
""" | |
获取番剧所有的剧集,自动生成类。 | |
""" | |
global episode_data_cache | |
episode_list = await self.get_episode_list() | |
if len(episode_list["main_section"]["episodes"]) == 0: | |
return [] | |
first_epid = episode_list["main_section"]["episodes"][0]["id"] | |
credential = self.credential if self.credential else Credential() | |
content_type = None | |
while content_type != InitialDataType.NEXT_DATA: | |
bangumi_meta, content_type = await get_initial_state( | |
url=f"https://www.bilibili.com/bangumi/play/ep{first_epid}", | |
credential=credential, | |
) | |
bangumi_meta["media_id"] = self.get_media_id() | |
episodes = [] | |
for ep in episode_list["main_section"]["episodes"]: | |
episode_data_cache[ep["id"]] = { | |
"bangumi_meta": bangumi_meta, | |
"bangumi_class": self, | |
} | |
episodes.append(Episode(epid=ep["id"], credential=self.credential)) | |
return episodes | |
async def get_stat(self) -> dict: | |
""" | |
获取番剧播放量,追番等信息 | |
Returns: | |
dict: 调用 API 返回的结果 | |
""" | |
credential = self.credential if self.credential is not None else Credential() | |
api = API["info"]["season_status"] | |
params = {"season_id": self.__ssid} | |
return await Api(**api, credential=credential).update_params(**params).result | |
async def get_overview(self) -> dict: | |
""" | |
获取番剧全面概括信息,包括发布时间、剧集情况、stat 等情况 | |
Returns: | |
dict: 调用 API 返回的结果 | |
""" | |
credential = self.credential if self.credential is not None else Credential() | |
if self.oversea: | |
api = API["info"]["collective_info_oversea"] | |
else: | |
api = API["info"]["collective_info"] | |
params = {"season_id": self.__ssid} | |
return await Api(**api, credential=credential).update_params(**params).result | |
async def set_follow( | |
bangumi: Bangumi, status: bool = True, credential: Union[Credential, None] = None | |
) -> dict: | |
""" | |
追番状态设置 | |
Args: | |
bangumi (Bangumi) : 番剧类 | |
status (bool, optional) : 追番状态. Defaults to True. | |
credential (Credential | None, optional): 凭据. Defaults to None. | |
Returns: | |
dict: 调用 API 返回的结果 | |
""" | |
credential = credential if credential is not None else Credential() | |
credential.raise_for_no_sessdata() | |
api = API["operate"]["follow_add"] if status else API["operate"]["follow_del"] | |
data = {"season_id": bangumi.get_season_id()} | |
return await Api(**api, credential=credential).update_data(**data).result | |
async def update_follow_status( | |
bangumi: Bangumi, status: int, credential: Union[Credential, None] = None | |
) -> dict: | |
""" | |
更新追番状态 | |
Args: | |
bangumi (Bangumi) : 番剧类 | |
credential (Credential | None, optional): 凭据. Defaults to None. | |
status (int) : 追番状态 1 想看 2 在看 3 已看 | |
Returns: | |
dict: 调用 API 返回的结果 | |
""" | |
credential = credential if credential is not None else Credential() | |
credential.raise_for_no_sessdata() | |
api = API["operate"]["follow_status"] | |
data = {"season_id": bangumi.get_season_id(), "status": status} | |
return await Api(**api, credential=credential).update_data(**data).result | |
class Episode(Video): | |
""" | |
番剧剧集类 | |
Attributes: | |
credential (Credential): 凭据类 | |
video_class (Video) : 视频类 | |
bangumi (Bangumi) : 所属番剧 | |
""" | |
def __init__(self, epid: int, credential: Union[Credential, None] = None): | |
""" | |
Args: | |
epid (int) : 番剧 epid | |
credential (Credential, optional): 凭据. Defaults to None. | |
""" | |
global episode_data_cache | |
self.credential: Credential = credential if credential else Credential() | |
self.__epid: int = epid | |
if not epid in episode_data_cache.keys(): | |
res, content_type = get_initial_state_sync( | |
url=f"https://www.bilibili.com/bangumi/play/ep{self.__epid}", | |
credential=self.credential, | |
) # 随机 __NEXT_DATA__ 见 https://github.com/Nemo2011/bilibili-api/issues/433 | |
if content_type == InitialDataType.NEXT_DATA: | |
content = res["props"]["pageProps"]["dehydratedState"]["queries"][0][ | |
"state" | |
]["data"]["seasonInfo"]["mediaInfo"] | |
self.bangumi = ( | |
Bangumi(ssid=content["season_id"]) | |
if not epid in episode_data_cache.keys() | |
else episode_data_cache[epid]["bangumi_class"] | |
) | |
for ep_info in content["episodes"]: | |
if int( | |
ep_info["id"] if "id" in ep_info else ep_info["ep_id"] | |
) == int(epid): | |
bvid = ep_info["bvid"] | |
self.__ep_info: dict = ep_info | |
break | |
else: # InitialDataType.INITIAL_STATE | |
self.__ep_info: dict = res["epInfo"] | |
self.bangumi = ( | |
Bangumi(ssid=res["mediaInfo"]["season_id"]) | |
if not epid in episode_data_cache.keys() | |
else episode_data_cache[epid]["bangumi_class"] | |
) | |
bvid = res["epInfo"]["bvid"] | |
else: | |
content = episode_data_cache[epid]["bangumi_meta"] | |
bvid = None | |
for einfo in content["props"]["pageProps"]["dehydratedState"]["queries"][0][ | |
"state" | |
]["data"]["seasonInfo"]["mediaInfo"]["episodes"]: | |
if einfo["ep_id"] == epid: | |
bvid = einfo["bvid"] | |
self.bangumi = episode_data_cache[epid]["bangumi_class"] | |
self.__ep_info: dict = episode_data_cache[epid] | |
self.video_class = Video(bvid=bvid, credential=self.credential) | |
super().__init__(bvid=bvid, credential=self.credential) | |
self.set_aid = self.set_aid_e | |
self.set_bvid = self.set_bvid_e | |
def get_epid(self) -> int: | |
""" | |
获取 epid | |
""" | |
return self.__epid | |
def set_aid_e(self, aid: int) -> None: | |
print("Set aid is not allowed in Episode") | |
def set_bvid_e(self, bvid: str) -> None: | |
print("Set bvid is not allowed in Episode") | |
async def get_cid(self) -> int: | |
""" | |
获取稿件 cid | |
Returns: | |
int: cid | |
""" | |
return (await self.get_episode_info())["epInfo"]["cid"] | |
def get_bangumi(self) -> "Bangumi": | |
""" | |
获取对应的番剧 | |
Returns: | |
Bangumi: 番剧类 | |
""" | |
return self.bangumi # type: ignore | |
def set_epid(self, epid: int) -> None: | |
self.__init__(epid, self.credential) | |
async def get_episode_info(self) -> dict: | |
""" | |
获取番剧单集信息 | |
Returns: | |
HTML 中的数据 | |
""" | |
if self.__ep_info is None: | |
res, content_type = get_initial_state( | |
url=f"https://www.bilibili.com/bangumi/play/ep{self.__epid}", | |
credential=self.credential, | |
) | |
if content_type == InitialDataType.NEXT_DATA: | |
content = res["props"]["pageProps"]["dehydratedState"]["queries"][0][ | |
"state" | |
]["data"]["mediaInfo"] | |
for ep_info in content["episodes"]: | |
if int( | |
ep_info["id"] if "id" in ep_info else ep_info["ep_id"] | |
) == int(self.get_epid()): | |
return ep_info | |
else: | |
return res["epInfo"] | |
else: | |
return self.__ep_info | |
async def get_bangumi_from_episode(self) -> "Bangumi": | |
""" | |
获取剧集对应的番剧 | |
Returns: | |
Bangumi: 输入的集对应的番剧类 | |
""" | |
info = await self.get_episode_info() | |
ssid = info["mediaInfo"]["season_id"] | |
return Bangumi(ssid=ssid) | |
async def get_download_url(self) -> dict: | |
""" | |
获取番剧剧集下载信息。 | |
Returns: | |
dict: 调用 API 返回的结果。 | |
""" | |
api = API["info"]["playurl"] | |
if True: | |
params = { | |
"avid": self.get_aid(), | |
"ep_id": self.get_epid(), | |
"qn": "127", | |
"otype": "json", | |
"fnval": 4048, | |
"fourk": 1, | |
} | |
return ( | |
await Api(**api, credential=self.credential).update_params(**params).result | |
) | |
async def get_danmaku_xml(self) -> str: | |
""" | |
获取所有弹幕的 xml 源文件(非装填) | |
Returns: | |
str: 文件源 | |
""" | |
cid = await self.get_cid() | |
url = f"https://comment.bilibili.com/{cid}.xml" | |
sess = get_session() | |
config: dict[str, Any] = {"url": url} | |
# 代理 | |
if settings.proxy: | |
config["proxies"] = {"all://", settings.proxy} | |
resp = await sess.get(**config) | |
return resp.content.decode("utf-8") | |
async def get_danmaku_view(self) -> dict: | |
""" | |
获取弹幕设置、特殊弹幕、弹幕数量、弹幕分段等信息。 | |
Returns: | |
dict: 二进制流解析结果 | |
""" | |
return await self.video_class.get_danmaku_view(0) | |
async def get_danmakus( | |
self, date: Union[datetime.date, None] = None | |
) -> List["Danmaku"]: | |
""" | |
获取弹幕 | |
Args: | |
date (datetime.date | None, optional): 指定某一天查询弹幕. Defaults to None. (不指定某一天) | |
Returns: | |
dict[Danmaku]: 弹幕列表 | |
""" | |
return await self.video_class.get_danmakus(0, date) | |
async def get_history_danmaku_index( | |
self, date: Union[datetime.date, None] = None | |
) -> Union[None, List[str]]: | |
""" | |
获取特定月份存在历史弹幕的日期。 | |
Args: | |
date (datetime.date | None, optional): 精确到年月. Defaults to None。 | |
Returns: | |
None | List[str]: 调用 API 返回的结果。不存在时为 None。 | |
""" | |
return await self.video_class.get_history_danmaku_index(0, date) | |