diff --git a/bilibili_api/__init__.py b/bilibili_api/__init__.py index aa34198f..7e0e98b2 100644 --- a/bilibili_api/__init__.py +++ b/bilibili_api/__init__.py @@ -10,7 +10,7 @@ from .utils.sync import sync from .utils.credential_refresh import Credential from .utils.picture import Picture -from .utils.short import get_real_url +from .utils.short import get_real_url, get_short_url from .utils.parse_link import ResourceType, parse_link from .utils.aid_bvid_transformer import aid2bvid, bvid2aid from .utils.danmaku import DmMode, Danmaku, DmFontSize, SpecialDanmaku @@ -135,6 +135,7 @@ "game", "get_aiohttp_session", "get_real_url", + "get_short_url", "get_session", "homepage", "hot", diff --git a/bilibili_api/utils/short.py b/bilibili_api/utils/short.py index ad84896d..a7819d3d 100644 --- a/bilibili_api/utils/short.py +++ b/bilibili_api/utils/short.py @@ -3,28 +3,158 @@ 一个很简单的处理短链接的模块,主要是读取跳转链接。 """ + from typing import Optional from .. import settings from .credential import Credential -from .network import get_session, get_aiohttp_session +from .network import get_session, get_aiohttp_session, get_spi_buvid, HEADERS +from ..exceptions import ApiException, ArgsException + +import re +import random +import string + + +async def acquire_buvid(credential: Optional[Credential] = None) -> str: + """ + 从Credential中取出buvid3,若不存在则通过spi获取buvid,若都不存在则随机生成一个的buvid3。 + + Args: + credential(Credential | None): 凭据类。 + + Returns: + buvid3的字符串 + """ + + # return given buvid3 if possible + if credential: + buvid3 = credential.get_cookies()["buvid3"] + + if buvid3 is not None: + return buvid3 + + # use spi to get buvid3 + try: + return (await get_spi_buvid())["data"]["b_3"] + + except KeyError: # if data or b_3 does not exist by spi + pass + + # random generation if spi is not possible + buvid3_pattern = ( + "8-4-4-4-17" # current buvid3 char-length pattern, might be changed later + ) + parts = buvid3_pattern.split("-") + + buvid3_rand_gen = [ + "".join(random.choices(string.digits + string.ascii_letters, k=int(part))) + for part in parts + ] + + return "-".join(buvid3_rand_gen) + "infoc" + + +async def get_short_url( + oid: Optional[int] = None, + share_content: Optional[str] = None, + share_title: Optional[str] = None, + share_origin: Optional[str] = "vinfo_share", + share_mode: Optional[int] = 3, + share_id: Optional[str] = "public.webview.0.0.pv", + platform: Optional[str] = "android", + mobi_app: Optional[str] = "android", + panel_type: Optional[int] = 1, + # regex_real_url: Optional[bool] = False, + credential: Optional[Credential] = None, +) -> str: + """ + 获取目标链接的短链接。支持 bilibili.com 的相关链接。 + + 尽管目标链接可能不存在,但仍然会生成短链接。 + 相同的目标链接重复调用此方法会获得不同的短链接。 -async def get_real_url(short_url: str, credential: Optional[Credential] = None) -> str: + Args: + oid (int | None): 内容 oid。 + + share_content (str | None): 分享内容。 + + share_title (str | None): 分享标题。 + + share_origin (str | None): 分享来源。 + + share_mode (int | None): 分享模式。 + + share_id (str | None): 分享 id。 + + platform (str | None): 平台。 + + mobi_app (str | None): 移动端应用。 + + panel_type (int | None): 面板类型。 + + credential (Credential | None): 凭据类。 + + Returns: + str: 目标链接的 b23.tv 短链接信息 (不一定为单独 URL) + """ + # 应该具体为检测 oid 类型 + # if regex_real_url: + # # validate the starting part of url + # url_start_pattern = re.compile(pattern=r"^https?:\/\/(?:www\.)?bilibili\.com") + + # if not re.match(pattern=url_start_pattern, string=share_content): + # raise ArgsException( + # msg=f"提供的 {share_content} 不符合格式。\ + # 支持的格式为 bilibili.com 的相关链接并含有 http 或 https 协议。" + # ) + + post_data = { + "build": 7300400, + "buvid": await acquire_buvid(credential=credential), + "oid": oid, + "share_title": share_title, + "share_content": share_content, + "share_origin": share_origin, + "mobi_app": mobi_app, + "panel_type": panel_type, + "platform": platform, + "share_id": share_id, + "share_mode": share_mode, + } + + api_url = "https://api.biliapi.net/x/share/click" + + if settings.http_client == settings.HTTPClient.HTTPX: + resp_content = ( + await get_session().post(url=api_url, headers=HEADERS, data=post_data) + ).json() + else: + resp = await get_aiohttp_session().post( + url=api_url, data=post_data, headers=HEADERS + ) + resp_content = await resp.json() + + # the 'content' sometimes will not be in the returned content due to build version, real_url, or buvid (rarely) + if "content" not in resp_content["data"]: + raise ApiException(msg="生成短链接失败。") + + return resp_content["data"]["content"] + + +async def get_real_url(short_url: str) -> str: """ 获取短链接跳转目标,以进行操作。 Args: short_url(str): 短链接。 - credential(Credential \| None): 凭据类。 - Returns: 目标链接(如果不是有效的链接会报错) 返回值为原 url 类型 """ - credential = credential if credential else Credential() try: if settings.http_client == settings.HTTPClient.HTTPX: diff --git a/docs/b23tv.md b/docs/b23tv.md index f0109adc..c4e50d54 100644 --- a/docs/b23tv.md +++ b/docs/b23tv.md @@ -2,7 +2,14 @@ bilibili_api 从 10.0.0 开始支持短链接了(说白了就是支持查看短链目标了) +获取默认链接(长链接)的短链接 +``` python +from bilibili_api import get_short_url, sync +print(sync(get_short_url(real_url="https://www.bilibili.com/video/BV18X4y1N7Yh/"))) # optionally pass in Credential +``` + +获取短链接的对应默认链接(长链接) ``` python from bilibili_api import get_real_url, sync -print(sync(get_real_url("https://b23.tv/mx00St"))) # https://www.bilibili.com/video/BV1YQ4y127Rd?p=1&share_medium=android&share_plat=android&share_session_id=d6c56bd5-db84-4cc8-9bb7-8f91cd8edfe0&share_source=COPY&share_tag=s_i×tamp=1629155789&unique_k=mx00St +print(sync(get_real_url(short_url="https://b23.tv/mx00St"))) # https://www.bilibili.com/video/BV1YQ4y127Rd?p=1&share_medium=android&share_plat=android&share_session_id=d6c56bd5-db84-4cc8-9bb7-8f91cd8edfe0&share_source=COPY&share_tag=s_i×tamp=1629155789&unique_k=mx00St ``` diff --git a/docs/modules/bilibili_api.md b/docs/modules/bilibili_api.md index d22ff904..6d2eb32e 100644 --- a/docs/modules/bilibili_api.md +++ b/docs/modules/bilibili_api.md @@ -324,12 +324,24 @@ BV 号转 AV 号。 --- +## async def get_short_url() + +| name | type | description | +|------------|---------------------|-------------| +| real_url | str | 真实链接 | +| credential | Optional[Credential] | 凭据类. | + +获取bilibili真实链接对应的短链接。 + +**注意:** 这个函数对于同一个真实链接的每一次调用都会返回不同的短链接。并且,请注意短链接也会包含你的分享信息(因为会redirect)。 + +**Returns:** b23.tv的短链接 + ## async def get_real_url() | name | type | description | | - | - | - | | short_url | str | 短链接 | -| credential | Optional[Credential] | 凭据类. | 获取短链接对应的真实链接。