diff --git a/bilibili_api/article.py b/bilibili_api/article.py index cd7a972f..9c537921 100644 --- a/bilibili_api/article.py +++ b/bilibili_api/article.py @@ -66,9 +66,9 @@ class ArticleType(Enum): """ 专栏类型 - - ARTICLE : 普通专栏,不与 opus 动态兼容。 + - ARTICLE : 普通专栏,不与 opus 图文兼容。 - NOTE : 公开笔记 - - SPECIAL_ARTICLE: 特殊专栏,采用笔记格式,且与 opus 动态完全兼容。 + - SPECIAL_ARTICLE: 特殊专栏,采用笔记格式,且与 opus 图文完全兼容。 """ ARTICLE = 0 @@ -188,16 +188,6 @@ def __init__(self, cvid: int, credential: Union[Credential, None] = None): ) self.__dyn_id = int(initial_state[0]["readInfo"]["dyn_id_str"]) - def turn_to_dynamic(self) -> "dynamic.Dynamic": - """ - 对于完全与 opus 兼容的部分的特殊专栏,将 Article 对象转换为 Dynamic 对象。 - - Returns: - Dynamic: Dynamic 对象 - """ - raise_for_statement(self.__type == ArticleType.SPECIAL_ARTICLE, "仅支持特殊专栏 (ArticleType.SPECIAL_ARTICLE)") - return dynamic.Dynamic(self.__dyn_id, credential=self.credential) - def get_cvid(self) -> int: return self.__cvid diff --git a/bilibili_api/data/api/dynamic.json b/bilibili_api/data/api/dynamic.json index 53479770..f656614e 100644 --- a/bilibili_api/data/api/dynamic.json +++ b/bilibili_api/data/api/dynamic.json @@ -199,15 +199,15 @@ }, "comment": "动态详细信息" }, - "detail_new": { - "url": "https://api.bilibili.com/x/polymer/web-dynamic/v1/opus/detail", + "reaction": { + "url": "https://api.bilibili.com/x/polymer/web-dynamic/v1/detail/reaction", "method": "GET", "verify": false, "params": { - "timezone_offset": "int: 时区偏移量", - "id": "int: 动态 ID" - }, - "comment": "动态详细信息" + "web_location": "str: 333.1369", + "id": "int: 动态 ID", + "offset": "str: 空" + } }, "dynamic_page_UPs_info": { "url": "https://api.bilibili.com/x/polymer/web-dynamic/v1/portal", diff --git a/bilibili_api/data/api/opus.json b/bilibili_api/data/api/opus.json new file mode 100644 index 00000000..d9e5dfa6 --- /dev/null +++ b/bilibili_api/data/api/opus.json @@ -0,0 +1,35 @@ +{ + "info": { + "detail_new": { + "url": "https://api.bilibili.com/x/polymer/web-dynamic/v1/opus/detail", + "method": "GET", + "verify": false, + "params": { + "timezone_offset": "int: 时区偏移量", + "id": "int: 动态 ID" + }, + "comment": "动态详细信息" + } + }, + "operate": { + "simple_action": { + "url": "https://api.bilibili.com/x/community/cosmo/interface/simple_action", + "method": "POST", + "verify": true, + "data": { + "meta": { + "spmid": "444.42.0.0", + "from_spmid": "333.1365.0.0", + "from": "unknown" + }, + "entity": { + "object_id_str": "890484395664736288", + "type": { "biz": 2 } + }, + "action": 4 + }, + "json_body": true, + "comment": "收藏/取消收藏" + } + } +} diff --git a/bilibili_api/dynamic.py b/bilibili_api/dynamic.py index 80ea5f08..9b5b9ec3 100644 --- a/bilibili_api/dynamic.py +++ b/bilibili_api/dynamic.py @@ -45,7 +45,7 @@ class DynamicType(Enum): VIDEO = "video" -class SendDynmaicType(Enum): +class SendDynamicType(Enum): """ 发送动态类型 scene 参数 @@ -58,7 +58,7 @@ class SendDynmaicType(Enum): IMAGE = 2 -class DynmaicContentType(Enum): +class DynamicContentType(Enum): """ 动态内容类型 @@ -74,6 +74,18 @@ class DynmaicContentType(Enum): VOTE = 4 +class DynamicType(Enum): + """ + 动态类型 + + + NORMAL: 普通动态 (链接为 https://t.bilibili.com/***) + + OPUS: 图文动态 (链接为 https://www.bilibili.com/opus/***) + """ + + NORMAL = "normal" + OPUS = "opus" + + async def _parse_at(text: str) -> Tuple[str, str, str]: """ @人格式:“@用户名 ”(注意最后有空格) @@ -243,7 +255,7 @@ def upload_image_sync( return return_info -class BuildDynmaic: +class BuildDynamic: """ 构建动态内容. 提供两种 API. @@ -276,7 +288,7 @@ def empty(): """ 新建空的动态以链式逐步构建 """ - return BuildDynmaic() + return BuildDynamic() @staticmethod def create_by_args( @@ -303,7 +315,7 @@ def create_by_args( send_time (datetime | None, optional): 发送时间. Defaults to None. """ - dyn = BuildDynmaic() + dyn = BuildDynamic() dyn.add_text(text) dyn.add_image(pics) if topic_id != -1: @@ -316,7 +328,7 @@ def create_by_args( dyn.set_send_time(send_time) return dyn - def add_plain_text(self, text: str) -> "BuildDynmaic": + def add_plain_text(self, text: str) -> "BuildDynamic": """ 添加纯文本 @@ -324,11 +336,11 @@ def add_plain_text(self, text: str) -> "BuildDynmaic": text (str): 文本内容 """ self.contents.append( - {"biz_id": "", "type": DynmaicContentType.TEXT.value, "raw_text": text} + {"biz_id": "", "type": DynamicContentType.TEXT.value, "raw_text": text} ) return self - def add_at(self, uid: Union[int, user.User]) -> "BuildDynmaic": + def add_at(self, uid: Union[int, user.User]) -> "BuildDynamic": """ 添加@用户,支持传入 User 类或 UID @@ -339,11 +351,11 @@ def add_at(self, uid: Union[int, user.User]) -> "BuildDynmaic": uid = uid.__uid name = user.User(uid).get_user_info_sync().get("name") self.contents.append( - {"biz_id": uid, "type": DynmaicContentType.AT.value, "raw_text": f"@{name}"} + {"biz_id": uid, "type": DynamicContentType.AT.value, "raw_text": f"@{name}"} ) return self - def add_emoji(self, emoji_id: int) -> "BuildDynmaic": + def add_emoji(self, emoji_id: int) -> "BuildDynamic": """ 添加表情 @@ -359,24 +371,24 @@ def add_emoji(self, emoji_id: int) -> "BuildDynmaic": self.contents.append( { "biz_id": "", - "type": DynmaicContentType.EMOJI.value, + "type": DynamicContentType.EMOJI.value, "raw_text": emote_info[str(emoji_id)], } ) return self - def add_vote(self, vote: vote.Vote) -> "BuildDynmaic": + def add_vote(self, vote: vote.Vote) -> "BuildDynamic": vote.get_info_sync() self.contents.append( { "biz_id": str(vote.get_vote_id()), - "type": DynmaicContentType.VOTE.value, + "type": DynamicContentType.VOTE.value, "raw_text": vote.title, } ) return self - def add_image(self, image: Union[List[Picture], Picture]) -> "BuildDynmaic": + def add_image(self, image: Union[List[Picture], Picture]) -> "BuildDynamic": """ 添加图片 @@ -388,7 +400,7 @@ def add_image(self, image: Union[List[Picture], Picture]) -> "BuildDynmaic": self.pics += image return self - def add_text(self, text: str) -> "BuildDynmaic": + def add_text(self, text: str) -> "BuildDynamic": """ 添加文本 (可包括 at, 表情包) @@ -500,7 +512,7 @@ def base_split(texts: List[str], at_and_emoji: List, last_length: int): self.contents.append( { "biz_id": piece["uid"], - "type": DynmaicContentType.AT.value, + "type": DynamicContentType.AT.value, "raw_text": piece["text"], } ) @@ -508,13 +520,13 @@ def base_split(texts: List[str], at_and_emoji: List, last_length: int): self.contents.append( { "biz_id": "", - "type": DynmaicContentType.EMOJI.value, + "type": DynamicContentType.EMOJI.value, "raw_text": piece["text"], } ) return self - def set_attach_card(self, oid: int) -> "BuildDynmaic": + def set_attach_card(self, oid: int) -> "BuildDynamic": """ 设置直播预约 @@ -531,7 +543,7 @@ def set_attach_card(self, oid: int) -> "BuildDynmaic": } return self - def set_topic(self, topic_id: int) -> "BuildDynmaic": + def set_topic(self, topic_id: int) -> "BuildDynamic": """ 设置话题 @@ -543,7 +555,7 @@ def set_topic(self, topic_id: int) -> "BuildDynmaic": def set_options( self, up_choose_comment: bool = False, close_comment: bool = False - ) -> "BuildDynmaic": + ) -> "BuildDynamic": """ 设置选项 @@ -568,10 +580,10 @@ def set_send_time(self, time: datetime): self.time = time return self - def get_dynamic_type(self) -> SendDynmaicType: + def get_dynamic_type(self) -> SendDynamicType: if len(self.pics) != 0: - return SendDynmaicType.IMAGE - return SendDynmaicType.TEXT + return SendDynamicType.IMAGE + return SendDynamicType.TEXT def get_contents(self) -> list: return self.contents @@ -589,12 +601,12 @@ def get_options(self) -> dict: return self.options -async def send_dynamic(info: BuildDynmaic, credential: Credential): +async def send_dynamic(info: BuildDynamic, credential: Credential): """ 发送动态 Args: - info (BuildDynmaic): 动态内容 + info (BuildDynamic): 动态内容 credential (Credential): 凭据 @@ -738,42 +750,33 @@ def __init__( """ self.__dynamic_id = dynamic_id self.credential = credential if credential is not None else Credential() - api = API["info"]["detail_new"] + + api = API["info"]["detail"] params = { "id": self.__dynamic_id, "timezone_offset": -480, + "features": "itemOpusStyle", } data = ( Api(**api, credential=self.credential).update_params(**params).result_sync ) - self.__special_article = data["item"]["type"] == 1 - self.__rid_str = int(data["item"]["basic"]["rid_str"]) - - def is_special_article(self) -> bool: - """ - 是否为部分的特殊专栏。此部分专栏与动态完全兼容。 - - Returns: - bool: 是否为特殊专栏 - """ - return self.__special_article + self.__opus = data["item"]["basic"]["comment_type"] == 11 def get_dynamic_id(self) -> int: return self.__dynamic_id - def turn_to_article(self) -> "article.Article": + def get_dynamic_type(self) -> DynamicType: """ - 对于部分的特殊专栏,将 Dynamic 对象转换为 Article 对象。 + 获取动态类型 Returns: - Article: Article 对象 + DynamicType: 动态类型 """ - raise_for_statement(self.__special_article, "仅支持特殊专栏") - return article.Article(self.__rid_str, credential=self.credential) + return DynamicType.OPUS if self.__opus else DynamicType.NORMAL async def get_info(self, features: str = "itemOpusStyle") -> dict: """ - (不建议使用此旧版 API,请转到新版 get_info_opus) + (对 Opus 动态,获取动态内容建议使用 Opus.get_detail()) 获取动态信息 @@ -795,19 +798,19 @@ async def get_info(self, features: str = "itemOpusStyle") -> dict: ) return data - async def get_info_opus(self) -> dict: + async def get_reaction(self, offset: str = "") -> dict: """ - 新版获取动态信息 + 获取点赞、转发 + + Args: + offset (str, optional): 偏移值(下一页的第一个动态 ID,为该请求结果中的 offset 键对应的值),类似单向链表. Defaults to "" Returns: dict: 调用 API 返回的结果 """ - api = API["info"]["detail_new"] - params = { - "id": self.__dynamic_id, - "timezone_offset": -480, - } + api = API["info"]["reaction"] + params = {"web_location": "333.1369", "offset": "", "id": self.get_dynamic_id()} return ( await Api(**api, credential=self.credential).update_params(**params).result ) diff --git a/docs/modules/article.md b/docs/modules/article.md index aa513e4c..c8ed6d84 100644 --- a/docs/modules/article.md +++ b/docs/modules/article.md @@ -43,9 +43,9 @@ from bilibili_api import article **Extends:** enum.Enum -- ARTICLE : 普通专栏 +- ARTICLE : 普通专栏,不与 opus 图文兼容。 - NOTE : 笔记专栏 -- SPECIAL_ARTICLE: 特殊专栏,采用笔记格式,且与 opus 动态完全兼容。 +- SPECIAL_ARTICLE: 特殊专栏,采用笔记格式,且与 opus 图文完全兼容。 ## class ArticleList @@ -115,12 +115,6 @@ from bilibili_api import article **Returns:** Note: 笔记类 -#### def turn_to_dynamic() - -对于完全与 opus 兼容的部分的特殊专栏,将 Article 对象转换为 Dynamic 对象。 - -**Returns:** Dynamic: Dynamic 对象 - #### def markdown() 转换为 Markdown diff --git a/docs/modules/dynamic.md b/docs/modules/dynamic.md index 41a3836f..3ec64a0d 100644 --- a/docs/modules/dynamic.md +++ b/docs/modules/dynamic.md @@ -8,10 +8,10 @@ from bilibili_api import dynamic ## async def upload_image() -| name | type | description | -| ------------ | ----------------- | ----------- | -| image | Picture | 图片流 | -| credential | Credential | 凭据 | +| name | type | description | +| ---------- | ---------- | ----------- | +| image | Picture | 图片流 | +| credential | Credential | 凭据 | 上传动态图片 @@ -19,19 +19,19 @@ from bilibili_api import dynamic --- -## class BuildDynmaic +## class BuildDynamic 构建动态内容 ### Attributes -| name | type | description | -| ---- | ---- | ----------- | -| contents | List | 动态内容字段 | -| pics | List | 图片字段 | +| name | type | description | +| ----------- | ---- | ------------ | +| contents | List | 动态内容字段 | +| pics | List | 图片字段 | | attach_card | dict | 动态卡片字段 | -| topic | dict | 话题字段 | -| options | dict | 选项字段 | +| topic | dict | 话题字段 | +| options | dict | 选项字段 | ### Functions @@ -39,7 +39,7 @@ from bilibili_api import dynamic | name | type | description | | ---- | ---- | ----------- | -| text | str | 文本内容 | +| text | str | 文本内容 | 添加文本内容(可以附加 at 人和表情包) @@ -47,38 +47,39 @@ from bilibili_api import dynamic | name | type | description | | ---- | ---- | ----------- | -| text | str | 文本内容 | +| text | str | 文本内容 | 添加纯内容 #### def add_at() -| name | type | description | -| ---- | ---- | ----------- | +| name | type | description | +| ---- | --------- | ---------------- | | user | int, User | 用户 ID 或用户类 | 添加 @ 用户 #### def add_emoji() -| name | type | description | -| ---- | ---- | ----------- | -| emoji_name | str | 表情中文名称 | +| name | type | description | +| ---------- | ---- | ------------ | +| emoji_name | str | 表情中文名称 | 添加表情 #### def add_vote() -| name | type | description | -| ---- | ---- | ----------- | +| name | type | description | +| ---- | --------- | ---------------- | | vote | Vote, int | 投票类或 vote_id | 添加投票 + #### def add_image() -| name | type | description | -| ---- | ---- | ----------- | -| image | Picture | 图片类 | +| name | type | description | +| ----- | ------- | ----------- | +| image | Picture | 图片类 | 添加图片 @@ -86,7 +87,7 @@ from bilibili_api import dynamic | name | type | description | | ---- | ---- | ----------- | -| oid | int | 卡片id | +| oid | int | 卡片 id | 设置直播预约 @@ -94,18 +95,18 @@ from bilibili_api import dynamic #### def set_topic() -| name | type | description | -| ---- | ---- | ----------- | -| topic_id | int, Topic | 话题id 或话题类 | +| name | type | description | +| -------- | ---------- | ---------------- | +| topic_id | int, Topic | 话题 id 或话题类 | 设置话题 #### def set_options() -| name | type | description | -| ---- | ---- | ----------- | +| name | type | description | +| ----------------- | ---- | ------------ | | up_choose_comment | bool | 开启精选评论 | -| close_comment | bool | 关闭评论 | +| close_comment | bool | 关闭评论 | 设置选项 @@ -113,12 +114,12 @@ from bilibili_api import dynamic ## async def send_dynamic() -| name | type | description | -| ------------ | ------------ | ----------- | -| info | BuildDynmaic | 动态构建类 | -| credential | Credential | 凭据 | +| name | type | description | +| ---------- | ------------ | ----------- | +| info | BuildDynamic | 动态构建类 | +| credential | Credential | 凭据 | -发送动态(Web端) +发送动态(Web 端) **Returns:** dict: 调用 API 返回的结果 @@ -168,34 +169,28 @@ from bilibili_api import dynamic ### Attributes -| name | type | description | -| ---- | ---- | ----------- | -| credential | Credential | 凭据 | +| name | type | description | +| ---------- | ---------- | ----------- | +| credential | Credential | 凭据 | ### Functions #### def \_\_init\_\_() -| name | type | description | -| ---------- | ---------- | ----------- | -| dynamic_id | int | 动态 ID | +| name | type | description | +| ---------- | ------------------ | ----------- | +| dynamic_id | int | 动态 ID | | credential | Credential \| None | 凭据 | #### def get_dynamic_id() 获取 dynamic_id -#### def is_special_article() +#### def get_dynamic_type() -是否为部分的特殊专栏。此部分专栏与动态完全兼容。 +获取动态类型 -**Returns:** bool: 是否为特殊专栏 - -#### def turn_to_article() - -对于部分的特殊专栏,将 Dynamic 对象转换为 Article 对象。 - -**Returns:** Article: Article 对象 +**Returns:** DynamicType: 动态类型 #### async def get_info() @@ -203,22 +198,26 @@ from bilibili_api import dynamic | -------- | ------------- | ------------------- | | features | str, optional | 默认 itemOpusStyle. | -**(不建议使用此旧版 API,请转到新版 get_info_opus)** +**(对 Opus 动态,获取动态内容建议使用 Opus.get_detail())** 获取动态信息 **Returns:** dict: 调用 API 返回的结果 -#### async def get_info_opus() +#### async def get_reaction() -新版获取动态信息 +| name | type | description | +| ------ | ------------- | -------------------------------------------------------------------------------------------------- | +| offset | str, optional | 偏移值(下一页的第一个动态 ID,为该请求结果中的 offset 键对应的值),类似单向链表. Defaults to "" | + +获取点赞、转发 **Returns:** dict: 调用 API 返回的结果 #### async def get_reposts() -| name | type | description | -| ------ | ------------- | ------------------------------------------------------------ | +| name | type | description | +| ------ | ------------- | -------------------------------------------------------------------------------------------------- | | offset | str, optional | 偏移值(下一页的第一个动态 ID,为该请求结果中的 offset 键对应的值),类似单向链表. Defaults to "0" | 获取动态转发列表 @@ -227,10 +226,10 @@ from bilibili_api import dynamic #### async def get_like() -| name | type | description | -| ------ | -------------- | --------------------------- | -| pn | int | 页码. Defaults to 1. | -| ps | int | 每页大小. Defaults to 30. | +| name | type | description | +| ---- | ---- | ------------------------- | +| pn | int | 页码. Defaults to 1. | +| ps | int | 每页大小. Defaults to 30. | 获取动态点赞列表 @@ -254,8 +253,8 @@ from bilibili_api import dynamic #### async def repost() -| name | type | description | -| ---- | ------------- | -------------------------------------------- | +| name | type | description | +| ---- | --------------------- | -------------------------------------------- | | text | str \| None, optional | 转发动态时的文本内容. Defaults to "转发动态" | 转发动态 @@ -266,8 +265,8 @@ from bilibili_api import dynamic #### async def get_new_dynamic_users() -| name | type | description | -| - | - | - | +| name | type | description | +| ---------- | ------------------ | ------------------------- | | credential | Credential \| None | 凭据类. Defaults to None. | 获取更新动态的关注者 @@ -278,10 +277,10 @@ from bilibili_api import dynamic #### async def get_live_users() -| name | type | description | -| - | - | - | -| size | int | 获取的数据数量. Defaults to 10. | -| credential | Credential \| None | 凭据类. Defaults to None. | +| name | type | description | +| ---------- | ------------------ | ------------------------------- | +| size | int | 获取的数据数量. Defaults to 10. | +| credential | Credential \| None | 凭据类. Defaults to None. | 获取正在直播的关注者 @@ -291,9 +290,9 @@ from bilibili_api import dynamic #### async def get_dynamic_page_UPs_info() -| name | type | description | -| - | - | - | -| credential | Credential | 凭据类. | +| name | type | description | +| ---------- | ---------- | ----------- | +| credential | Credential | 凭据类. | 获取动态页 UP 主列表 @@ -303,18 +302,18 @@ from bilibili_api import dynamic #### async def get_dynamic_page_info() -| name | type | description | -| - | - | - | -| credential | Credential | 凭据类. | -| _type | DynamicType, optional | 动态类型. Defaults to None. | -| host_mid | int, optional | UP 主 UID. Defaults to None. | -| features | str, optional | 默认 itemOpusStyle. | -| offset | int, optional | 偏移值(下一页的第一个动态 ID,为该请求结果中的 offset 键对应的值),类似单向链表. Defaults to None. | -| pn | int | 页码. Defaults to 1. | +| name | type | description | +| ---------- | --------------------- | ---------------------------------------------------------------------------------------------------- | +| credential | Credential | 凭据类. | +| \_type | DynamicType, optional | 动态类型. Defaults to None. | +| host_mid | int, optional | UP 主 UID. Defaults to None. | +| features | str, optional | 默认 itemOpusStyle. | +| offset | int, optional | 偏移值(下一页的第一个动态 ID,为该请求结果中的 offset 键对应的值),类似单向链表. Defaults to None. | +| pn | int | 页码. Defaults to 1. | 获取动态页动态列表 -获取全部动态或者相应类型需传入 _type +获取全部动态或者相应类型需传入 \_type 获取指定 UP 主动态需传入 host_mid diff --git a/docs/opus.md b/docs/opus.md new file mode 100644 index 00000000..c7ba5052 --- /dev/null +++ b/docs/opus.md @@ -0,0 +1,52 @@ +# `Opus`, `Article`, `Dynamic` + +~~不得不说阿 b 真会整活~~ + +为防止许多人对这三个类之间的关系、定义不理解,下文将作出详细解释。 + +## 1. 定义 + +- `Opus`: 图文。可用于访问链接为 `https://www.bilibili.com/opus/***`。 +- `Article`: 专栏。可用于访问链接为 `https://www.bilibili.com/read/cv***`。 +- `Dynamic`: 动态。可用于访问链接为 `https://t.bilibili.com/***`。 + +例如崩坏星穹铁道的版本更新说明,可以用以下两个链接访问: + +- `https://www.bilibili.com/read/cv27705422/` +- `https://www.bilibili.com/opus/863994527716737095` + +所以这个版本更新说明既是专栏,又是图文。 + +## 2. 分类 + +接下来我们对各个类之间的关系进行一个梳理: + +``` + |---------| + 专栏图文 + 公开笔记 + 普通专栏 = | Article | + |---------| + + + + + 动态图文 + + = + +|---------| |---------| +| Opus | + 普通动态 = | Dynamic | +|---------| |---------| +``` + +## 3. 解析 + +1. 部分专栏和动态发布时,会自动转成图文,但是他们还是属于动专栏和动态。 +2. 在创建图文之后,也会对专栏创建对应动态。大概率是因为图文的 `API` 部分是直接照抄动态的来用的。因此所有的图文全部属于动态。 +3. 剩下不是图文的专栏或者动态,即为普通专栏和普通动态。 +4. 公开笔记会以专栏的形式存在。 + +## 4. 模块提供的类之间的转换 + +- `Article` -> `Opus` +- `Dynamic` -> `Opus` +- `Opus` -> `Article` +- `Opus` -> `Dynamic` diff --git a/tests/test_dynamic.py b/tests/test_dynamic.py index 4fc283ee..c6f25344 100644 --- a/tests/test_dynamic.py +++ b/tests/test_dynamic.py @@ -18,7 +18,7 @@ # # 测试发送动态 # print("测试立即发送纯文本动态") # text_dynamic_build = ( -# dynamic.BuildDynmaic() +# dynamic.BuildDynamic() # .add_text("测试立即发送纯文本动态") # .add_image( # Picture.from_file("./design/logo.png").upload_file_sync( @@ -91,5 +91,5 @@ async def test_n_get_dynamic_page_info_by_mid(): ) -async def test_o_get_dynamic_info_opus(): - return await dy.get_info_opus() +async def test_p_get_reaction(): + return await dy.get_reaction()