From d1ee41819589ba1bbe0c7ad90cd7fb50a744bab8 Mon Sep 17 00:00:00 2001 From: yyoung Date: Mon, 25 Dec 2023 22:12:24 +0800 Subject: [PATCH] fix: update Bilibili API (close #88) --- docs/site/bilibili.md | 28 +++------ docs/site/bilibili.zh.md | 28 +++------ nazurin/config.py | 3 +- nazurin/sites/bilibili/api.py | 97 +++++++++++++++-------------- nazurin/sites/bilibili/config.py | 2 +- nazurin/sites/bilibili/interface.py | 2 + 6 files changed, 68 insertions(+), 92 deletions(-) diff --git a/docs/site/bilibili.md b/docs/site/bilibili.md index 3071f8d7..cb4e002d 100644 --- a/docs/site/bilibili.md +++ b/docs/site/bilibili.md @@ -14,7 +14,7 @@ Storage path for downloaded images. ### BILIBILI_FILE_NAME -:material-lightbulb-on: Optional, defaults to `{dynamic_id_str}_{index} - {user[name]}({user[uid]})` +:material-lightbulb-on: Optional, defaults to `{id_str}_{index} - {user[name]}({user[mid]})` File name for downloaded images. @@ -24,31 +24,17 @@ _Only common used ones are listed._ ```json { - "item": { - "pictures": [ - { - "img_height": 900, - "img_size": 840.989990234375, - "img_width": 1600 - } - ], - "pictures_count": 1, - "reply": 5, - "title": "", - "upload_time": "Upload time" - }, "user": { "name": "User name", - "uid": "User ID" + "mid": "User ID" }, - "dynamic_id_str": "ID", - "view": 497, - "repost": 5, - "comment": 5, - "like": 68, + "id_str": "ID", "timestamp": "Timestamp", "filename": "Original file name, without extension", - "pic": "Current image object in `item.pictures`", + "pic": { + "height": 1440, + "width": 1080 + }, "index": "Image index" } ``` diff --git a/docs/site/bilibili.zh.md b/docs/site/bilibili.zh.md index 68eb288e..47effd9e 100644 --- a/docs/site/bilibili.zh.md +++ b/docs/site/bilibili.zh.md @@ -14,7 +14,7 @@ ### BILIBILI_FILE_NAME -:material-lightbulb-on: 可选,默认为 `{dynamic_id_str}_{index} - {user[name]}({user[uid]})` +:material-lightbulb-on: 可选,默认为 `{id_str}_{index} - {user[name]}({user[mid]})` 文件名称。 @@ -24,31 +24,17 @@ _此处只列出常用项。_ ```json { - "item": { - "pictures": [ - { - "img_height": 900, - "img_size": 840.989990234375, - "img_width": 1600 - } - ], - "pictures_count": 1, - "reply": 5, - "title": "", - "upload_time": "上传时间" - }, "user": { "name": "用户名称", - "uid": "用户ID" + "mid": "用户ID" }, - "dynamic_id_str": "ID", - "view": 497, - "repost": 5, - "comment": 5, - "like": 68, + "id_str": "ID", "timestamp": "时间戳", "filename": "原始文件名称,不含扩展名", - "pic": "当前图片在`item.pictures`中的对象", + "pic": { + "height": 1440, + "width": 1080 + }, "index": "当前图片索引" } ``` diff --git a/nazurin/config.py b/nazurin/config.py index 4fe90cd2..16f454be 100644 --- a/nazurin/config.py +++ b/nazurin/config.py @@ -40,8 +40,7 @@ UA = ( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " - "Chrome/90.0.4430.85 " - "Safari/537.36" + "Chrome/120.0.0.0 Safari/537.36" ) # Local directory to store database and temporary files diff --git a/nazurin/sites/bilibili/api.py b/nazurin/sites/bilibili/api.py index a2c3ff56..0a3b4142 100644 --- a/nazurin/sites/bilibili/api.py +++ b/nazurin/sites/bilibili/api.py @@ -1,4 +1,3 @@ -import json import os from datetime import datetime from typing import List, Tuple @@ -13,60 +12,48 @@ class Bilibili: @network_retry - async def get_dynamic(self, dynamic_id: int): + async def get_dynamic(self, dynamic_id: str): """Get dynamic data from API.""" api = ( - "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr" - "/get_dynamic_detail?dynamic_id=" + str(dynamic_id) + f"https://api.bilibili.com/x/polymer/web-dynamic/v1/detail?id={dynamic_id}" ) async with Request() as request: async with request.get(api) as response: response.raise_for_status() data = await response.json() # For some IDs, the API returns code 0 but empty content - if data["code"] == 500207 or ( - data["code"] == 0 and "card" not in data["data"] - ): + code = data.get("code") + if code == 4101147 or "data" not in data: raise NazurinError("Dynamic not found") - if data["code"] != 0: - raise NazurinError("Failed to get dynamic: " + data["message"]) - card = data["data"]["card"] - desc = card["desc"] - card = json.loads(card["card"]) - card.update( - { - "type": desc["type"], - "dynamic_id_str": desc["dynamic_id_str"], - "view": desc["view"], - "repost": desc["repost"], - "comment": desc["comment"], - "like": desc["like"], - "timestamp": desc["timestamp"], - } - ) - if "vip" in card["user"]: - del card["user"]["vip"] - return card + if code != 0: + raise NazurinError( + f"Failed to get dynamic: code = {code}, message = {data['message']}" + ) + item = data["data"]["item"] + return self.cleanup_item(item) - async def fetch(self, dynamic_id: int) -> Illust: + async def fetch(self, dynamic_id: str) -> Illust: """Fetch images and detail.""" - card = await self.get_dynamic(dynamic_id) - imgs = self.get_images(card) - caption = self.build_caption(card) - caption["url"] = f"https://t.bilibili.com/{dynamic_id}" - return Illust(imgs, caption, card) + item = await self.get_dynamic(dynamic_id) + imgs = self.get_images(item) + caption = self.build_caption(item) + caption["url"] = f"https://www.bilibili.com/opus/{dynamic_id}" + return Illust(imgs, caption, item) @staticmethod - def get_images(card) -> List[Image]: + def get_images(item: dict) -> List[Image]: """Get all images in a dynamic card.""" - if "item" not in card or "pictures" not in card["item"]: + major_items = item["modules"]["module_dynamic"]["major"] + if not major_items: + raise NazurinError("No image found") + draw_items = major_items["draw"]["items"] + if not len(draw_items): raise NazurinError("No image found") - pics = card["item"]["pictures"] imgs = [] - for index, pic in enumerate(pics): - url = pic["img_src"] - destination, filename = Bilibili.get_storage_dest(card, pic, index) - size = pic["img_size"] * 1024 # size returned by API is in KB + for index, pic in enumerate(draw_items): + url = pic["src"] + destination, filename = Bilibili.get_storage_dest(item, pic, index) + size = pic["size"] * 1024 # size returned by API is in KB # Sometimes it returns a wrong size that is not in whole bytes, # in this case we just ignore it. if size % 1 != 0: @@ -78,30 +65,32 @@ def get_images(card) -> List[Image]: destination, url + "@518w.jpg", size, - pic["img_width"], - pic["img_height"], + pic["width"], + pic["height"], ) ) return imgs @staticmethod - def get_storage_dest(card: dict, pic: dict, index: int = 0) -> Tuple[str, str]: + def get_storage_dest(item: dict, pic: dict, index: int = 0) -> Tuple[str, str]: """ Format destination and filename. """ - url = pic["img_src"] - timestamp = datetime.fromtimestamp(card["timestamp"]) + url = pic["src"] + timestamp = datetime.fromtimestamp(item["modules"]["module_author"]["pub_ts"]) basename = os.path.basename(url) filename, extension = os.path.splitext(basename) + user = item["modules"]["module_author"] context = { - **card, + "user": user, # Original filename, without extension "filename": filename, # Image index "index": index, "timestamp": timestamp, "extension": extension, + "id_str": item["id_str"], "pic": pic, } return ( @@ -110,7 +99,21 @@ def get_storage_dest(card: dict, pic: dict, index: int = 0) -> Tuple[str, str]: ) @staticmethod - def build_caption(card) -> Caption: + def build_caption(item: dict) -> Caption: + modules = item["modules"] return Caption( - {"author": card["user"]["name"], "content": card["item"]["description"]} + { + "author": "#" + modules["module_author"]["name"], + "content": modules["module_dynamic"]["desc"]["text"], + } ) + + @staticmethod + def cleanup_item(item: dict) -> dict: + try: + del item["basic"] + del item["modules"]["module_author"]["avatar"] + del item["modules"]["module_more"] + except KeyError: + pass + return item diff --git a/nazurin/sites/bilibili/config.py b/nazurin/sites/bilibili/config.py index 710dd417..c70b4f69 100644 --- a/nazurin/sites/bilibili/config.py +++ b/nazurin/sites/bilibili/config.py @@ -7,5 +7,5 @@ with env.prefixed("FILE_"): DESTINATION: str = env.str("PATH", default="Bilibili") FILENAME: str = env.str( - "NAME", default="{dynamic_id_str}_{index} - {user[name]}({user[uid]})" + "NAME", default="{id_str}_{index} - {user[name]}({user[mid]})" ) diff --git a/nazurin/sites/bilibili/interface.py b/nazurin/sites/bilibili/interface.py index 0d89b7e1..87ca59e1 100644 --- a/nazurin/sites/bilibili/interface.py +++ b/nazurin/sites/bilibili/interface.py @@ -11,6 +11,8 @@ r"t\.bilibili\.com/(\d+)", # https://t.bilibili.com/h5/dynamic/detail/123456789012345678 r"t\.bilibili\.com/h5/dynamic/detail/(\d+)", + # https://www.bilibili.com/opus/123456789012345678 + r"bilibili\.com/opus/(\d+)", ]