diff --git a/bilibili_api/__init__.py b/bilibili_api/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..1bb7b37c95888ac22fcb03ff42abb321e357887e --- /dev/null +++ b/bilibili_api/__init__.py @@ -0,0 +1,166 @@ +""" +bilibili_api + +哔哩哔哩的各种 API 调用便捷整合(视频、动态、直播等),另外附加一些常用的功能。 +""" + +import asyncio +import platform + +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.parse_link import ResourceType, parse_link +from .utils.aid_bvid_transformer import aid2bvid, bvid2aid +from .utils.danmaku import DmMode, Danmaku, DmFontSize, SpecialDanmaku +from .utils.network import ( + HEADERS, + get_session, + set_session, + get_aiohttp_session, + set_aiohttp_session, + get_httpx_sync_session, + set_httpx_sync_session +) +from .errors import ( + LoginError, + ApiException, + ArgsException, + LiveException, + NetworkException, + ResponseException, + VideoUploadException, + ResponseCodeException, + DanmakuClosedException, + CredentialNoBuvid3Exception, + CredentialNoBiliJctException, + DynamicExceedImagesException, + CredentialNoSessdataException, + CredentialNoDedeUserIDException, +) +from . import ( + app, + ass, + hot, + game, + live, + note, + rank, + show, + user, + vote, + audio, + emoji, + login, + manga, + music, + topic, + video, + cheese, + client, + search, + article, + bangumi, + comment, + dynamic, + session, + festival, + homepage, + settings, + watchroom, + live_area, + video_tag, + black_room, + login_func, + video_zone, + favorite_list, + channel_series, + video_uploader, + creative_center, + article_category, + interactive_video, + audio_uploader, +) + +BILIBILI_API_VERSION = "16.2.0" + +# 如果系统为 Windows,则修改默认策略,以解决代理报错问题 +if "windows" in platform.system().lower(): + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) # type: ignore + +__all__ = [ + "ApiException", + "ArgsException", + "BILIBILI_API_VERSION", + "Credential", + "CredentialNoBiliJctException", + "CredentialNoBuvid3Exception", + "CredentialNoDedeUserIDException", + "CredentialNoSessdataException", + "Danmaku", + "DanmakuClosedException", + "DmFontSize", + "DmMode", + "DynamicExceedImagesException", + "HEADERS", + "LiveException", + "LoginError", + "NetworkException", + "Picture", + "ResourceType", + "ResponseCodeException", + "ResponseException", + "SpecialDanmaku", + "VideoUploadException", + "aid2bvid", + "app", + "article", + "article_category", + "ass", + "audio", + "audio_uploader", + "bangumi", + "black_room", + "bvid2aid", + "channel_series", + "cheese", + "client", + "comment", + "creative_center", + "dynamic", + "emoji", + "favorite_list", + "festival", + "game", + "get_aiohttp_session", + "get_real_url", + "get_session", + "homepage", + "hot", + "interactive_video", + "live", + "live_area", + "login", + "login_func", + "manga", + "music", + "note", + "parse_link", + "rank", + "search", + "session", + "set_aiohttp_session", + "set_session", + "settings", + "show", + "sync", + "topic", + "user", + "video", + "video_tag", + "video_uploader", + "video_zone", + "vote", + "watchroom", +] diff --git a/bilibili_api/__pycache__/__init__.cpython-38.pyc b/bilibili_api/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06806ebd4f881e695557094eae77a3c542fc4ffd Binary files /dev/null and b/bilibili_api/__pycache__/__init__.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/app.cpython-38.pyc b/bilibili_api/__pycache__/app.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a69bbc13fa2b4abc2ffdade90313d528fb4a568 Binary files /dev/null and b/bilibili_api/__pycache__/app.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/article.cpython-38.pyc b/bilibili_api/__pycache__/article.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..652c4aca7330e7751485b5143bc50103c351634f Binary files /dev/null and b/bilibili_api/__pycache__/article.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/article_category.cpython-38.pyc b/bilibili_api/__pycache__/article_category.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c2c01a01b26bc96f009e5fc7b2f7ce2b660e1734 Binary files /dev/null and b/bilibili_api/__pycache__/article_category.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/ass.cpython-38.pyc b/bilibili_api/__pycache__/ass.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..073077519a13b2d4bd9751dd37b1d6d837fedaad Binary files /dev/null and b/bilibili_api/__pycache__/ass.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/audio.cpython-38.pyc b/bilibili_api/__pycache__/audio.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d3cca8f5005362c892e4f8c73d25c35b4a18fe92 Binary files /dev/null and b/bilibili_api/__pycache__/audio.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/audio_uploader.cpython-38.pyc b/bilibili_api/__pycache__/audio_uploader.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3ca04159fcd318bd2482f87b1fb0d28e9ddddc07 Binary files /dev/null and b/bilibili_api/__pycache__/audio_uploader.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/bangumi.cpython-38.pyc b/bilibili_api/__pycache__/bangumi.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..48919217e8dc25a8e6fe598838e62e266112d8fa Binary files /dev/null and b/bilibili_api/__pycache__/bangumi.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/black_room.cpython-38.pyc b/bilibili_api/__pycache__/black_room.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53c6135fa2fd8c52615caf06da49d9b71f7f5568 Binary files /dev/null and b/bilibili_api/__pycache__/black_room.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/channel_series.cpython-38.pyc b/bilibili_api/__pycache__/channel_series.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b74198e3745e0901e5d4fdd3df7c46bb221d821b Binary files /dev/null and b/bilibili_api/__pycache__/channel_series.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/cheese.cpython-38.pyc b/bilibili_api/__pycache__/cheese.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..abb33baeb04e3c15e11a3bcd6ecc01624ab7ab93 Binary files /dev/null and b/bilibili_api/__pycache__/cheese.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/client.cpython-38.pyc b/bilibili_api/__pycache__/client.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f62b63c1389282e45f70626d442ac12bd99df3c4 Binary files /dev/null and b/bilibili_api/__pycache__/client.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/comment.cpython-38.pyc b/bilibili_api/__pycache__/comment.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..775eedfa6d3a2770ed58bbba9deb6af90ef468dc Binary files /dev/null and b/bilibili_api/__pycache__/comment.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/creative_center.cpython-38.pyc b/bilibili_api/__pycache__/creative_center.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2120e121efaef52384d2d8f73f60c5c70f0ff36d Binary files /dev/null and b/bilibili_api/__pycache__/creative_center.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/dynamic.cpython-38.pyc b/bilibili_api/__pycache__/dynamic.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4cffc3bcea23b75d90a6ecfa1897c7b9cdc1c35a Binary files /dev/null and b/bilibili_api/__pycache__/dynamic.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/emoji.cpython-38.pyc b/bilibili_api/__pycache__/emoji.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..841da0d57a37be198550e4fa2c774470bc31c6c8 Binary files /dev/null and b/bilibili_api/__pycache__/emoji.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/favorite_list.cpython-38.pyc b/bilibili_api/__pycache__/favorite_list.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..952f47671bab28974b5b87b69bbac2c7bc403387 Binary files /dev/null and b/bilibili_api/__pycache__/favorite_list.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/festival.cpython-38.pyc b/bilibili_api/__pycache__/festival.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..13d6990cf7208c87a1b7e23094b0608533758fc9 Binary files /dev/null and b/bilibili_api/__pycache__/festival.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/game.cpython-38.pyc b/bilibili_api/__pycache__/game.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5efad5e48b48cdbc7d46d7ba40f4a69eda9179fd Binary files /dev/null and b/bilibili_api/__pycache__/game.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/homepage.cpython-38.pyc b/bilibili_api/__pycache__/homepage.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33d5e5034f29ab6a9a367b4ba94a1a837765e988 Binary files /dev/null and b/bilibili_api/__pycache__/homepage.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/hot.cpython-38.pyc b/bilibili_api/__pycache__/hot.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ba9844d57e909ef63a1b7699a617943c6b38c92d Binary files /dev/null and b/bilibili_api/__pycache__/hot.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/interactive_video.cpython-38.pyc b/bilibili_api/__pycache__/interactive_video.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2c7e82d2e880a74d268a58e2a8afb351033a3d4d Binary files /dev/null and b/bilibili_api/__pycache__/interactive_video.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/live.cpython-38.pyc b/bilibili_api/__pycache__/live.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c965f3f6dd24a39421b5f730cddfdd0677c703a2 Binary files /dev/null and b/bilibili_api/__pycache__/live.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/live_area.cpython-38.pyc b/bilibili_api/__pycache__/live_area.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd10b9bb71a3764dca9300a418ac55d6ed3a7bf9 Binary files /dev/null and b/bilibili_api/__pycache__/live_area.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/login.cpython-38.pyc b/bilibili_api/__pycache__/login.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..989a8bc92ed80931a96d904fa60a81611d7e552a Binary files /dev/null and b/bilibili_api/__pycache__/login.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/login_func.cpython-38.pyc b/bilibili_api/__pycache__/login_func.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ca72d5b92bf622531cbc6fe7d275d92e5bebb071 Binary files /dev/null and b/bilibili_api/__pycache__/login_func.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/manga.cpython-38.pyc b/bilibili_api/__pycache__/manga.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41cdcb22ce95cff66ba437e464c59bceadec55fa Binary files /dev/null and b/bilibili_api/__pycache__/manga.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/music.cpython-38.pyc b/bilibili_api/__pycache__/music.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a0cf256bffac92585f27bf7e06410f5497f5164 Binary files /dev/null and b/bilibili_api/__pycache__/music.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/note.cpython-38.pyc b/bilibili_api/__pycache__/note.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33996046933ff565f3958f0e7941d0f1fbb225e1 Binary files /dev/null and b/bilibili_api/__pycache__/note.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/opus.cpython-38.pyc b/bilibili_api/__pycache__/opus.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8c2fb152d1fa5891aeb3ebc7b6ccff54775c9416 Binary files /dev/null and b/bilibili_api/__pycache__/opus.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/rank.cpython-38.pyc b/bilibili_api/__pycache__/rank.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3bd0e51ba9453b60a96daaed00a4589a49198073 Binary files /dev/null and b/bilibili_api/__pycache__/rank.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/search.cpython-38.pyc b/bilibili_api/__pycache__/search.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d22507aab9214ed9ce99a473df44194b343256f2 Binary files /dev/null and b/bilibili_api/__pycache__/search.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/session.cpython-38.pyc b/bilibili_api/__pycache__/session.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6540154033d8c442815b6b6998efbcf7a2858c7 Binary files /dev/null and b/bilibili_api/__pycache__/session.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/settings.cpython-38.pyc b/bilibili_api/__pycache__/settings.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4d116cb0db809e9c99bfea0082c7fd21bcc951e5 Binary files /dev/null and b/bilibili_api/__pycache__/settings.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/show.cpython-38.pyc b/bilibili_api/__pycache__/show.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f7022e023913962676bd70b5bce7e9d0eb87c1bc Binary files /dev/null and b/bilibili_api/__pycache__/show.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/topic.cpython-38.pyc b/bilibili_api/__pycache__/topic.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b43811493747bb3fb0968e60b46e94142713fad Binary files /dev/null and b/bilibili_api/__pycache__/topic.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/user.cpython-38.pyc b/bilibili_api/__pycache__/user.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec1ca47999764cdf555a49028cbaa2f6a9fe6ca4 Binary files /dev/null and b/bilibili_api/__pycache__/user.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/video.cpython-38.pyc b/bilibili_api/__pycache__/video.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..320a9b5441921e63da4f64460475b8d41f39223e Binary files /dev/null and b/bilibili_api/__pycache__/video.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/video_tag.cpython-38.pyc b/bilibili_api/__pycache__/video_tag.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c3b77e9bbb5fe26ca6b9b98533a8812a81cc2300 Binary files /dev/null and b/bilibili_api/__pycache__/video_tag.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/video_uploader.cpython-38.pyc b/bilibili_api/__pycache__/video_uploader.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0352f7330366d363f1679ef50173fd64ace32adb Binary files /dev/null and b/bilibili_api/__pycache__/video_uploader.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/video_zone.cpython-38.pyc b/bilibili_api/__pycache__/video_zone.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d344759665c6ffe4c41c4e9f56d938957da203b2 Binary files /dev/null and b/bilibili_api/__pycache__/video_zone.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/vote.cpython-38.pyc b/bilibili_api/__pycache__/vote.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..350af01a8e36da43bd0ec6e2fc78c543e80cfe8c Binary files /dev/null and b/bilibili_api/__pycache__/vote.cpython-38.pyc differ diff --git a/bilibili_api/__pycache__/watchroom.cpython-38.pyc b/bilibili_api/__pycache__/watchroom.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ad5a28b54ec99860ba9664607c935c175bb9887 Binary files /dev/null and b/bilibili_api/__pycache__/watchroom.cpython-38.pyc differ diff --git a/bilibili_api/_pyinstaller/__pycache__/entry_points.cpython-38.pyc b/bilibili_api/_pyinstaller/__pycache__/entry_points.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f9925318caecd0d688b054106240a894d21ff1c Binary files /dev/null and b/bilibili_api/_pyinstaller/__pycache__/entry_points.cpython-38.pyc differ diff --git a/bilibili_api/_pyinstaller/__pycache__/hook-bilibili_api.cpython-38.pyc b/bilibili_api/_pyinstaller/__pycache__/hook-bilibili_api.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..36e6f490f5f571ed820e06c6f5c436bf9ef0821e Binary files /dev/null and b/bilibili_api/_pyinstaller/__pycache__/hook-bilibili_api.cpython-38.pyc differ diff --git a/bilibili_api/_pyinstaller/entry_points.py b/bilibili_api/_pyinstaller/entry_points.py new file mode 100644 index 0000000000000000000000000000000000000000..0cbfa3bc4ff2aad330ef3e65b74da1817c925fdd --- /dev/null +++ b/bilibili_api/_pyinstaller/entry_points.py @@ -0,0 +1,6 @@ +import os +from typing import List + + +def get_hook_dirs() -> List[str]: + return [os.path.abspath(os.path.dirname(__file__))] diff --git a/bilibili_api/_pyinstaller/hook-bilibili_api.py b/bilibili_api/_pyinstaller/hook-bilibili_api.py new file mode 100644 index 0000000000000000000000000000000000000000..5e06e4a1d1ea61dd3a118f497723620cc5660fc3 --- /dev/null +++ b/bilibili_api/_pyinstaller/hook-bilibili_api.py @@ -0,0 +1,5 @@ +from typing import List, Tuple + +from PyInstaller.utils.hooks import collect_data_files + +datas: List[Tuple[str, str]] = collect_data_files("bilibili_api") diff --git a/bilibili_api/app.py b/bilibili_api/app.py new file mode 100644 index 0000000000000000000000000000000000000000..f6260253140351358724e2c4d3d94cc84d11072c --- /dev/null +++ b/bilibili_api/app.py @@ -0,0 +1,123 @@ +""" +bilibili_api.app + +手机 APP 相关 +""" + +import time +from hashlib import md5 +from typing import Union + +from .utils.utils import get_api +from .utils.credential import Credential +from .utils.network import Api + +API = get_api("app") + + +async def get_loading_images( + mobi_app: str = "android", + platform: str = "android", + height: int = 1920, + width: int = 1080, + build: int = 999999999, + birth: str = "", + credential: Union[Credential, None] = None, +): + """ + 获取开屏启动画面 + + Args: + build (int, optional) : 客户端内部版本号 + + mobi_app (str, optional) : android / iphone / ipad + + platform (str, optional) : android / ios / ios + + height (int, optional) : 屏幕高度 + + width (int, optional) : 屏幕宽度 + + birth (str, optional) : 生日日期(四位数,例 0101) + + credential (Credential | None, optional): 凭据. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + credential = credential if credential is not None else Credential() + + api = API["splash"]["list"] + params = { + "build": build, + "mobi_app": mobi_app, + "platform": platform, + "height": height, + "width": width, + "birth": birth, + } + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_loading_images_special( + mobi_app: str = "android", + platform: str = "android", + height: int = 1920, + width: int = 1080, + credential: Union[Credential, None] = None, +): + """ + 获取特殊开屏启动画面 + + Args: + mobi_app (str, optional) : android / iphone / ipad + + platform (str, optional) : android / ios / ios + + height (str, optional) : 屏幕高度 + + width (str, optional) : 屏幕宽度 + + credential (Credential | None, optional): 凭据. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + APPKEY = "1d8b6e7d45233436" + APPSEC = "560c52ccd288fed045859ed18bffd973" + + ts = int(time.time()) + + credential = credential if credential is not None else Credential() + + api = API["splash"]["brand"] + sign_params = ( + "appkey=" + + APPKEY + + "&mobi_app=" + + mobi_app + + "&platform=" + + platform + + "&screen_height=" + + str(height) + + "&screen_width=" + + str(width) + + "&ts=" + + str(ts) + + APPSEC + ) + + sign = md5() + sign.update(sign_params.encode(encoding="utf-8")) + sign = sign.hexdigest() + + params = { + "appkey": APPKEY, + "mobi_app": mobi_app, + "platform": platform, + "screen_height": height, + "screen_width": width, + "ts": ts, + "sign": sign, + } + return await Api(**api, credential=credential).update_params(**params).result diff --git a/bilibili_api/article.py b/bilibili_api/article.py new file mode 100644 index 0000000000000000000000000000000000000000..c1a0056ae459df5944ece377696415f360c97451 --- /dev/null +++ b/bilibili_api/article.py @@ -0,0 +1,1078 @@ +""" +bilibili_api.article + +专栏相关 +""" + +import re +import json +from copy import copy +from enum import Enum +from html import unescape +from datetime import datetime +from urllib.parse import unquote +from typing import List, Union, TypeVar, overload + +import yaml +import httpx +from yarl import URL +from bs4 import BeautifulSoup, element + +from .utils.initial_state import get_initial_state, get_initial_state_sync +from .utils.utils import get_api, raise_for_statement +from .utils.credential import Credential +from .utils.network import Api, get_session +from .utils import cache_pool +from .exceptions.NetworkException import ApiException, NetworkException +from .video import get_cid_info_sync +from . import note +from . import opus + +API = get_api("article") + +# 文章颜色表 +ARTICLE_COLOR_MAP = { + "default": "222222", + "blue-01": "56c1fe", + "lblue-01": "73fdea", + "green-01": "89fa4e", + "yellow-01": "fff359", + "pink-01": "ff968d", + "purple-01": "ff8cc6", + "blue-02": "02a2ff", + "lblue-02": "18e7cf", + "green-02": "60d837", + "yellow-02": "fbe231", + "pink-02": "ff654e", + "purple-02": "ef5fa8", + "blue-03": "0176ba", + "lblue-03": "068f86", + "green-03": "1db100", + "yellow-03": "f8ba00", + "pink-03": "ee230d", + "purple-03": "cb297a", + "blue-04": "004e80", + "lblue-04": "017c76", + "green-04": "017001", + "yellow-04": "ff9201", + "pink-04": "b41700", + "purple-04": "99195e", + "gray-01": "d6d5d5", + "gray-02": "929292", + "gray-03": "5f5f5f", +} + + +class ArticleType(Enum): + """ + 专栏类型 + + - ARTICLE : 普通专栏,不与 opus 图文兼容。 + - NOTE : 公开笔记 + - SPECIAL_ARTICLE: 特殊专栏,采用笔记格式,且与 opus 图文完全兼容。 + """ + + ARTICLE = 0 + NOTE = 2 + SPECIAL_ARTICLE = 3 + + +class ArticleRankingType(Enum): + """ + 专栏排行榜类型枚举。 + + + MONTH: 月榜 + + WEEK: 周榜 + + DAY_BEFORE_YESTERDAY: 前日榜 + + YESTERDAY: 昨日榜 + """ + + MONTH = 1 + WEEK = 2 + DAY_BEFORE_YESTERDAY = 4 + YESTERDAY = 3 + + +ArticleT = TypeVar("ArticleT", bound="Article") + + +async def get_article_rank( + rank_type: ArticleRankingType = ArticleRankingType.YESTERDAY, +): + """ + 获取专栏排行榜 + + Args: + rank_type (ArticleRankingType): 排行榜类别. Defaults to ArticleRankingType.YESTERDAY. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["rank"] + params = {"cid": rank_type.value} + return await Api(**api).update_params(**params).result + + +class ArticleList: + """ + 文集类 + + Attributes: + credential (Credential): 凭据类 + """ + + def __init__(self, rlid: int, credential: Union[Credential, None] = None): + """ + Args: + rlid (int) : 文集 id + + credential (Credential | None, optional): 凭据类. Defaults to None. + """ + self.__rlid = rlid + self.credential = credential + + def get_rlid(self) -> int: + return self.__rlid + + async def get_content(self) -> dict: + """ + 获取专栏文集文章列表 + + Returns: + dict: 调用 API 返回的结果 + """ + credential = self.credential if self.credential is not None else Credential() + + api = API["info"]["list"] + params = {"id": self.__rlid} + return await Api(**api, credential=credential).update_params(**params).result + + +class Article: + """ + 专栏类 + + Attributes: + credential (Credential): 凭据类 + """ + + def __init__(self, cvid: int, credential: Union[Credential, None] = None): + """ + Args: + cvid (int) : cv 号 + + credential (Credential | None, optional): 凭据. Defaults to None. + """ + self.__children: List[Node] = [] + self.credential: Credential = ( + credential if credential is not None else Credential() + ) + self.__meta = None + self.__cvid = cvid + self.__has_parsed: bool = False + + # 设置专栏类别 + if cache_pool.article_is_opus.get(self.__cvid): + self.__type = ArticleType.SPECIAL_ARTICLE + else: + api = API["info"]["view"] + params = {"id": self.__cvid} + resp = Api(**api).update_params(**params).request_sync(raw=True) + + if resp["code"] != 0: + self.__type = ArticleType.ARTICLE + elif resp["data"]["type"] == 0: + self.__type = ArticleType.ARTICLE + elif resp["data"]["type"] == 2: + self.__type = ArticleType.NOTE + else: + self.__type = ArticleType.SPECIAL_ARTICLE + + if cache_pool.article_dyn_id.get(self.__cvid): + self.__dyn_id = cache_pool.article_dyn_id[self.__cvid] + else: + initial_state = get_initial_state_sync( + f"https://www.bilibili.com/read/cv{self.__cvid}" + ) + self.__dyn_id = int(initial_state[0]["readInfo"]["dyn_id_str"]) + + def get_cvid(self) -> int: + return self.__cvid + + def get_type(self) -> ArticleType: + """ + 获取专栏类型(专栏/笔记) + + Returns: + ArticleType: 专栏类型 + """ + return self.__type + + def is_note(self) -> bool: + """ + 检查专栏是否笔记 + + Returns: + bool: 是否笔记 + """ + return self.__type == ArticleType.NOTE + + def turn_to_note(self) -> "note.Note": + """ + 对于完全与 opus 兼容的部分的特殊专栏,将 Article 对象转换为 Dynamic 对象。 + + Returns: + Note: 笔记类 + """ + raise_for_statement( + self.__type == ArticleType.NOTE, "仅支持公开笔记 (ArticleType.NOTE)" + ) + return note.Note( + cvid=self.__cvid, note_type=note.NoteType.PUBLIC, credential=self.credential + ) + + def turn_to_opus(self) -> "opus.Opus": + """ + 对于 SPECIAL_ARTICLE,将其转为图文 + """ + raise_for_statement( + self.__type == ArticleType.SPECIAL_ARTICLE, "仅支持图文专栏" + ) + cache_pool.opus_type[self.__dyn_id] = 1 + cache_pool.opus_info[self.__dyn_id] = {"basic": {"rid_str": str(self.__cvid)}} + return opus.Opus(self.__dyn_id, credential=self.credential) + + def markdown(self) -> str: + """ + 转换为 Markdown + + 请先调用 fetch_content() + + Returns: + str: Markdown 内容 + """ + if not self.__has_parsed: + raise ApiException("请先调用 fetch_content()") + + content = "" + + for node in self.__children: + try: + markdown_text = node.markdown() + except: + continue + else: + content += markdown_text + + meta_yaml = yaml.safe_dump(self.__meta, allow_unicode=True) + content = f"---\n{meta_yaml}\n---\n\n{content}" + return content + + def json(self) -> dict: + """ + 转换为 JSON 数据 + + 请先调用 fetch_content() + + Returns: + dict: JSON 数据 + """ + if not self.__has_parsed: + raise ApiException("请先调用 fetch_content()") + + return { + "type": "Article", + "meta": self.__meta, + "children": list(map(lambda x: x.json(), self.__children)), + } + + async def fetch_content(self) -> None: + """ + 获取并解析专栏内容 + + 该返回不会返回任何值,调用该方法后请再调用 `self.markdown()` 或 `self.json()` 来获取你需要的值。 + """ + + resp = await self.get_all() + + document = BeautifulSoup(f"
{resp['readInfo']['content']}
", "lxml") + + async def parse(el: BeautifulSoup): + node_list = [] + + for e in el.contents: # type: ignore + if type(e) == element.NavigableString: + # 文本节点 + node = TextNode(e) # type: ignore + node_list.append(node) + continue + + e: BeautifulSoup = e + if e.name == "p": + # 段落 + node = ParagraphNode() + node_list.append(node) + + if "style" in e.attrs: + if "text-align: center" in e.attrs["style"]: + node.align = "center" + + elif "text-align: right" in e.attrs["style"]: + node.align = "right" + + else: + node.align = "left" + + node.children = await parse(e) + + elif e.name == "h1": + # 标题 + node = HeadingNode() + node_list.append(node) + + node.children = await parse(e) + + elif e.name == "strong": + # 粗体 + node = BoldNode() + node_list.append(node) + + node.children = await parse(e) + + elif e.name == "span": + # 各种样式 + if "style" in e.attrs: + style = e.attrs["style"] + + if "text-decoration: line-through" in style: + # 删除线 + node = DelNode() + node_list.append(node) + + node.children = await parse(e) + + elif "class" in e.attrs: + className = e.attrs["class"][0] + + if "font-size" in className: + # 字体大小 + node = FontSizeNode() + node_list.append(node) + + node.size = int(re.search("font-size-(\d\d)", className)[1]) # type: ignore + node.children = await parse(e) + + elif "color" in className: + # 字体颜色 + node = ColorNode() + node_list.append(node) + + color_text = re.search("color-(.*);?", className)[1] # type: ignore + node.color = ARTICLE_COLOR_MAP[color_text] + + node.children = await parse(e) + else: + if e.text != "": + # print(e.text.replace("\n", "")) + # print() + node = TextNode(e.text) + # print("Add a text node: ", e.text) + node_list.append(node) + node.children = parse(e) # type: ignore + + elif e.name == "blockquote": + # 引用块 + # print(e.text) + node = BlockquoteNode() + node_list.append(node) + node.children = await parse(e) + + elif e.name == "figure": + if "class" in e.attrs: + className = e.attrs["class"] + + if "img-box" in className: + img_el: BeautifulSoup = e.find("img") # type: ignore + if img_el == None: + pass + elif "class" in img_el.attrs: + className = img_el.attrs["class"] + + if "cut-off" in className: + # 分割线 + node = SeparatorNode() + node_list.append(node) + + if "aid" in img_el.attrs: + # 各种卡片 + aid = img_el.attrs["aid"] + + if "video-card" in className: + # 视频卡片,考虑有两列视频 + for a in aid.split(","): + node = VideoCardNode() + node_list.append(node) + + node.aid = int(a) + + elif "article-card" in className: + # 文章卡片 + node = ArticleCardNode() + node_list.append(node) + + node.cvid = int(aid) + + elif "fanju-card" in className: + # 番剧卡片 + node = BangumiCardNode() + node_list.append(node) + + node.epid = int(aid[2:]) + + elif "music-card" in className: + # 音乐卡片 + node = MusicCardNode() + node_list.append(node) + + node.auid = int(aid[2:]) + + elif "shop-card" in className: + # 会员购卡片 + node = ShopCardNode() + node_list.append(node) + + node.pwid = int(aid[2:]) + + elif "caricature-card" in className: + # 漫画卡片,考虑有两列 + + for i in aid.split(","): + node = ComicCardNode() + node_list.append(node) + + node.mcid = int(i) + + elif "live-card" in className: + # 直播卡片 + node = LiveCardNode() + node_list.append(node) + + node.room_id = int(aid) + else: + # 图片节点 + node = ImageNode() + node_list.append(node) + + node.url = "https:" + e.find("img").attrs["data-src"] # type: ignore + + figcaption_el: BeautifulSoup = e.find("figcaption") # type: ignore + + if figcaption_el: + if figcaption_el.contents: + node.alt = figcaption_el.contents[0] # type: ignore + + elif "code-box" in className: + # 代码块 + node = CodeNode() + node_list.append(node) + + pre_el: BeautifulSoup = e.find("pre") # type: ignore + node.lang = pre_el.attrs["data-lang"].split("@")[0].lower() + node.code = unquote(pre_el.attrs["codecontent"]) + + elif e.name == "ol": + # 有序列表 + node = OlNode() + node_list.append(node) + + node.children = await parse(e) + + elif e.name == "li": + # 列表元素 + node = LiNode() + node_list.append(node) + + node.children = await parse(e) + + elif e.name == "ul": + # 无序列表 + node = UlNode() + node_list.append(node) + + node.children = await parse(e) + + elif e.name == "a": + # 超链接 + if len(e.contents) == 0: + from .utils.parse_link import ResourceType, parse_link + + parse_link_res = await parse_link(e.attrs["href"]) + if parse_link_res[1] == ResourceType.VIDEO: + node = VideoCardNode() + node.aid = parse_link_res[0].get_aid() + node_list.append(node) + elif parse_link_res[1] == ResourceType.AUDIO: + node = MusicCardNode() + node.auid = parse_link_res[0].get_auid() + node_list.append(node) + elif parse_link_res[1] == ResourceType.LIVE: + node = LiveCardNode() + node.room_id = parse_link_res[0].room_display_id + node_list.append(node) + elif parse_link_res[1] == ResourceType.ARTICLE: + node = ArticleCardNode() + node.cvid = parse_link_res[0].get_cvid() + node_list.append(node) + else: + # XXX: 暂不支持其他的站内链接 + pass + else: + node = AnchorNode() + node_list.append(node) + + node.url = e.attrs["href"] + node.text = e.contents[0] # type: ignore + + elif e.name == "img": + className = e.attrs.get("class") + + if not className: + # 图片 + node = ImageNode() + node.url = e.attrs.get("data-src") # type: ignore + node_list.append(node) + + elif "latex" in className: + # 公式 + node = LatexNode() + node_list.append(node) + + node.code = unquote(e["alt"]) # type: ignore + + return node_list + + def parse_note(data: List[dict]): + for field in data: + if not isinstance(field["insert"], str): + if "tag" in field["insert"].keys(): + node = VideoCardNode() + node.aid = get_cid_info_sync(field["insert"]["tag"]["cid"])[ + "cid" + ] + self.__children.append(node) + elif "imageUpload" in field["insert"].keys(): + node = ImageNode() + node.url = field["insert"]["imageUpload"]["url"] + self.__children.append(node) + elif "cut-off" in field["insert"].keys(): + node = ImageNode() + node.url = field["insert"]["cut-off"]["url"] + self.__children.append(node) + elif "native-image" in field["insert"].keys(): + node = ImageNode() + node.url = field["insert"]["native-image"]["url"] + self.__children.append(node) + else: + raise Exception() + else: + node = TextNode(field["insert"]) + if "attributes" in field.keys(): + if field["attributes"].get("bold") == True: + bold = BoldNode() + bold.children = [node] + node = bold + if field["attributes"].get("strike") == True: + delete = DelNode() + delete.children = [node] + node = delete + if field["attributes"].get("underline") == True: + underline = UnderlineNode() + underline.children = [node] + node = underline + if field["attributes"].get("background") == True: + # FIXME: 暂不支持背景颜色 + pass + if field["attributes"].get("color") != None: + color = ColorNode() + color.color = field["attributes"]["color"].replace("#", "") + color.children = [node] + node = color + if field["attributes"].get("size") != None: + size = FontSizeNode() + size.size = field["attributes"]["size"] + size.children = [node] + node = size + else: + pass + self.__children.append(node) + + # 文章元数据 + self.__meta = copy(resp["readInfo"]) + del self.__meta["content"] + + # 解析正文 + if self.__type != ArticleType.SPECIAL_ARTICLE: + self.__children = await parse(document.find("div")) # type: ignore + else: + s = resp["readInfo"]["content"] + s = unescape(s) + parse_note(json.loads(s)["ops"]) + + self.__has_parsed = True + + async def get_info(self) -> dict: + """ + 获取专栏信息 + + Returns: + dict: 调用 API 返回的结果 + """ + + api = API["info"]["view"] + params = {"id": self.__cvid} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_all(self) -> dict: + """ + 一次性获取专栏尽可能详细数据,包括原始内容、标签、发布时间、标题、相关专栏推荐等 + + Returns: + dict: 调用 API 返回的结果 + """ + return ( + await get_initial_state(f"https://www.bilibili.com/read/cv{self.__cvid}") + )[0] + + async def set_like(self, status: bool = True) -> dict: + """ + 设置专栏点赞状态 + + Args: + status (bool, optional): 点赞状态. Defaults to True + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + + api = API["operate"]["like"] + data = {"id": self.__cvid, "type": 1 if status else 2} + return await Api(**api, credential=self.credential).update_data(**data).result + + async def set_favorite(self, status: bool = True) -> dict: + """ + 设置专栏收藏状态 + + Args: + status (bool, optional): 收藏状态. Defaults to True + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + + api = ( + API["operate"]["add_favorite"] if status else API["operate"]["del_favorite"] + ) + + data = {"id": self.__cvid} + return await Api(**api, credential=self.credential).update_data(**data).result + + async def add_coins(self) -> dict: + """ + 给专栏投币,目前只能投一个 + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + + upid = (await self.get_info())["mid"] + api = API["operate"]["coin"] + data = {"aid": self.__cvid, "multiply": 1, "upid": upid, "avtype": 2} + return await Api(**api, credential=self.credential).update_data(**data).result + + # TODO: 专栏上传/编辑/删除 + + +class Node: + def __init__(self): + pass + + @overload + def markdown(self) -> str: # type: ignore + pass + + @overload + def json(self) -> dict: # type: ignore + pass + + +class ParagraphNode(Node): + def __init__(self): + self.children = [] + self.align = "left" + + def markdown(self): + content = "".join([node.markdown() for node in self.children]) + return content + "\n\n" + + def json(self): + return { + "type": "ParagraphNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class HeadingNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + text = "".join([node.markdown() for node in self.children]) + if len(text) == 0: + return "" + return f"## {text}\n\n" + + def json(self): + return { + "type": "HeadingNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class BlockquoteNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + t = "".join([node.markdown() for node in self.children]) + # 填补空白行的 > 并加上标识符 + t = "\n".join(["> " + line for line in t.split("\n")]) + "\n\n" + + return t + + def json(self): + return { + "type": "BlockquoteNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class ItalicNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + text = "".join([node.markdown() for node in self.children]) + if len(text) == 0: + return "" + return f" *{text}* " + + def json(self): + return { + "type": "ItalicNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class BoldNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + t = "".join([node.markdown() for node in self.children]) + if len(t) == 0: + return "" + return f" **{t.lstrip().rstrip()}** " + + def json(self): + return { + "type": "BoldNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class DelNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + text = "".join([node.markdown() for node in self.children]) + if len(text) == 0: + return "" + return f" ~~{text}~~ " + + def json(self): + return { + "type": "DelNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class UnderlineNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + text = "".join([node.markdown() for node in self.children]) + if len(text) == 0: + return "" + return " $\\underline{" + text + "}$ " + + def json(self): + return { + "type": "UnderlineNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class UlNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + return "\n".join(["- " + node.markdown() for node in self.children]) + + def json(self): + return { + "type": "UlNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class OlNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + t = [] + for i, node in enumerate(self.children): + t.append(f"{i + 1}. {node.markdown()}") + return "\n".join(t) + + def json(self): + return { + "type": "OlNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class LiNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + return "".join([node.markdown() for node in self.children]) + + def json(self): + return { + "type": "LiNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class ColorNode(Node): + def __init__(self): + self.color = "000000" + self.children = [] + + def markdown(self): + return "".join([node.markdown() for node in self.children]) + + def json(self): + return { + "type": "ColorNode", + "color": self.color, + "children": list(map(lambda x: x.json(), self.children)), + } + + +class FontSizeNode(Node): + def __init__(self): + self.size = 16 + self.children = [] + + def markdown(self): + return "".join([node.markdown() for node in self.children]) + + def json(self): + return { + "type": "FontSizeNode", + "size": self.size, + "children": list(map(lambda x: x.json(), self.children)), + } + + +# 特殊节点,即无子节点 + + +class TextNode(Node): + def __init__(self, text: str): + self.text = text + + def markdown(self): + txt = self.text + txt = txt.replace("\t", " ") + txt = txt.replace(" ", " ") + txt = txt.replace(chr(160), " ") + special_chars = ["\\", "*", "$", "<", ">", "|"] + for c in special_chars: + txt = txt.replace(c, "\\" + c) + return txt + + def json(self): + return {"type": "TextNode", "text": self.text} + + +class ImageNode(Node): + def __init__(self): + self.url = "" + self.alt = "" + + def markdown(self): + if URL(self.url).scheme == "": + self.url = "https:" + self.url + alt = self.alt.replace("[", "\\[") + return f"![{alt}]({self.url})\n\n" + + def json(self): + if URL(self.url).scheme == "": + self.url = "https:" + self.url + return {"type": "ImageNode", "url": self.url, "alt": self.alt} + + +class LatexNode(Node): + def __init__(self): + self.code = "" + + def markdown(self): + if "\n" in self.code: + # 块级公式 + return f"$$\n{self.code}\n$$" + else: + # 行内公式 + return f"${self.code}$" + + def json(self): + return {"type": "LatexNode", "code": self.code} + + +class CodeNode(Node): + def __init__(self): + self.code = "" + self.lang = "" + + def markdown(self): + return f"```{self.lang if self.lang else ''}\n{self.code}\n```\n\n" + + def json(self): + return {"type": "CodeNode", "code": self.code, "lang": self.lang} + + +# 卡片 + + +class VideoCardNode(Node): + def __init__(self): + self.aid = 0 + + def markdown(self): + return f"[视频 av{self.aid}](https://www.bilibili.com/av{self.aid})\n\n" + + def json(self): + return {"type": "VideoCardNode", "aid": self.aid} + + +class ArticleCardNode(Node): + def __init__(self): + self.cvid = 0 + + def markdown(self): + return f"[文章 cv{self.cvid}](https://www.bilibili.com/read/cv{self.cvid})\n\n" + + def json(self): + return {"type": "ArticleCardNode", "cvid": self.cvid} + + +class BangumiCardNode(Node): + def __init__(self): + self.epid = 0 + + def markdown(self): + return f"[番剧 ep{self.epid}](https://www.bilibili.com/bangumi/play/ep{self.epid})\n\n" + + def json(self): + return {"type": "BangumiCardNode", "epid": self.epid} + + +class MusicCardNode(Node): + def __init__(self): + self.auid = 0 + + def markdown(self): + return f"[音乐 au{self.auid}](https://www.bilibili.com/audio/au{self.auid})\n\n" + + def json(self): + return {"type": "MusicCardNode", "auid": self.auid} + + +class ShopCardNode(Node): + def __init__(self): + self.pwid = 0 + + def markdown(self): + return f"[会员购 {self.pwid}](https://show.bilibili.com/platform/detail.html?id={self.pwid})\n\n" + + def json(self): + return {"type": "ShopCardNode", "pwid": self.pwid} + + +class ComicCardNode(Node): + def __init__(self): + self.mcid = 0 + + def markdown(self): + return ( + f"[漫画 mc{self.mcid}](https://manga.bilibili.com/m/detail/mc{self.mcid})\n\n" + ) + + def json(self): + return {"type": "ComicCardNode", "mcid": self.mcid} + + +class LiveCardNode(Node): + def __init__(self): + self.room_id = 0 + + def markdown(self): + return f"[直播 {self.room_id}](https://live.bilibili.com/{self.room_id})\n\n" + + def json(self): + return {"type": "LiveCardNode", "room_id": self.room_id} + + +class AnchorNode(Node): + def __init__(self): + self.url = "" + self.text = "" + + def markdown(self): + text = self.text.replace("[", "\\[") + return f"[{text}]({self.url})" + + def json(self): + return {"type": "AnchorNode", "url": self.url, "text": self.text} + + +class SeparatorNode(Node): + def __init__(self): + pass + + def markdown(self): + return "\n------\n" + + def json(self): + return {"type": "SeparatorNode"} diff --git a/bilibili_api/article_category.py b/bilibili_api/article_category.py new file mode 100644 index 0000000000000000000000000000000000000000..6fee4b4c744730c9bf2c2f10358e5eb51b021b64 --- /dev/null +++ b/bilibili_api/article_category.py @@ -0,0 +1,151 @@ +""" +bilibili_api.article_category + +专栏分类相关 +""" +import os +import copy +import json +from enum import Enum +from typing import List, Tuple, Optional + +from .utils.utils import get_api +from .utils.network import Api + +API = get_api("article-category") + + +class ArticleOrder(Enum): + """ + 专栏排序方式. + + + DEFAULT: 默认 + + TIME: 投稿时间排序 + + LIKE: 点赞数最多 + + COMMENTS: 评论数最多 + + FAVORITES: 收藏数最多 + """ + + DEFAULT = 0 + TIME = 1 + LIKE = 2 + COMMENTS = 3 + FAVORITES = 4 + + +def get_category_info_by_id(id: int) -> Tuple[Optional[dict], Optional[dict]]: + """ + 获取专栏分类信息 + + Args: + id (int): id + + Returns: + Tuple[dict | None, dict | None]: 第一个是主分区,第二个是字分区。没有找到则为 (None, None) + """ + with open( + os.path.join(os.path.dirname(__file__), "data/article_category.json"), + encoding="utf-8", + ) as f: + data = json.loads(f.read()) + + for main_category in data: + if main_category["id"] == id: + return main_category, None + for sub_category in main_category["children"]: + if sub_category["id"] == id: + return main_category, sub_category + else: + return None, None + + +def get_category_info_by_name(name: str) -> Tuple[Optional[dict], Optional[dict]]: + """ + 获取专栏分类信息 + + Args: + name (str): 分类名 + + Returns: + Tuple[dict | None, dict | None]: 第一个是主分区,第二个是字分区。没有找到则为 (None, None) + """ + with open( + os.path.join(os.path.dirname(__file__), "data/article_category.json"), + encoding="utf-8", + ) as f: + data = json.loads(f.read()) + + for main_category in data: + if main_category["name"] == name: + return main_category, None + for sub_category in main_category["children"]: + if sub_category["name"] == name: + return main_category, sub_category + else: + return None, None + + +def get_categories_list() -> List[dict]: + """ + 获取所有的分类的数据 + + Returns: + List[dict]: 所有分区的数据 + """ + with open( + os.path.join(os.path.dirname(__file__), "data/article_category.json"), + encoding="utf-8", + ) as f: + data = json.loads(f.read()) + categories_list = [] + for main_category in data: + main_category_copy = copy.copy(main_category) + categories_list.append(main_category_copy) + main_category_copy.pop("children") + for sub_category in main_category["children"]: + sub_category_copy = copy.copy(sub_category) + sub_category_copy["father"] = main_category_copy + categories_list.append(sub_category_copy) + return categories_list + + +def get_categories_list_sub() -> dict: + """ + 获取所有分区的数据 + + 含父子关系(即一层次只有主分区) + + Returns: + dict: 所有分区的数据 + """ + with open( + os.path.join(os.path.dirname(__file__), "data/article_category.json"), + encoding="utf-8", + ) as f: + return json.loads(f.read()) + + +async def get_category_recommend_articles( + category_id: int = 0, + order: ArticleOrder = ArticleOrder.DEFAULT, + page_num: int = 1, + page_size: int = 20, +) -> dict: + """ + 获取指定分区的推荐文章 + + Args: + category_id (int) : 专栏分类的 id, 0 为全部. Defaults to 0. + + order (ArticleOrder): 排序方式. Defaults to ArticleOrder.DEFAULT. + + page_num (int) : 页码. Defaults to 1. + + page_size (int) : 每一页数据大小. Defaults to 20. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["recommends"] + params = {"cid": category_id, "sort": order.value, "pn": page_num, "ps": page_size} + return await Api(**api).update_params(**params).result diff --git a/bilibili_api/ass.py b/bilibili_api/ass.py new file mode 100644 index 0000000000000000000000000000000000000000..0a06bc372a49b4591503f7c0fa0dbb9055a1775f --- /dev/null +++ b/bilibili_api/ass.py @@ -0,0 +1,309 @@ +""" +bilibili_api.ass + +有关 ASS 文件的操作 +""" +import os +from tempfile import gettempdir +from typing import Union, Optional + +from .video import Video +from .bangumi import Episode +from .cheese import CheeseVideo +from .utils.srt2ass import srt2ass +from .utils.json2srt import json2srt +from .utils.credential import Credential +from .utils.danmaku2ass import Danmaku2ASS +from .utils.network import get_session +from .exceptions.ArgsException import ArgsException + + +def export_ass_from_xml( + file_local, + output_local, + stage_size, + font_name, + font_size, + alpha, + fly_time, + static_time, +) -> None: + """ + 以一个 XML 文件创建 ASS + + 一定看清楚 Arguments! + + Args: + file_local (str) : 文件输入 + output_local (str) : 文件输出 + stage_size (tuple(int)): 视频大小 + font_name (str) : 字体 + font_size (float) : 字体大小 + alpha (float) : 透明度(0-1) + fly_time (float) : 滚动弹幕持续时间 + static_time (float) : 静态弹幕持续时间 + """ + Danmaku2ASS( + input_files=file_local, + input_format="Bilibili", + output_file=output_local, + stage_width=stage_size[0], + stage_height=stage_size[1], + reserve_blank=0, + font_face=font_name, + font_size=font_size, + text_opacity=alpha, + duration_marquee=fly_time, + duration_still=static_time, + ) + + +def export_ass_from_srt(file_local, output_local) -> None: + """ + 转换 srt 至 ass + + Args: + file_local (str): 文件位置 + + output_local (str): 输出位置 + """ + srt2ass(file_local, output_local, "movie") + + +def export_ass_from_json(file_local, output_local) -> None: + """ + 转换 json 至 ass + + Args: + file_local (str): 文件位置 + + output_local (str): 输出位置 + """ + json2srt(file_local, output_local.replace(".ass", ".srt")) + srt2ass(output_local.replace(".ass", ".srt"), output_local, "movie") + os.remove(output_local.replace(".ass", ".srt")) + + +async def make_ass_file_subtitle( + obj: Union[Video, Episode], + page_index: Optional[int] = 0, + cid: Optional[int] = None, + out: Optional[str] = "test.ass", + lan_name: Optional[str] = "中文(自动生成)", + lan_code: Optional[str] = "ai-zh", + credential: Credential = Credential(), +) -> None: + """ + 生成视频字幕文件 + + Args: + obj (Union[Video,Episode]): 对象 + + page_index (int, optional) : 分 P 索引 + + cid (int, optional) : cid + + out (str, optional) : 输出位置. Defaults to "test.ass". + + lan_name (str, optional) : 字幕名,如”中文(自动生成)“,是简介的 subtitle 项的'list'项中的弹幕的'lan_doc'属性。Defaults to "中文(自动生成)". + + lan_code (str, optional) : 字幕语言代码,如 ”中文(自动翻译)” 和 ”中文(自动生成)“ 为 "ai-zh" + + credential (Credential) : Credential 类. 必须在此处或传入的视频 obj 中传入凭据,两者均存在则优先此处 + """ + # 目测必须得有 Credential 才能获取字幕 + if credential.has_sessdata(): + obj.credential = credential + elif not obj.credential.has_sessdata(): + raise credential.raise_for_no_sessdata() + + if isinstance(obj, Episode): + info = await obj.get_player_info(cid=await obj.get_cid(), epid=obj.get_epid()) + else: + if cid == None: + if page_index == None: + raise ArgsException("page_index 和 cid 至少提供一个。") + cid = await obj.get_cid(page_index=page_index) + info = await obj.get_player_info(cid=cid) + json_files = info["subtitle"]["subtitles"] + for subtitle in json_files: + if subtitle["lan_doc"] == lan_name or subtitle["lan"] == lan_code: + url = subtitle["subtitle_url"] + if isinstance(obj, Episode) or "https:" not in url: + url = "https:" + url + req = await get_session().request("GET", url) + file_dir = gettempdir() + "/" + "subtitle.json" + with open(file_dir, "wb") as f: + f.write(req.content) + export_ass_from_json(file_dir, out) + return + raise ValueError("没有找到指定字幕") + + +async def make_ass_file_danmakus_protobuf( + obj: Union[Video, Episode, CheeseVideo], + page: int = 0, + out="test.ass", + cid: Union[int, None] = None, + credential: Union[Credential, None] = None, + date=None, + font_name="Simsun", + font_size=25.0, + alpha=1, + fly_time=7, + static_time=5, +) -> None: + """ + 生成视频弹幕文件 + + 来源:protobuf + + Args: + obj (Union[Video,Episode,CheeseVideo]) : 对象 + + page (int, optional) : 分 P 号. Defaults to 0. + + out (str, optional) : 输出文件. Defaults to "test.ass" + + cid (int | None, optional) : cid. Defaults to None. + + credential (Credential | None, optional) : 凭据. Defaults to None. + + date (datetime.date, optional) : 获取时间. Defaults to None. + + font_name (str, optional) : 字体. Defaults to "Simsun". + + font_size (float, optional) : 字体大小. Defaults to 25.0. + + alpha (float, optional) : 透明度(0-1). Defaults to 1. + + fly_time (float, optional) : 滚动弹幕持续时间. Defaults to 7. + + static_time (float, optional) : 静态弹幕持续时间. Defaults to 5. + """ + credential = credential if credential else Credential() + if date: + credential.raise_for_no_sessdata() + if isinstance(obj, Video): + v = obj + if isinstance(obj, Episode): + cid = 0 + else: + if cid is None: + if page is None: + raise ArgsException("page_index 和 cid 至少提供一个。") + # type: ignore + cid = await v._Video__get_cid_by_index(page) + try: + info = await v.get_info() + except: + info = {"dimension": {"width": 1440, "height": 1080}} + width = info["dimension"]["width"] + height = info["dimension"]["height"] + if width == 0: + width = 1440 + if height == 0: + height = 1080 + stage_size = (width, height) + if isinstance(obj, Episode): + danmakus = await v.get_danmakus() + else: + danmakus = await v.get_danmakus(cid=cid, date=date) # type: ignore + elif isinstance(obj, CheeseVideo): + stage_size = (1440, 1080) + danmakus = await obj.get_danmakus() + else: + raise ValueError("请传入 Video/Episode/CheeseVideo 类!") + with open(gettempdir() + "/danmaku_temp.xml", "w+", encoding="utf-8") as file: + file.write("") + for d in danmakus: + file.write(d.to_xml()) + file.write("") + export_ass_from_xml( + gettempdir() + "/danmaku_temp.xml", + out, + stage_size, + font_name, + font_size, + alpha, + fly_time, + static_time, + ) + + +async def make_ass_file_danmakus_xml( + obj: Union[Video, Episode, CheeseVideo], + page: int = 0, + out="test.ass", + cid: Union[int, None] = None, + font_name="Simsun", + font_size=25.0, + alpha=1, + fly_time=7, + static_time=5, +) -> None: + """ + 生成视频弹幕文件 + + 来源:xml + + Args: + obj (Union[Video,Episode,Cheese]): 对象 + + page (int, optional) : 分 P 号. Defaults to 0. + + out (str, optional) : 输出文件. Defaults to "test.ass". + + cid (int | None, optional) : cid. Defaults to None. + + font_name (str, optional) : 字体. Defaults to "Simsun". + + font_size (float, optional) : 字体大小. Defaults to 25.0. + + alpha (float, optional) : 透明度(0-1). Defaults to 1. + + fly_time (float, optional) : 滚动弹幕持续时间. Defaults to 7. + + static_time (float, optional) : 静态弹幕持续时间. Defaults to 5. + """ + if isinstance(obj, Video): + v = obj + if isinstance(obj, Episode): + cid = 0 + else: + if cid is None: + if page is None: + raise ArgsException("page_index 和 cid 至少提供一个。") + cid = await v._Video__get_cid_by_index(page) # type: ignore + try: + info = await v.get_info() + except: + info = {"dimension": {"width": 1440, "height": 1080}} + width = info["dimension"]["width"] + height = info["dimension"]["height"] + if width == 0: + width = 1440 + if height == 0: + height = 1080 + stage_size = (width, height) + if isinstance(obj, Episode): + xml_content = await v.get_danmaku_xml() + else: + xml_content = await v.get_danmaku_xml(cid=cid) # type: ignore + elif isinstance(obj, CheeseVideo): + stage_size = (1440, 1080) + xml_content = await obj.get_danmaku_xml() + else: + raise ValueError("请传入 Video/Episode/CheeseVideo 类!") + with open(gettempdir() + "/danmaku_temp.xml", "w+", encoding="utf-8") as file: + file.write(xml_content) + export_ass_from_xml( + gettempdir() + "/danmaku_temp.xml", + out, + stage_size, + font_name, + font_size, + alpha, + fly_time, + static_time, + ) diff --git a/bilibili_api/audio.py b/bilibili_api/audio.py new file mode 100644 index 0000000000000000000000000000000000000000..1f330036cd8636ee10a3ad018546afe79961d7ac --- /dev/null +++ b/bilibili_api/audio.py @@ -0,0 +1,202 @@ +""" +bilibili_api.audio + +音频相关 +""" + +from enum import Enum +from typing import Union, Optional + +from .utils.utils import get_api +from .utils.credential import Credential +from .utils.network import Api + +API = get_api("audio") + + +class Audio: + """ + 音频 + + Attributes: + credential (Credential): 凭据类 + """ + + def __init__(self, auid: int, credential: Union[Credential, None] = None): + """ + Args: + auid (int) : 音频 AU 号 + + credential (Credential | None, optional): 凭据. Defaults to None + """ + self.credential = credential if credential is not None else Credential() + self.__auid = auid + + def get_auid(self) -> int: + return self.__auid + + async def get_info(self) -> dict: + """ + 获取音频信息 + + Returns: + dict: 调用 API 返回的结果 + """ + + api = API["audio_info"]["info"] + params = {"sid": self.__auid} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_tags(self) -> dict: + """ + 获取音频 tags + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["audio_info"]["tag"] + params = {"sid": self.__auid} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_download_url(self) -> dict: + """ + 获取音频下载链接 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["audio_info"]["download_url"] + params = {"sid": self.__auid, "privilege": 2, "quality": 2} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def add_coins(self, num: int = 2) -> dict: + """ + 投币 + + Args: + num (int, optional): 投币数量。Defaults to 2. + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + + api = API["audio_operate"]["coin"] + data = {"sid": self.__auid, "multiply": num} + + return await Api(**api, credential=self.credential).update_data(**data).result + + # TODO: 音频编辑 + + +class AudioList: + """ + 歌单 + + Attributes: + credential (Credential): 凭据类 + """ + + def __init__(self, amid: int, credential: Union[Credential, None] = None): + """ + Args: + amid (int) : 歌单 ID + + credential (Credential | None, optional): 凭据. Defaults to None. + """ + self.__amid = amid + self.credential = credential if credential is not None else Credential() + + def get_amid(self) -> int: + return self.__amid + + async def get_info(self) -> dict: + """ + 获取歌单信息 + + Returns: + dict: 调用 API 返回的结果 + """ + + api = API["list_info"]["info"] + params = {"sid": self.__amid} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_tags(self) -> dict: + """ + 获取歌单 tags + + Returns: + dict: 调用 API 返回的结果 + """ + + api = API["list_info"]["tag"] + params = {"sid": self.__amid} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_song_list(self, pn: int = 1) -> dict: + """ + 获取歌单歌曲列表 + + Args: + pn (int, optional): 页码. Defaults to 1 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["list_info"]["song_list"] + params = {"sid": self.__amid, "pn": pn, "ps": 100} + + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + # TODO: 歌单编辑 + + +async def get_user_stat(uid: int, credential: Union[Credential, None] = None) -> dict: + """ + 获取用户数据(收听数,粉丝数等) + + Args: + uid (int) : 用户 UID + + credential (Credential | None, optional): 凭据. Defaults to None + + Returns: + dict: 调用 API 返回的结果 + """ + credential = credential if credential is not None else Credential() + api = API["audio_info"]["user"] + params = {"uid": uid} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_hot_song_list( + pn: int = 1, credential: Union[Credential, None] = None +) -> dict: + """ + 获取热门歌单 + + Args: + pn(int, optional) : 页数. Defaults to 1 + + credential (Credential | None, optional): 凭据. Defaults to None + + Returns: + dict: 调用 API 返回的结果 + """ + credential = credential if credential is not None else Credential() + api = API["list_info"]["hot"] + params = {"pn": pn, "ps": 100} + return await Api(**api, credential=credential).update_params(**params).result diff --git a/bilibili_api/audio_uploader.py b/bilibili_api/audio_uploader.py new file mode 100644 index 0000000000000000000000000000000000000000..954708e2627e32c6f32627d67a3cf3462139424b --- /dev/null +++ b/bilibili_api/audio_uploader.py @@ -0,0 +1,811 @@ +""" +bilibili_api.audio_uploader + +音频上传 +""" + +import asyncio +import os +import json +import time +from enum import Enum +from typing import List, Union, Optional +from dataclasses import dataclass, field + +from . import user +from .utils.upos import UposFile, UposFileUploader +from .utils.utils import get_api, raise_for_statement +from .utils.picture import Picture +from .utils.AsyncEvent import AsyncEvent +from .utils.credential import Credential +from .exceptions.ApiException import ApiException +from .utils.network import Api, get_session, HEADERS +from .exceptions.NetworkException import NetworkException + +from enum import Enum + +_API = get_api("audio_uploader") + + +class SongCategories: + class ContentType(Enum): # cr_type + """ + 内容类型 + + + MUSIC: 音乐 + + AUDIO_PROGRAM: 有声节目 + """ + + MUSIC = 1 + AUDIO_PROGRAM = 2 + + class CreationType(Enum): + """ + 创作类型 + + + ORIGINAL: 原创 + + COVER: 翻唱/翻奏 + + REMIX: 改编/remix + """ + + ORIGINAL = 1 + COVER = 2 + REMIX = 48 + + class SongType(Enum): + """ + 声音类型 + + + HUMAN_SINGING: 人声演唱 + + VOCALOID: VOCALOID歌手 + + HUMAN_GHOST: 人力鬼畜 + + PURE_MUSIC: 纯音乐/演奏 + """ + + HUMAN_SINGING = 3 + VOCALOID = 4 + HUMAN_GHOST = 5 + PURE_MUSIC = 6 + + class Style(Enum): + """ + 音乐风格 + + + POP: 流行 + + ANCIENT: 古风 + + ROCK: 摇滚 + + FOLK: 民谣 + + ELECTRONIC: 电子 + + DANCE: 舞曲 + + RAP: 说唱 + + LIGHT_MUSIC: 轻音乐 + + ACAPELLA: 阿卡贝拉 + + JAZZ: 爵士 + + COUNTRY: 乡村 + + RNB_SOUL: R&B/Soul + + CLASSICAL: 古典 + + ETHNIC: 民族 + + BRITISH: 英伦 + + METAL: 金属 + + PUNK: 朋克 + + BLUES: 蓝调 + + REGGAE: 雷鬼 + + WORLD_MUSIC: 世界音乐 + + LATIN: 拉丁 + + ALTERNATIVE_INDEPENDENT: 另类/独立 + + NEW_AGE: New Age + + POST_ROCK: 后摇 + + BOSSA_NOVA: Bossa Nova + """ + + POP = 7 + ANCIENT = 8 + ROCK = 9 + FOLK = 10 + ELECTRONIC = 11 + DANCE = 12 + RAP = 13 + LIGHT_MUSIC = 14 + ACAPELLA = 15 + JAZZ = 16 + COUNTRY = 17 + RNB_SOUL = 18 + CLASSICAL = 19 + ETHNIC = 20 + BRITISH = 21 + METAL = 22 + PUNK = 23 + BLUES = 24 + REGGAE = 25 + WORLD_MUSIC = 26 + LATIN = 27 + ALTERNATIVE_INDEPENDENT = 28 + NEW_AGE = 29 + POST_ROCK = 30 + BOSSA_NOVA = 31 + + class Language(Enum): + """ + 语言 + + + CHINESE: 华语 + + JAPANESE: 日语 + + ENGLISH: 英语 + + KOREAN: 韩语 + + CANTONESE: 粤语 + + OTHER_LANGUAGES: 其他语种 + """ + + CHINESE = 32 + JAPANESE = 33 + ENGLISH = 34 + KOREAN = 35 + CANTONESE = 36 + OTHER_LANGUAGES = 37 + + class Theme(Enum): + """ + 主题 + + + ANIMATION: 动画 + + GAME: 游戏 + + FILM_AND_TELEVISION: 影视 + + INTERNET_SONG: 网络歌曲 + + SECOND_CREATION: 同人 + + IDOL: 偶像 + """ + + ANIMATION = 38 + GAME = 39 + FILM_AND_TELEVISION = 40 + INTERNET_SONG = 41 + SECOND_CREATION = 42 + IDOL = 43 + + class AudioType(Enum): + """ + 有声节目类型 + + + RADIO_DRAMA: 广播剧 + + AUDIO_STORY: 有声故事 + + OTHER: 其他 + """ + + RADIO_DRAMA = 44 + AUDIO_STORY = 45 + OTHER = 47 + + +class CompilationCategories: + class SongType(Enum): + """ + 声音类型 + + + HUMAN_SINGING: 人声演唱 + + VOCALOID_SINGER: VOCALOID歌手 + + HUMAN_KICHUKU: 人力鬼畜 + + PURE_MUSIC: 纯音乐 + """ + + HUMAN_SINGING = 102 + VOCALOID_SINGER = 103 + HUMAN_KICHUKU = 104 + PURE_MUSIC = 105 + + class CreationType(Enum): + """ + 创作类型 + + + ORIGINAL: 原创 + + COVER: 翻唱/翻奏 + + REMIX: 改编/remix + """ + + ORIGINAL = 106 + COVER = 107 + REMIX = 108 + + class Language(Enum): + """ + 语种 + + + CHINESE: 中文 + + JAPANESE: 日语 + + ENGLISH: 英语 + + KOREAN: 韩语 + + CANTONESE: 粤语 + + OTHER_LANGUAGES: 其他语种 + """ + + CHINESE = 109 + JAPANESE = 110 + ENGLISH = 111 + KOREAN = 112 + CANTONESE = 113 + OTHER_LANGUAGES = 114 + + class Theme(Enum): + """ + 主题来源 + + + GAME: 游戏 + + ANIMATION: 动画 + + FILM_AND_TELEVISION: 影视 + + NETWORK_SONG: 网络歌曲 + + DERIVATIVE_WORK: 同人 + + IDOL: 偶像 + """ + + ANIMATION = 115 + GAME = 116 + FILM_AND_TELEVISION = 117 + NETWORK_SONG = 118 + DERIVATIVE_WORK = 119 + IDOL = 120 + + class Style(Enum): + """ + 风格 + + + POP: 流行 + + ANCIENT_STYLE: 古风 + + ROCK: 摇滚 + + FOLK_SONG: 民谣 + + ELECTRONIC: 电子 + + DANCE_MUSIC: 舞曲 + + RAP: 说唱 + + LIGHT_MUSIC: 轻音乐 + + A_CAPPELLA: 阿卡贝拉 + + JAZZ: 爵士 + + COUNTRY_MUSIC: 乡村 + + R_AND_B: R&B/Soul + + CLASSICAL: 古典 + + CLASSICAL: 古典 + + ETHNIC: 民族 + + BRITISH: 英伦 + + METAL: 金属 + + PUNK: 朋克 + + BLUES: 蓝调 + + REGGAE: 雷鬼 + + WORLD_MUSIC: 世界音乐 + + LATIN: 拉丁 + + ALTERNATIVE: 另类/独立 + + NEW_AGE: New Age + + POST_ROCK: 后摇 + + BOSSA_NOVA: Bossa Nova + """ + + POP = 121 + ANCIENT_STYLE = 122 + ROCK = 123 + FOLK_SONG = 124 + ELECTRONIC = 125 + DANCE_MUSIC = 126 + RAP = 127 + LIGHT_MUSIC = 128 + A_CAPPELLA = 129 + JAZZ = 130 + COUNTRY_MUSIC = 131 + R_AND_B = 132 + CLASSICAL = 133 + ETHNIC = 134 + BRITISH = 135 + METAL = 136 + PUNK = 137 + BLUES = 138 + REGGAE = 139 + WORLD_MUSIC = 140 + LATIN = 141 + ALTERNATIVE = 142 + NEW_AGE = 143 + POST_ROCK = 144 + BOSSA_NOVA = 145 + + class ContentType(Enum): + """ + 内容类型 + + + MUSIC: 音乐 + + AUDIO_PROGRAM: 有声节目 + """ + + MUSIC = 146 + AUDIO_PROGRAM = 147 + + class AudioType(Enum): + """ + 声音类型 + + + RADIO_DRAMA: 广播剧 + + AUDIO_STORY: 有声故事 + + ASMR: ASMR + + OTHER: 其他 + """ + + RADIO_DRAMA = 148 + AUDIO_STORY = 149 + ASMR = 150 + OTHER = 151 + + +class AudioUploaderEvents(Enum): + """ + 上传事件枚举 + + Events: + + PREUPLOAD 获取上传信息 + + PREUPLOAD_FAILED 获取上传信息失败 + + PRE_CHUNK 上传分块前 + + AFTER_CHUNK 上传分块后 + + CHUNK_FAILED 区块上传失败 + + PRE_COVER 上传封面前 + + AFTER_COVER 上传封面后 + + COVER_FAILED 上传封面失败 + + PRE_SUBMIT 提交音频前 + + SUBMIT_FAILED 提交音频失败 + + AFTER_SUBMIT 提交音频后 + + COMPLETED 完成上传 + + ABORTED 用户中止 + + FAILED 上传失败 + """ + + PREUPLOAD = "PREUPLOAD" + PREUPLOAD_FAILED = "PREUPLOAD_FAILED" + + PRE_CHUNK = "PRE_CHUNK" + AFTER_CHUNK = "AFTER_CHUNK" + CHUNK_FAILED = "CHUNK_FAILED" + + PRE_COVER = "PRE_COVER" + AFTER_COVER = "AFTER_COVER" + COVER_FAILED = "COVER_FAILED" + + PRE_SUBMIT = "PRE_SUBMIT" + SUBMIT_FAILED = "SUBMIT_FAILED" + AFTER_SUBMIT = "AFTER_SUBMIT" + + COMPLETED = "COMPLETE" + ABORTED = "ABORTED" + FAILED = "FAILED" + + +@dataclass +class AuthorInfo: + name: str + uid: int = 0 + + +@dataclass +class SongMeta: + """ + content_type (SongCategories.ContentType): 内容类型 + + song_type (Union[SongCategories.SongType, SongCategories.AudioType]): 歌曲类型 + + creation_type (SongCategories.CreationType): 创作类型 + + language (Optional[SongCategories.Language]): 语言类型 + + theme (Optional[SongCategories.Theme]): 主题来源 + + style (Optional[SongCategories.Style]): 风格类型 + + singer (List[AuthorInfo]): 歌手 + + player (Optional[List[AuthorInfo]]): 演奏 + + sound_source (Optional[List[AuthorInfo]]): 音源 + + tuning (Optional[List[AuthorInfo]]): 调音 + + lyricist (Optional[List[AuthorInfo]]): 作词 + + arranger (List[AuthorInfo]): 编曲 + + composer (Optional[List[AuthorInfo]]): 作曲 + + mixer (Optional[str]): 混音 + + cover_maker (Optional[List[AuthorInfo]]): 封面制作者 + + instrument (Optional[List[str]]): 乐器 + + origin_url (Optional[str]): 原曲链接 + + origin_title (Optional[str]): 原曲标题 + + title (str): 标题 + + cover (Optional[Picture]): 封面 + + description (Optional[str]): 描述 + + tags (Union[List[str], str]): 标签 + + aid (Optional[int]): 视频 aid + + cid (Optional[int]): 视频 cid + + tid (Optional[int]): 视频 tid + + compilation_id (Optional[int]): 合辑 ID + + lrc (Optional[str]): 歌词 + """ + + title: str + desc: str + tags: Union[List[str], str] + content_type: SongCategories.ContentType + song_type: Union[SongCategories.SongType, SongCategories.AudioType] + creation_type: SongCategories.CreationType + language: Optional[SongCategories.Language] = None + theme: Optional[SongCategories.Theme] = None + style: Optional[SongCategories.Style] = None + singer: Optional[List[AuthorInfo]] = field(default_factory=list) + player: Optional[List[AuthorInfo]] = field(default_factory=list) + sound_source: Optional[List[AuthorInfo]] = field(default_factory=list) + tuning: Optional[List[AuthorInfo]] = field(default_factory=list) + lyricist: Optional[List[AuthorInfo]] = field(default_factory=list) + arranger: Optional[List[AuthorInfo]] = field(default_factory=list) + composer: Optional[List[AuthorInfo]] = field(default_factory=list) + mixer: Optional[List[AuthorInfo]] = field(default_factory=list) + cover_maker: Optional[List[AuthorInfo]] = field(default_factory=list) + instrument: Optional[List[str]] = field(default_factory=list) + origin_url: Optional[str] = None + origin_title: Optional[str] = None + cover: Optional[Picture] = None + aid: Optional[int] = None + cid: Optional[int] = None + tid: Optional[int] = None + lrc: Optional[str] = None + compilation_id: Optional[int] = None + is_bgm: bool = True + + +class AudioUploader(AsyncEvent): + """ + 音频上传 + """ + + __song_id: int + __upos_file: UposFile + __task: asyncio.Task + + def _check_meta(self): + raise_for_statement(self.meta.content_type is not None) + raise_for_statement(self.meta.song_type is not None) + raise_for_statement(self.meta.cover is not None and isinstance(self.meta.cover, str)) + if self.meta.content_type == SongCategories.ContentType.MUSIC: + raise_for_statement(self.meta.creation_type is not None) + raise_for_statement(self.meta.song_type is not None) + raise_for_statement(self.meta.language is not None) + + if self.meta.song_type == SongCategories.SongType.HUMAN_SINGING: + raise_for_statement(self.meta.singer is not None) + raise_for_statement(self.meta.language is not None) + + elif self.meta.song_type in [ + SongCategories.SongType.VOCALOID, + SongCategories.SongType.HUMAN_GHOST, + ]: + raise_for_statement(self.meta.sound_source is not None) + raise_for_statement(self.meta.language is not None) + raise_for_statement(self.meta.tuning is not None) + + elif self.meta.song_type == SongCategories.SongType.PURE_MUSIC: + raise_for_statement(self.meta.player is not None) + + if isinstance(self.meta.tags, str): + self.meta.tags = self.meta.tags.split(",") + raise_for_statement(len(self.meta.tags != 0)) + + raise_for_statement(self.meta.title is not None) + raise_for_statement(self.meta.cover is not None) + raise_for_statement(self.meta.desc is not None) + + def __init__(self, path: str, meta: SongMeta, credential: Credential): + """ + 初始化 + + Args: + path (str): 文件路径 + + meta (AudioMeta): 元数据 + + credential (Credential): 账号信息 + """ + super().__init__() + self.path = path + self.meta = meta + self.credential = credential + self.__upos_file = UposFile(path) + + async def _preupload(self) -> dict: + """ + 分 P 上传初始化 + + Returns: + dict: 初始化信息 + """ + self.dispatch(AudioUploaderEvents.PREUPLOAD.value, {"song": self.meta}) + api = _API["preupload"] + + # 首先获取音频文件预检信息 + session = get_session() + + resp = await session.get( + api["url"], + params={ + "profile": "uga/bup", + "name": os.path.basename(self.path), + "size": self.__upos_file.size, + "r": "upos", + "ssl": "0", + "version": "2.6.0", + "build": 2060400, + }, + cookies=self.credential.get_cookies(), + headers=HEADERS.copy(), + ) + if resp.status_code >= 400: + self.dispatch( + AudioUploaderEvents.PREUPLOAD_FAILED.value, {"song": self.meta} + ) + raise NetworkException(resp.status_code, resp.reason_phrase) + + preupload = resp.json() + + if preupload["OK"] != 1: + self.dispatch( + AudioUploaderEvents.PREUPLOAD_FAILED.value, {"song": self.meta} + ) + raise ApiException(json.dumps(preupload)) + + url = f'https:{preupload["endpoint"]}/{preupload["upos_uri"].removeprefix("upos://")}' + headers = HEADERS.copy() + headers["x-upos-auth"] = preupload["auth"] + + # 获取 upload_id + resp = await session.post( + url, + headers=headers, + params={ + "uploads": "", + "output": "json", + "profile": "uga/bup", + "filesize": self.__upos_file.size, + "partsize": preupload["chunk_size"], + "biz_id": preupload["biz_id"], + }, + ) + if resp.status_code >= 400: + self.dispatch( + AudioUploaderEvents.PREUPLOAD_FAILED.value, {"song": self.meta} + ) + raise ApiException("获取 upload_id 错误") + + data = json.loads(resp.text) + + if data["OK"] != 1: + self.dispatch( + AudioUploaderEvents.PREUPLOAD_FAILED.value, {"song": self.meta} + ) + raise ApiException("获取 upload_id 错误:" + json.dumps(data)) + + preupload["upload_id"] = data["upload_id"] + self.__song_id = preupload["biz_id"] + return preupload + + async def _upload_cover(self, cover: str) -> str: + return await upload_cover(cover, self.credential) + + async def _main(self): + preupload = await self._preupload() + await UposFileUploader(file=self.__upos_file, preupload=preupload).upload() + if self.meta.lrc: + lrc_url = await upload_lrc( + song_id=self.__song_id, lrc=self.meta.lrc, credential=self.credential + ) + else: + lrc_url = "" + self.dispatch(AudioUploaderEvents.PRE_COVER) + if self.meta.cover: + try: + cover_url = await self._upload_cover(self.meta.cover) + except Exception as e: + self.dispatch(AudioUploaderEvents.COVER_FAILED, {"err": e}) + raise e + self.dispatch(AudioUploaderEvents.AFTER_COVER.value, cover_url) + self.dispatch(AudioUploaderEvents.PRE_SUBMIT.value) + try: + result = await self._submit(lrc_url=lrc_url, cover_url=cover_url) + except Exception as e: + self.dispatch(AudioUploaderEvents.SUBMIT_FAILED.value, {"err": e}) + raise e + self.dispatch(AudioUploaderEvents.AFTER_SUBMIT.value, result) + return result + + async def _submit(self, cover_url: str, lrc_url: str = "") -> int: + uploader = await user.get_self_info(self.credential) + data = { + "lyric_url": lrc_url, + "cover_url": cover_url, + "song_id": self.__song_id, + "mid": uploader["mid"], + "cr_type": self.meta.content_type.value, + "creation_type_id": self.meta.creation_type.value, + "music_type_id": self.meta.song_type.value, + "style_type_id": self.meta.style.value if self.meta.style else 0, + "theme_type_id": self.meta.theme.value if self.meta.theme else 0, + "language_type_id": self.meta.language.value if self.meta.language else 0, + "origin_title": self.meta.origin_title if self.meta.origin_title else "", + "origin_url": self.meta.origin_url if self.meta.origin_url else "", + "avid": self.meta.aid if self.meta.aid else "", + "tid": self.meta.tid if self.meta.tid else "", + "cid": self.meta.cid if self.meta.cid else "", + "compilation_id": self.meta.compilation_id + if self.meta.compilation_id + else "", + "title": self.meta.title, + "intro": self.meta.desc, + "member_with_type": [ + { + "m_type": 1, # 歌手 + "members": [ + {"name": singer.name, "mid": singer.uid} + for singer in self.meta.singer + ], + }, + { + "m_type": 2, # 作词 + "members": [ + {"name": lyricist.name, "mid": lyricist.uid} + for lyricist in self.meta.lyricist + ], + }, + { + "m_type": 3, + "members": [ + {"name": composer.name, "mid": composer.uid} + for composer in self.meta.composer + ], + }, # 作曲 + { + "m_type": 4, + "members": [ + {"name": arranger.name, "mid": arranger.uid} + for arranger in self.meta.arranger + ], + }, # 编曲 + { + "m_type": 5, + "members": [ + {"name": mixer.name, "mid": mixer.uid} + for mixer in self.meta.mixer + ], + }, # 混音只能填一个人,你问我为什么我不知道 + { + "m_type": 6, + "members": [ + {"name": cover_maker.name, "mid": cover_maker.uid} + for cover_maker in self.meta.cover_maker + ], + }, # 本家作者 + { + "m_type": 7, + "members": [ + {"name": cover_maker.name, "mid": cover_maker.uid} + for cover_maker in self.meta.cover_maker + ], + }, # 封面 + { + "m_type": 8, + "members": [ + {"name": sound_source.name, "mid": sound_source.uid} + for sound_source in self.meta.sound_source + ], + }, # 音源 + { + "m_type": 9, + "members": [ + {"name": tuning.name, "mid": tuning.uid} + for tuning in self.meta.tuning + ], + }, # 调音 + { + "m_type": 10, + "members": [ + {"name": player.name, "mid": player.uid} + for player in self.meta.player + ], + }, # 演奏 + { + "m_type": 11, + "members": [ + {"name": instrument} for instrument in self.meta.instrument + ], + }, # 乐器 + { + "m_type": 127, + "members": [{"name": uploader["name"], "mid": uploader["mid"]}], + }, # 上传者 + ], + "song_tags": [{"tagName": tag_name} for tag_name in self.meta.tags], + "create_time": "%.3f" % time.time(), + "activity_id": 0, + "is_bgm": 1 if self.meta.is_bgm else 0, + "source": 0, + "album_id": 0, + } + api = _API["submit_single_song"] + return ( + await Api(**api, credential=self.credential, json_body=True, no_csrf=True) + .update_data(**data) + .result + ) + + async def start(self) -> dict: + """ + 开始上传 + """ + task = asyncio.create_task(self._main()) + self.__task = task + + try: + result = await task + self.__task = None + return result + except asyncio.CancelledError: + # 忽略 task 取消异常 + pass + except Exception as e: + self.dispatch(AudioUploaderEvents.FAILED.value, {"err": e}) + raise e + + async def abort(self): + """ + 中断更改 + """ + if self.__task: + self.__task.cancel("用户手动取消") + + self.dispatch(AudioUploaderEvents.ABORTED.value, None) + + +async def upload_lrc(lrc: str, song_id: int, credential: Credential) -> str: + """ + 上传 LRC 歌词 + + Args: + lrc (str): 歌词 + + credential (Credential): 凭据 + """ + api = _API["lrc"] + data = {"song_id": song_id, "lrc": lrc} + return await Api(**api, credential=credential).update_data(**data).result + + +async def get_upinfo(param: Union[int, str], credential: Credential) -> List[dict]: + """ + 获取 UP 信息 + + Args: + param (Union[int, str]): UP 主 ID 或者用户名 + + credential (Credential): 凭据 + """ + api = _API["upinfo"] + data = {"param": param} + return await Api(**api, credential=credential).update_data(**data).result + + +async def upload_cover(cover: Picture, credential: Credential) -> str: + api = _API["image"] + # 小于 3MB + raise_for_statement(os.path.getsize(cover) < 1024 * 1024 * 3, "3MB size limit") + # 宽高比 1:1 + raise_for_statement(cover.width == cover.height, "width == height, 600 * 600 recommanded") + files = {"file": cover.content} + return await Api(**api, credential=credential).update_files(**files).result diff --git a/bilibili_api/bangumi.py b/bilibili_api/bangumi.py new file mode 100644 index 0000000000000000000000000000000000000000..53fd39aa2ba93f6ab7febd4944423f2c348e09ad --- /dev/null +++ b/bilibili_api/bangumi.py @@ -0,0 +1,1478 @@ +""" +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 + + @staticmethod + 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) diff --git a/bilibili_api/black_room.py b/bilibili_api/black_room.py new file mode 100644 index 0000000000000000000000000000000000000000..8b3e766524b3ca7d56a9b1def074f04308172c13 --- /dev/null +++ b/bilibili_api/black_room.py @@ -0,0 +1,345 @@ +""" +bilibili_api.black_room + +小黑屋 +""" + +from enum import Enum +from typing import List, Union, Optional + +from .utils.utils import get_api +from .utils.credential import Credential +from .utils.network import Api + + +class BlackReasonType(Enum): + """ + 违规原因类型枚举 (英语翻译错误请忽略/提 issue/发起 PR) + + - ALL: 全部 + - FLOOD_SCREEN: 刷屏 + - SOFA: 抢沙发 + - PRON_VULGAR: 色情低俗内容 + - GAMBLED_SCAMS: 赌博诈骗内容 + - ILLEGAL: 违禁信息 + - ADS: 垃圾广告信息 + - PERSONAL_ATTACK: 人身攻击 + - INVASION_OF_PRIVACY: 侵犯隐私 + - LEAD_WAR: 引战 + - SPOILER: 剧透 + - ADD_MALICIOUS_TAG: 恶意为他人添加标签 + - DEL_OTHERS_TAG: 恶意删除他人标签 + - PRON: 色情 + - VULGAR: 低俗 + - VIOLENT: 暴力血腥内容 + - MALICIOUS_ARCHIVES: 恶意投稿行为 + - ILLEGAL_STATION: 发布非法网站信息 + - SEND_UNREAL_EVENT: 发布不实信息 + - ABETMENT: 发布教唆怂恿信息 + - MALICIOUS_SPAMMING: 恶意刷屏 + - ILLEGAL_ACCOUNT: 账号违规 + - PLAGIARISM: 抄袭 + - PRETEND_ORIGINAL: 冒充官号 + - BAD_FOR_YOUNGS: 青少年不宜 + - BREAK_INTERNET_SECURITY: 破坏网络安全 + - SEND_UNREAL_MISLEADING_EVENT: 发布不实舞蹈信息 + - VIOLATE_SITE_OPERATING_RULES: 违规网站运营规则 + - MALICIOUS_TOPICS: 恶意创建话题 + - CREATE_ILLEGAL_LUCKY_DRAW: 发布违规抽奖 + - PRETEND_OTHER: 冒充他人 + """ + + ALL = 0 + FLOOD_SCREEN = 1 + SOFA = 2 + PRON_VULGAR = 3 + GAMBLED_SCAMS = 4 + ILLEGAL = 5 + ADS = 6 + PERSONAL_ATTACK = 7 + INVASION_OF_PRIVACY = 8 + LEAD_WAR = 9 + SPOILER = 10 + ADD_MALICIOUS_TAG = 11 + DEL_OTHERS_TAG = 12 + PRON = 13 + VULGAR = 14 + VIOLENT = 15 + MALICIOUS_ARCHIVES = 16 + ILLEGAL_STATION = 17 + SEND_UNREAL_EVENT = 18 + ABETMENT = 19 + MALICIOUS_SPAMMING = 20 + ILLEGAL_ACCOUNT = 21 + PLAGIARISM = 22 + PRETEND_ORIGINAL = 23 + BAD_FOR_YOUNGS = 24 + BREAK_INTERNET_SECURITY = 25 + SEND_UNREAL_MISLEADING_EVENT = 26 + PRETEND_ORIGINAL_ACCOUNT = 27 + SEND_BAD_EVENT = 28 + VIOLATE_SITE_OPERATING_RULES = 29 + MALICIOUS_TOPICS = 30 + CREATE_ILLEGAL_LUCKY_DRAW = 31 + PRETEND_OTHER = 32 + + +class BlackType(Enum): + """ + 违规类型枚举 + + - ALL: 全部 + - COMMENT: 评论 + - DANMAKU: 弹幕 + - PRIVATE_MESSAGE: 私信 + - TAG: 标签 + - PERSONAL_INFORMATION: 个人信息 + - VIDEO: 视频 + - ARTICLE: 专栏 + - DYNAMIC: 动态 + - ALBUM: 相簿 + """ + + ALL = 0 + COMMENT = 1 + DANMAKU = 2 + PRIVATE_MESSAGE = 3 + TAG = 4 + PERSONAL_INFORMATION = 5 + VIDEO = 6 + ARTICLE = 8 + DYNAMIC = 10 + ALBUM = 11 + + +class BlackFrom(Enum): + """ + 违规来源 + + - SYSTEM: 系统封禁 + - ADMIN: 风纪仲裁 + - ALL: 全部 + """ + + SYSTEM = 0 + ADMIN = 1 + ALL = None + + +class JuryVoteOpinion(Enum): + """ + 仲裁投票类型枚举,选择对应案件类型的观点 + + 单条评论(弹幕) + - SUITABLE: 合适 + - AVERAGE: 一般 + - UNSUITABLE: 不合适 + - UNKNOW: 无法判断 + + 评论(弹幕)氛围 + - ENV_GREAT: 评论环境好 + - ENV_AVERAGE: 评论环境一般 + - ENV_BAD: 评论环境差 + - ENV_UNKNOW: 无法判断评论环境 + """ + + SUITABLE = 1 + AVERAGE = 2 + UNSUITABLE = 3 + UNKNOW = 4 + ENV_GREAT = 11 + ENV_AVERAGE = 12 + ENV_BAD = 13 + ENV_UNKNOW = 14 + + +API = get_api("black-room") + + +async def get_blocked_list( + from_: BlackFrom = BlackFrom.ALL, + type_: BlackType = BlackType.ALL, + pn: int = 1, + credential: Union[Credential, None] = None, +) -> dict: + """ + 获取小黑屋中的违规列表 + + Args: + from_ (BlackFrom) : 违规来源. Defaults to BlackFrom.ALL. + + type_ (int) : 违规类型. Defaults to BlackType.ALL. + + pn (int) : 页数. Defaults to 1. + + credential (Credential | None): 凭据. Defaults to None. + """ + credential = credential if credential else Credential() + api = API["black_room"]["info"] + params = {"pn": pn, "otype": type_.value} + if from_.value != None: + params["btype"] = from_.value + return await Api(**api, credential=credential).update_params(**params).result + + +class BlackRoom: + """ + 小黑屋 + + Attributes: + credential (Credential): 凭据类 + """ + + def __init__(self, black_room_id: int, credential: Union[Credential, None] = None): + """ + Args: + black_room_id (int) : 小黑屋 id + + credential (Credential | None, optional): 凭据类. Defaults to None. + """ + self.__id = black_room_id + self.credential = credential if credential else Credential() + + async def get_details(self) -> dict: + """ + 获取小黑屋详细信息 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["black_room"]["detail"] + params = {"id": self.__id} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_reason(self) -> BlackReasonType: + """ + 获取小黑屋的封禁原因 + + Returns: + BlackReasonType: 封禁原因枚举类 + """ + return BlackReasonType((await self.get_details())["reasonType"]) + + async def get_id(self) -> int: + return self.__id + + async def set_id(self, id_) -> None: + self.__init__(id_, self.credential) + + +class JuryCase: + def __init__(self, case_id: str, credential: Credential): + """ + Args: + case_id (str) : 案件 id + + credential (Credential) : 凭据类 + """ + self.case_id = case_id + self.credential = credential + + async def get_details(self) -> dict: + """ + 获取案件详细信息 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["jury"]["detail"] + params = {"case_id": self.case_id} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_opinions(self, pn: int = 1, ps: int = 20) -> dict: + """ + 获取案件的观点列表 + + Args: + pn (int, optional): 页数. Defaults to 1. + + ps (int, optional): 每页数量. Defaults to 20. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["jury"]["opinion"] + params = {"case_id": self.case_id, "pn": pn, "ps": ps} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def vote( + self, + opinion: JuryVoteOpinion, + is_insider: bool, + is_anonymous: bool, + reason: Optional[str] = None, + ) -> dict: + """ + 进行仲裁投票 + + Args: + opinion (JuryVoteOpinion): 投票选项类型 + + is_insider (bool): 是否观看此类视频 + + is_anonymous (bool): 是否匿名投票 + + reason (str, optional): 投票理由. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["jury"]["vote"] + data = { + "case_id": self.case_id, + "vote": opinion.value, + "insiders": 1 if is_insider else 0, + "anonymous": 1 if is_anonymous else 0, + "csrf": self.credential.bili_jct, + } + if reason: + data["content"] = reason + return await Api(**api, credential=self.credential).update_data(**data).result + + +async def get_next_jury_case(credential: Credential) -> JuryCase: + """ + 获取下一个待审理的案件 + + Args: + credential (Credential | None, optional): 凭据类. Defaults to None. + + Returns: + JuryCase: 案件类 + """ + credential.raise_for_no_sessdata() + api = API["jury"]["next_case"] + return JuryCase( + (await Api(**api, credential=credential).result)["case_id"], credential + ) + + +async def get_jury_case_list( + credential: Credential, pn: int = 1, ps: int = 20 +) -> List[JuryCase]: + """ + 获取仲裁案件列表 + + Args: + credential (Credential): 凭据类 + + pn (int, optional): 页数. Defaults to 1. + + ps (int, optional): 每页数量. Defaults to 20. + + Returns: + List[JuryCase]: 仲裁案件列表 + """ + api = API["jury"]["case_list"] + params = {"pn": pn, "ps": ps} + info = await Api(**api, credential=credential).update_params(**params).result + return [JuryCase(case["case_id"], credential) for case in info["list"]] diff --git a/bilibili_api/channel_series.py b/bilibili_api/channel_series.py new file mode 100644 index 0000000000000000000000000000000000000000..cc2af7c954acaafbd1c33f02eeb88927d1c05065 --- /dev/null +++ b/bilibili_api/channel_series.py @@ -0,0 +1,284 @@ +""" +bilibili_api.channel_series + +用户合集与列表相关 +""" +import json +from enum import Enum +from typing import List, Union, Optional + +import httpx + +from .utils.utils import get_api, raise_for_statement +from .utils.credential import Credential +from .utils.network import Api, HEADERS + +API_USER = get_api("user") +API = get_api("channel-series") + +channel_meta_cache = {} + + +class ChannelOrder(Enum): + """ + 合集视频排序顺序。 + + DEFAULT: 默认排序 + + CHANGE : 升序排序 + """ + + DEFAULT = "false" + CHANGE = "true" + + +class ChannelSeriesType(Enum): + """ + 合集与列表类型 + + + SERIES: 相同视频分类 + + SEASON: 新概念多 P + + **SEASON 类合集与列表名字为`合集·XXX`,请注意区别** + """ + + SERIES = 0 + SEASON = 1 + + +class ChannelSeries: + """ + 合集与列表类 + + Attributes: + credential (Credential): 凭据类. Defaults to None. + """ + + def __init__( + self, + uid: int = -1, + type_: ChannelSeriesType = ChannelSeriesType.SERIES, + id_: int = -1, + credential: Union[Credential, None] = None, + ): + """ + Args: + uid(int) : 用户 uid. Defaults to -1. + + type_(ChannelSeriesType): 合集与列表类型. Defaults to ChannelSeriesType.SERIES. + + id_(int) : season_id 或 series_id. Defaults to -1. + + credential(Credential) : 凭证. Defaults to None. + """ + global channel_meta_cache + raise_for_statement(id_ != -1) + raise_for_statement(type_ != None) + from .user import User + + self.__uid = uid + self.is_new = type_.value + self.id_ = id_ + self.owner = User(self.__uid, credential=credential) + self.credential = credential + self.meta = None + if not f"{type_.value}-{id_}" in channel_meta_cache.keys(): + if self.is_new: + api = API_USER["channel_series"]["season_info"] + params = {"season_id": self.id_} + else: + api = API_USER["channel_series"]["info"] + params = {"series_id": self.id_} + resp = Api(**api).update_params(**params).result_sync + if self.is_new: + self.meta = resp["info"] + self.meta["mid"] = resp["info"]["upper"]["mid"] + self.__uid = self.meta["mid"] + self.owner = User(self.__uid, credential=credential) + else: + self.meta = resp["meta"] + self.__uid = self.meta["mid"] + self.owner = User(self.__uid, credential=credential) + else: + self.meta = channel_meta_cache[f"{type_.value}-{id_}"] + + def get_meta(self) -> dict: + """ + 获取元数据 + + Returns: + 调用 API 返回的结果 + """ + return self.meta # type: ignore + + async def get_videos( + self, sort: ChannelOrder = ChannelOrder.DEFAULT, pn: int = 1, ps: int = 100 + ) -> dict: + """ + 获取合集视频 + Args: + sort(ChannelOrder): 排序方式 + + pn(int) : 页数,默认为 1 + + ps(int) : 每一页显示的视频数量 + + Returns: + 调用 API 返回的结果 + """ + if self.is_new: + return await self.owner.get_channel_videos_season(self.id_, sort, pn, ps) + else: + return await self.owner.get_channel_videos_series(self.id_, sort, pn, ps) + + +async def create_channel_series( + name: str, + aids: List[int] = [], + keywords: List[str] = [], + description: str = "", + credential: Union[Credential, None] = None, +) -> dict: + """ + 新建一个视频列表 (旧版合集) + + Args: + name (str): 列表名称。 + + aids (List[int]): 要加入列表的视频的 aid 列表。 + + keywords (List[str]): 列表的关键词。 + + description (str): 列表的描述。 + + credential (Credential | None): 凭据类。 + + Returns: + dict: 调用 API 返回的结果 + """ + from .user import get_self_info + + credential = credential if credential else Credential() + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + api = API_USER["channel_series"]["create"] + info = await get_self_info(credential) + data = { + "mid": info["mid"], + "aids": ",".join(map(lambda x: str(x), aids)), + "name": name, + "keywords": ",".join(keywords), + "description": description, + } + return await Api(**api, credential=credential).update_data(**data).result + + +async def del_channel_series(series_id: int, credential: Credential) -> dict: + """ + 删除视频列表(旧版合集) + + Args: + series_id (int) : 旧版合集 id。 + + credential (Credential): 凭据类。 + + Returns: + dict: 调用 API 返回的结果 + """ + from .user import User, get_self_info + + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + series_total = ChannelSeries( + type_=ChannelSeriesType.SERIES, id_=series_id, credential=credential + ).get_meta()["total"] + self_uid = (await get_self_info(credential))["mid"] + aids = [] + pages = series_total // 20 + (1 if (series_total % 20 != 0) else 0) + for page in range(1, pages + 1, 1): + page_info = await User(self_uid, credential).get_channel_videos_series( + series_id, pn=page, ps=20 + ) + for aid in page_info["aids"]: + aids.append(aid) + api = API_USER["channel_series"]["del_channel_series"] + data = { + "mid": self_uid, + "series_id": series_id, + "aids": ",".join(map(lambda x: str(x), aids)), + } + return await Api(**api, credential=credential).update_data(**data).result + + +async def add_aids_to_series( + series_id: int, aids: List[int], credential: Credential +) -> dict: + """ + 添加视频至视频列表(旧版合集) + + Args: + series_id (int) : 旧版合集 id。 + + aids (List[int]) : 视频 aid 列表。 + + credential (Credential): 凭据类。 + + Returns: + dict: 调用 API 返回的结果 + """ + from .user import get_self_info + + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + self_info = await get_self_info(credential) + api = API_USER["channel_series"]["add_channel_aids_series"] + data = { + "mid": self_info["mid"], + "series_id": series_id, + "aids": ",".join(map(lambda x: str(x), aids)), + } + return await Api(**api, credential=credential).update_data(**data).result + + +async def del_aids_from_series( + series_id: int, aids: List[int], credential: Credential +) -> dict: + """ + 从视频列表(旧版合集)删除视频 + + Args: + series_id (int) : 旧版合集 id。 + + aids (List[int]) : 视频 aid 列表。 + + credential (Credential): 凭据类。 + + Returns: + dict: 调用 API 返回的结果 + """ + from .user import get_self_info + + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + self_info = await get_self_info(credential) + api = API_USER["channel_series"]["del_channel_aids_series"] + data = { + "mid": self_info["mid"], + "series_id": series_id, + "aids": ",".join(map(lambda x: str(x), aids)), + } + return await Api(**api, credential=credential).update_data(**data).result + + +async def set_follow_channel_season( + season_id: int, status: bool = True, credential: Optional[Credential] = None +) -> dict: + """ + 设置是否订阅合集(新版) + + Args: + season_id (int) : 合集 id + + status (bool): 是否订阅状态. Defaults to True. + """ + api = API["operate"]["fav"] if status else API["operate"]["unfav"] + data = {"season_id": season_id} + return await Api(**api, credential=credential).update_data(**data).result diff --git a/bilibili_api/cheese.py b/bilibili_api/cheese.py new file mode 100644 index 0000000000000000000000000000000000000000..08b1d697a7a0a99beade1cc959516f8f4be0058c --- /dev/null +++ b/bilibili_api/cheese.py @@ -0,0 +1,766 @@ +""" +bilibili_api.cheese + +有关 bilibili 课程的 api。 + +注意,注意!课程中的视频和其他视频几乎没有任何相通的 API! + +不能将 CheeseVideo 换成 Video 类。(CheeseVideo 类保留了所有的通用的 API) + +获取下载链接需要使用 bilibili_api.cheese.get_download_url,video.get_download_url 不适用。 + +还有,课程的 season_id 和 ep_id 不与番剧相通,井水不犯河水,请不要错用! +""" + +import json +import datetime +from typing import Any, List, Union + +from . import settings +from .utils.utils import get_api +from .utils.danmaku import Danmaku +from .utils.credential import Credential +from .utils.BytesReader import BytesReader +from .exceptions.ArgsException import ArgsException +from .utils.network import Api, get_session +from .exceptions import NetworkException, ResponseException, DanmakuClosedException + +API = get_api("cheese") +API_video = get_api("video") + + +cheese_video_meta_cache = {} + + +class CheeseList: + """ + 课程类 + + Attributes: + credential (Credential): 凭据类 + """ + + def __init__( + self, + season_id: int = -1, + ep_id: int = -1, + credential: Union[Credential, None] = None, + ): + """ + Args: + season_id (int) : ssid + + ep_id (int) : 单集 ep_id + + credential (Credential): 凭据类 + + 注意:season_id 和 ep_id 任选一个即可,两个都选的话 + 以 season_id 为主 + """ + if (season_id == -1) and (ep_id == -1): + raise ValueError("season id 和 ep id 必须选一个") + self.__season_id = season_id + self.__ep_id = ep_id + self.credential = credential if credential else Credential() + if self.__season_id == -1: + # self.season_id = str(sync(self.get_meta())["season_id"]) + api = API["info"]["meta"] + params = {"season_id": self.__season_id, "ep_id": self.__ep_id} + meta = Api(**api, credential=self.credential).update_params(**params).result_sync + self.__season_id = int(meta["season_id"]) + + def set_season_id(self, season_id: int) -> None: + self.__init__(season_id=season_id) + + def set_ep_id(self, ep_id: int) -> None: + self.__init__(ep_id=ep_id) + + def get_season_id(self) -> int: + return self.__season_id + + async def get_meta(self) -> dict: + """ + 获取教程元数据 + + Returns: + 调用 API 所得的结果。 + """ + api = API["info"]["meta"] + params = {"season_id": self.__season_id, "ep_id": self.__ep_id} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_list_raw(self): + """ + 获取教程所有视频 (返回原始数据) + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["list"] + params = {"season_id": self.__season_id, "pn": 1, "ps": 1000} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_list(self) -> List["CheeseVideo"]: + """ + 获取教程所有视频 + + Returns: + List[CheeseVideo]: 课程视频列表 + """ + global cheese_video_meta_cache + api = API["info"]["list"] + params = {"season_id": self.__season_id, "pn": 1, "ps": 1000} + lists = ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + cheese_videos = [] + for c in lists["items"]: + c["ssid"] = self.get_season_id() + cheese_video_meta_cache[c["id"]] = c + cheese_videos.append(CheeseVideo(c["id"], self.credential)) + return cheese_videos + + +class CheeseVideo: + """ + 教程视频类 + 因为不和其他视频相通,所以这里是一个新的类,无继承 + + Attributes: + credential (Credential): 凭据类 + + cheese (CheeseList): 所属的课程 + """ + + def __init__(self, epid, credential: Union[Credential, None] = None): + """ + Args: + epid (int) : 单集 ep_id + + credential (Credential): 凭据类 + """ + global cheese_video_meta_cache + self.__epid = epid + meta = cheese_video_meta_cache.get(epid) + if meta == None: + self.cheese = CheeseList(ep_id=self.__epid) + else: + self.cheese = CheeseList(season_id=meta["ssid"]) + self.credential = credential if credential else Credential() + if meta == None: + api = API["info"]["meta"] + params = {"season_id": self.cheese.get_season_id(), "ep_id": self.__epid} + metadata = Api(**api).update_params(**params).result_sync + for v in metadata["episodes"]: + if v["id"] == epid: + self.__aid = v["aid"] + self.__cid = v["cid"] + self.__meta = v + else: + self.__meta = meta + self.__aid = meta["aid"] + self.__cid = meta["cid"] + + def get_aid(self) -> int: + return self.__aid + + def get_cid(self) -> int: + return self.__cid + + def get_meta(self) -> dict: + """ + 获取课程元数据 + + Returns: + dict: 视频元数据 + """ + return self.__meta + + def get_cheese(self) -> "CheeseList": + """ + 获取所属课程 + + Returns: + CheeseList: 所属课程 + """ + return self.cheese + + def set_epid(self, epid: int) -> None: + self.__init__(epid, self.credential) + + def get_epid(self) -> int: + return self.__epid + + async def get_download_url(self) -> dict: + """ + 获取下载链接 + + Returns: + dict: 调用 API 返回的结果。 + """ + api = API["info"]["playurl"] + params = { + "avid": self.get_aid(), + "ep_id": self.get_epid(), + "cid": self.get_cid(), + "qn": 127, + "fnval": 4048, + "fourk": 1, + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_stat(self) -> dict: + """ + 获取视频统计数据(播放量,点赞数等)。 + + Returns: + dict: 调用 API 返回的结果。 + """ + api = API_video["info"]["stat"] + params = {"aid": self.get_aid()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_pages(self) -> dict: + """ + 获取分 P 信息。 + + Returns: + dict: 调用 API 返回的结果。 + """ + api = API_video["info"]["pages"] + params = {"aid": self.get_aid()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_danmaku_view(self) -> dict: + """ + 获取弹幕设置、特殊弹幕、弹幕数量、弹幕分段等信息。 + + Returns: + dict: 调用 API 返回的结果。 + """ + cid = self.get_cid() + session = get_session() + api = API_video["danmaku"]["view"] + + config = {} + config["url"] = api["url"] + config["params"] = {"type": 1, "oid": cid, "pid": self.get_aid()} + config["cookies"] = self.credential.get_cookies() + config["headers"] = { + "Referer": "https://www.bilibili.com", + "User-Agent": "Mozilla/5.0", + } + + try: + resp = await session.get(**config) + except Exception as e: + raise NetworkException(-1, str(e)) + + resp_data = resp.read() + json_data = {} + reader = BytesReader(resp_data) + # 解析二进制数据流 + + def read_dm_seg(stream: bytes): + reader_ = BytesReader(stream) + data = {} + while not reader_.has_end(): + t = reader_.varint() >> 3 + if t == 1: + data["page_size"] = reader_.varint() + elif t == 2: + data["total"] = reader_.varint() + else: + continue + return data + + def read_flag(stream: bytes): + reader_ = BytesReader(stream) + data = {} + while not reader_.has_end(): + t = reader_.varint() >> 3 + if t == 1: + data["rec_flag"] = reader_.varint() + elif t == 2: + data["rec_text"] = reader_.string() + elif t == 3: + data["rec_switch"] = reader_.varint() + else: + continue + return data + + def read_command_danmakus(stream: bytes): + reader_ = BytesReader(stream) + data = {} + while not reader_.has_end(): + t = reader_.varint() >> 3 + if t == 1: + data["id"] = reader_.varint() + elif t == 2: + data["oid"] = reader_.varint() + elif t == 3: + data["mid"] = reader_.varint() + elif t == 4: + data["commend"] = reader_.string() + elif t == 5: + data["content"] = reader_.string() + elif t == 6: + data["progress"] = reader_.varint() + elif t == 7: + data["ctime"] = reader_.string() + elif t == 8: + data["mtime"] = reader_.string() + elif t == 9: + data["extra"] = json.loads(reader_.string()) + + elif t == 10: + data["id_str"] = reader_.string() + else: + continue + return data + + def read_settings(stream: bytes): + reader_ = BytesReader(stream) + data = {} + while not reader_.has_end(): + t = reader_.varint() >> 3 + + if t == 1: + data["dm_switch"] = reader_.bool() + elif t == 2: + data["ai_switch"] = reader_.bool() + elif t == 3: + data["ai_level"] = reader_.varint() + elif t == 4: + data["enable_top"] = reader_.bool() + elif t == 5: + data["enable_scroll"] = reader_.bool() + elif t == 6: + data["enable_bottom"] = reader_.bool() + elif t == 7: + data["enable_color"] = reader_.bool() + elif t == 8: + data["enable_special"] = reader_.bool() + elif t == 9: + data["prevent_shade"] = reader_.bool() + elif t == 10: + data["dmask"] = reader_.bool() + elif t == 11: + data["opacity"] = reader_.float(True) + elif t == 12: + data["dm_area"] = reader_.varint() + elif t == 13: + data["speed_plus"] = reader_.float(True) + elif t == 14: + data["font_size"] = reader_.float(True) + elif t == 15: + data["screen_sync"] = reader_.bool() + elif t == 16: + data["speed_sync"] = reader_.bool() + elif t == 17: + data["font_family"] = reader_.string() + elif t == 18: + data["bold"] = reader_.bool() + elif t == 19: + data["font_border"] = reader_.varint() + elif t == 20: + data["draw_type"] = reader_.string() + else: + continue + return data + + def read_image_danmakus(string: bytes): + image_list = [] + reader_ = BytesReader(string) + while not reader_.has_end(): + type_ = reader_.varint() >> 3 + if type_ == 1: + details_dict = {} + details_dict["texts"] = [] + img_details = reader_.bytes_string() + reader_details = BytesReader(img_details) + while not reader_details.has_end(): + type_details = reader_details.varint() >> 3 + if type_details == 1: + details_dict["texts"].append(reader_details.string()) + elif type_details == 2: + details_dict["image"] = reader_details.string() + elif type_details == 3: + id_string = reader_details.bytes_string() + id_reader = BytesReader(id_string) + while not id_reader.has_end(): + type_id = id_reader.varint() >> 3 + if type_id == 2: + details_dict["id"] = id_reader.varint() + else: + raise ResponseException("解析响应数据错误") + image_list.append(details_dict) + else: + raise ResponseException("解析响应数据错误") + return image_list + + while not reader.has_end(): + type_ = reader.varint() >> 3 + + if type_ == 1: + json_data["state"] = reader.varint() + elif type_ == 2: + json_data["text"] = reader.string() + elif type_ == 3: + json_data["text_side"] = reader.string() + elif type_ == 4: + json_data["dm_seg"] = read_dm_seg(reader.bytes_string()) + elif type_ == 5: + json_data["flag"] = read_flag(reader.bytes_string()) + elif type_ == 6: + if "special_dms" not in json_data: + json_data["special_dms"] = [] + json_data["special_dms"].append(reader.string()) + elif type_ == 7: + json_data["check_box"] = reader.bool() + elif type_ == 8: + json_data["count"] = reader.varint() + elif type_ == 9: + if "command_dms" not in json_data: + json_data["command_dms"] = [] + json_data["command_dms"].append( + read_command_danmakus(reader.bytes_string()) + ) + elif type_ == 10: + json_data["dm_setting"] = read_settings(reader.bytes_string()) + elif type_ == 12: + json_data["image_dms"] = read_image_danmakus(reader.bytes_string()) + else: + continue + return json_data + + async def get_danmakus(self, date: Union[datetime.date, None] = None): + """ + 获取弹幕。 + + Args: + date (datetime.Date | None, optional): 指定日期后为获取历史弹幕,精确到年月日。Defaults to None. + + Returns: + List[Danmaku]: Danmaku 类的列表。 + """ + if date is not None: + self.credential.raise_for_no_sessdata() + + # self.credential.raise_for_no_sessdata() + + session = get_session() + aid = self.get_aid() + params: dict[str, Any] = {"oid": self.get_cid(), "type": 1, "pid": aid} + if date is not None: + # 获取历史弹幕 + api = API_video["danmaku"]["get_history_danmaku"] + params["date"] = date.strftime("%Y-%m-%d") + params["type"] = 1 + all_seg = 1 + else: + api = API_video["danmaku"]["get_danmaku"] + view = await self.get_danmaku_view() + all_seg = view["dm_seg"]["total"] + + danmakus = [] + + for seg in range(all_seg): + if date is None: + # 仅当获取当前弹幕时需要该参数 + params["segment_index"] = seg + 1 + + config = {} + config["url"] = api["url"] + config["params"] = params + config["headers"] = { + "Referer": "https://www.bilibili.com", + "User-Agent": "Mozilla/5.0", + } + config["cookies"] = self.credential.get_cookies() + + try: + req = await session.get(**config) + except Exception as e: + raise NetworkException(-1, str(e)) + + if "content-type" not in req.headers.keys(): + break + else: + content_type = req.headers["content-type"] + if content_type != "application/octet-stream": + raise ResponseException("返回数据类型错误:") + + # 解析二进制流数据 + data = req.read() + if data == b"\x10\x01": + # 视频弹幕被关闭 + raise DanmakuClosedException() + + reader = BytesReader(data) + while not reader.has_end(): + type_ = reader.varint() >> 3 + if type_ != 1: + if type_ == 4: + reader.bytes_string() + # 什么鬼?我用 protoc 解析出乱码! + elif type_ == 5: + # 大会员专属颜色 + reader.varint() + reader.varint() + reader.varint() + reader.bytes_string() + elif type_ == 13: + # ??? + continue + else: + raise ResponseException("解析响应数据错误") + + dm = Danmaku("") + dm_pack_data = reader.bytes_string() + dm_reader = BytesReader(dm_pack_data) + + while not dm_reader.has_end(): + data_type = dm_reader.varint() >> 3 + + if data_type == 1: + dm.id_ = dm_reader.varint() + elif data_type == 2: + dm.dm_time = dm_reader.varint() / 1000 + elif data_type == 3: + dm.mode = dm_reader.varint() + elif data_type == 4: + dm.font_size = dm_reader.varint() + elif data_type == 5: + dm.color = hex(dm_reader.varint())[2:] + elif data_type == 6: + dm.crc32_id = dm_reader.string() + elif data_type == 7: + dm.text = dm_reader.string() + elif data_type == 8: + dm.send_time = dm_reader.varint() + elif data_type == 9: + dm.weight = dm_reader.varint() + elif data_type == 10: + dm.action = str(dm_reader.varint()) + elif data_type == 11: + dm.pool = dm_reader.varint() + elif data_type == 12: + dm.id_str = dm_reader.string() + elif data_type == 13: + dm.attr = dm_reader.varint() + elif data_type == 14: + dm.uid = dm_reader.varint() + elif data_type == 15: + dm_reader.varint() + elif data_type == 20: + dm_reader.bytes_string() + elif data_type == 21: + dm_reader.bytes_string() + else: + break + danmakus.append(dm) + return danmakus + + async def get_pbp(self): + """ + 获取高能进度条 + + Returns: + 调用 API 返回的结果 + """ + cid = self.get_cid() + + api = API_video["info"]["pbp"] + + params = {"cid": cid} + + session = get_session() + + return json.loads( + ( + await session.get( + api["url"], params=params, cookies=self.credential.get_cookies() + ) + ).text + ) + + async def send_danmaku(self, danmaku: Union[Danmaku, None] = None): + """ + 发送弹幕。 + + Args: + danmaku (Danmaku | None): Danmaku 类。Defaults to None. + + Returns: + dict: 调用 API 返回的结果。 + """ + + danmaku = danmaku if danmaku else Danmaku("") + + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API_video["danmaku"]["send_danmaku"] + + if danmaku.is_sub: + pool = 1 + else: + pool = 0 + data = { + "type": 1, + "oid": self.get_cid(), + "msg": danmaku.text, + "aid": self.get_aid(), + "progress": int(danmaku.dm_time * 1000), + "color": int(danmaku.color, 16), + "fontsize": danmaku.font_size, + "pool": pool, + "mode": danmaku.mode, + "plat": 1, + } + return await Api(**api, credential=self.credential).update_data(**data).result + + async def has_liked(self): + """ + 视频是否点赞过。 + + Returns: + bool: 视频是否点赞过。 + """ + self.credential.raise_for_no_sessdata() + + api = API_video["info"]["has_liked"] + params = {"aid": self.get_aid()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + == 1 + ) + + async def get_pay_coins(self): + """ + 获取视频已投币数量。 + + Returns: + int: 视频已投币数量。 + """ + self.credential.raise_for_no_sessdata() + + api = API_video["info"]["get_pay_coins"] + params = {"aid": self.get_aid()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + )["multiply"] + + async def has_favoured(self): + """ + 是否已收藏。 + + Returns: + bool: 视频是否已收藏。 + """ + self.credential.raise_for_no_sessdata() + + api = API_video["info"]["has_favoured"] + params = {"aid": self.get_aid()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + )["favoured"] + + async def like(self, status: bool = True): + """ + 点赞视频。 + + Args: + status (bool, optional): 点赞状态。Defaults to True. + + Returns: + dict: 调用 API 返回的结果。 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API_video["operate"]["like"] + data = {"aid": self.get_aid(), "like": 1 if status else 2} + return await Api(**api, credential=self.credential).update_data(**data).result + + async def pay_coin(self, num: int = 1, like: bool = False): + """ + 投币。 + + Args: + num (int, optional) : 硬币数量,为 1 ~ 2 个。Defaults to 1. + + like (bool, optional): 是否同时点赞。Defaults to False. + + Returns: + dict: 调用 API 返回的结果。 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + if num not in (1, 2): + raise ArgsException("投币数量只能是 1 ~ 2 个。") + + api = API_video["operate"]["coin"] + data = { + "aid": self.get_aid(), + "multiply": num, + "like": 1 if like else 0, + } + return await Api(**api, credential=self.credential).update_data(**data).result + + async def set_favorite( + self, add_media_ids: List[int] = [], del_media_ids: List[int] = [] + ): + """ + 设置视频收藏状况。 + + Args: + add_media_ids (List[int], optional): 要添加到的收藏夹 ID. Defaults to []. + + del_media_ids (List[int], optional): 要移出的收藏夹 ID. Defaults to []. + + Returns: + dict: 调用 API 返回结果。 + """ + if len(add_media_ids) + len(del_media_ids) == 0: + raise ArgsException("对收藏夹无修改。请至少提供 add_media_ids 和 del_media_ids 中的其中一个。") + + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API_video["operate"]["favorite"] + data = { + "rid": self.get_aid(), + "type": 2, + "add_media_ids": ",".join(map(lambda x: str(x), add_media_ids)), + "del_media_ids": ",".join(map(lambda x: str(x), del_media_ids)), + } + return await Api(**api, credential=self.credential).update_data(**data).result + + async def get_danmaku_xml(self): + """ + 获取弹幕(xml 源) + + Returns: + str: xml 文件源 + """ + url = f"https://comment.bilibili.com/{self.get_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") diff --git a/bilibili_api/client.py b/bilibili_api/client.py new file mode 100644 index 0000000000000000000000000000000000000000..c651d2ac8ee6617fa9cb3a6c14dbf85f42d7410a --- /dev/null +++ b/bilibili_api/client.py @@ -0,0 +1,32 @@ +""" +bilibili_api.client + +IP 终端相关 +""" + +from .utils.utils import get_api +from .utils.network import Api + +API = get_api("client") + + +async def get_zone() -> dict: + """ + 通过 IP 获取地理位置 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["zone"] + return await Api(**api).result + + +# async def get_client_info() -> dict: +# """ +# 获取 IP 信息 + +# Returns: +# dict: 调用 API 返回的结果 +# """ +# api = API["info"] +# return await Api(**api).result diff --git a/bilibili_api/comment.py b/bilibili_api/comment.py new file mode 100644 index 0000000000000000000000000000000000000000..0df1707a3d0ddb973e874e26f347e79c6d25f694 --- /dev/null +++ b/bilibili_api/comment.py @@ -0,0 +1,454 @@ +""" +bilibili_api.comment + +评论相关。 + +关于资源 ID(oid)的一些示例({}部分为应该传入的参数)。 + ++ 视频:AV 号:av{170001}。 ++ 专栏:cv{9762979}。 ++ 动态(画册类型):{116859542}。 ++ 动态(纯文本):{497080393649439253}。 ++ 课程:ep{5556} ++ 小黑屋: ban/{2600321} +""" +from enum import Enum +from typing import Union, Optional + +from .utils.utils import get_api +from .utils.credential import Credential +from .utils.network import Api +from .exceptions.ArgsException import ArgsException + +API = get_api("common") + + +class CommentResourceType(Enum): + """ + 资源类型枚举。 + + + VIDEO: 视频。 + + ARTICLE: 专栏。 + + DYNAMIC_DRAW: 画册。 + + DYNAMIC: 动态(画册也属于动态的一种,只不过画册还有一个专门的 ID)。 + + AUDIO:音频。 + + AUDIO_LIST:歌单。 + + CHEESE: 课程 + + BLACK_ROOM: 小黑屋 + + MANGA: 漫画 + """ + + VIDEO = 1 + ARTICLE = 12 + DYNAMIC_DRAW = 11 + DYNAMIC = 17 + AUDIO = 14 + AUDIO_LIST = 19 + CHEESE = 33 + BLACK_ROOM = 6 + MANGA = 22 + + +class OrderType(Enum): + """ + 评论排序方式枚举。 + + + LIKE:按点赞数倒序。 + + TIME:按发布时间倒序。 + """ + + LIKE = 2 + TIME = 0 + + +class ReportReason(Enum): + """ + 举报类型枚举 + + + OTHER: 其他 + + SPAM_AD: 垃圾广告 + + PORNOGRAPHY: 色情 + + FLOOD: 刷屏 + + PROVOCATION: 引战 + + SPOILER: 剧透 + + POLITICS: 政治 + + PERSONAL_ATTACK: 人身攻击 + + IRRELEVANT_CONTENT: 内容不相关 + + ILLEGAL: 违法违规 + + VULGAR: 低俗 + + ILLEGAL_WEBSITE: 非法网站 + + GAMBLING_FRAUD: 赌博诈骗 + + SPREADING_FALSE_INFORMATION: 传播不实信息 + + INCITING_INFORMATION: 怂恿教唆信息 + + PRIVACY_VIOLATION: 侵犯隐私 + + FLOOR_TAKING: 抢楼 + + INAPPROPRIATE_CONTENT_FOR_MINORS: 青少年不良信息 + """ + + OTHER = 0 + SPAM_AD = 1 + PORNOGRAPHY = 2 + FLOOD = 3 + PROVOCATION = 4 + SPOILER = 5 + POLITICS = 6 + PERSONAL_ATTACK = 7 + IRRELEVANT_CONTENT = 8 + ILLEGAL = 9 + VULGAR = 10 + ILLEGAL_WEBSITE = 11 + GAMBLING_FRAUD = 12 + SPREADING_FALSE_INFORMATION = 13 + INCITING_INFORMATION = 14 + PRIVACY_VIOLATION = 15 + FLOOR_TAKING = 16 + INAPPROPRIATE_CONTENT_FOR_MINORS = 17 + + +class Comment: + """ + 对单条评论的相关操作。 + + Attributes: + credential (Credential): 凭据类 + """ + + def __init__( + self, + oid: int, + type_: CommentResourceType, + rpid: int, + credential: Union[Credential, None] = None, + ): + """ + Args: + oid (int) : 评论所在资源 ID。 + + type_ (ResourceType): 评论所在资源类型枚举。 + + rpid (int) : 评论 ID。 + + credential (Credential) : 凭据类. Defaults to None. + """ + self.__oid = oid + self.__rpid = rpid + self.__type = type_ + self.credential = credential if credential else Credential() + + def __get_data(self, status: bool) -> dict: + """ + 获取通用请求载荷。 + + Args: + status (bool): 状态。 + + Returns: + dict: 请求载荷数据。 + """ + return { + "oid": self.__oid, + "type": self.__type.value, + "rpid": self.__rpid, + "action": 1 if status else 0, + } + + def get_rpid(self) -> int: + return self.__rpid + + def get_type(self) -> CommentResourceType: + return self.__type + + def get_oid(self) -> int: + return self.__oid + + async def like(self, status: bool = True) -> dict: + """ + 点赞评论。 + + Args: + status (bool, optional): 状态, Defaults to True. + + Returns: + dict: 调用 API 返回的结果 + """ + + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["comment"]["like"] + return ( + await Api(**api, credential=self.credential) + .update_data(**self.__get_data(status)) + .result + ) + + async def hate(self, status: bool = True) -> dict: + """ + 点踩评论。 + + Args: + status (bool, optional): 状态, Defaults to True. + + Returns: + dict: 调用 API 返回的结果 + """ + + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["comment"]["hate"] + return ( + await Api(**api, credential=self.credential) + .update_data(**self.__get_data(status)) + .result + ) + + async def pin(self, status: bool = True) -> dict: + """ + 置顶评论。 + + Args: + status (bool, optional): 状态, Defaults to True. + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["comment"]["pin"] + return ( + await Api(**api, credential=self.credential) + .update_data(**self.__get_data(status)) + .result + ) + + async def delete(self) -> dict: + """ + 删除评论。 + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["comment"]["del"] + data = self.__get_data(True) + del data["action"] + return await Api(**api, credential=self.credential).update_data(**data).result + + async def get_sub_comments(self, page_index: int = 1) -> dict: + """ + 获取子评论。即评论下的评论。 + + Args: + page_index (int, optional): 页码索引,从 1 开始。Defaults to 1. + + Returns: + dict: 调用 API 返回的结果 + """ + if page_index <= 0: + raise ArgsException("page_index 必须大于或等于 1") + + api = API["comment"]["sub_reply"] + params = { + "pn": page_index, + "ps": 10, + "type": self.__type.value, + "oid": self.__oid, + "root": self.__rpid, + } + + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def report( + self, report_reason: ReportReason, content: Optional[str] = None + ) -> dict: + """ + 举报评论 + + Args: + report_reason (ReportReason): 举报类型枚举 + + content (str, optional): 其他举报备注内容仅 reason=ReportReason.OTHER 可用且不能为 None. + + Error Code: + 0: 成功 + -101: 账号未登录 + -102: 账号被封停 + -111: csrf校验失败 + -400: 请求错误 + -403: 权限不足 + -404: 无此项 + -500: 服务器错误 + -509: 请求过于频繁 + 12002: 评论区已关闭 + 12006: 没有该评论 + 12008: 已经举报过了 + 12009: 评论主体的type不合法 + 12019: 举报过于频繁 + 12077: 举报理由过长或过短 + """ + + self.credential.raise_for_no_sessdata() + + api = API["comment"]["report"] + if content is not None and report_reason != ReportReason.OTHER: + raise ArgsException("content 只能在 report_reason=ReportReason.OTHER 时使用") + elif content is None and report_reason == ReportReason.OTHER: + raise ArgsException("report_reason=ReportReason.OTHER 时 content 不能为空") + data = { + "oid": self.__oid, + "type": self.__type.value, + "rpid": self.__rpid, + "reason": report_reason.value, + "content": content, + } + return await Api(**api, credential=self.credential).update_data(**data).result + + +async def send_comment( + text: str, + oid: int, + type_: CommentResourceType, + root: Union[int, None] = None, + parent: Union[int, None] = None, + credential: Union[None, Credential] = None, +) -> dict: + """ + 通用发送评论 API。 + + 说明 `root` 和 `parent`,假设评论的是视频,常见的评论有三种情况: + + 1. 只在视频下面发送评论:root=None, parent=None; + 2. 回复视频下面的评论:root=评论 ID, parent=None; + 3. 回复视频下面的评论中的评论:root=在哪条评论下评论的 ID, parent=回复哪条评论的 ID。 + + 当 root 为空时,parent 必须为空。 + + Args: + text (str) : 评论内容。 + + oid (str) : 资源 ID。 + + type_ (CommentsResourceType) : 资源类型枚举。 + + root (int, optional): 根评论 ID, Defaults to None. + + parent (int, optional): 父评论 ID, Defaults to None. + + credential (Credential) : 凭据 + + Returns: + dict: 调用 API 返回的结果 + """ + if credential is None: + credential = Credential() + + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + + data = { + "oid": oid, + "type": type_.value, + "message": text, + "plat": 1, + } + + if root is None and parent is None: + # 直接回复资源 + pass + elif root is not None and parent is None: + # 回复资源下面的评论 + data["root"] = root + data["parent"] = root + elif root is not None and parent is not None: + # 回复资源下面的评论的评论 + data["root"] = root + data["parent"] = parent + else: + # root=None 时,parent 不得设置 + raise ArgsException("root=None 时,parent 不得设置") + + api = API["comment"]["send"] + return await Api(**api, credential=credential).update_data(**data).result + + +async def get_comments( + oid: int, + type_: CommentResourceType, + page_index: int = 1, + order: OrderType = OrderType.TIME, + credential: Union[Credential, None] = None, +) -> dict: + """ + 获取资源评论列表。 + + 第二页以及往后需要提供 `credential` 参数。 + + Args: + oid (int) : 资源 ID。 + + type_ (CommentsResourceType) : 资源类枚举。 + + page_index (int, optional) : 页码. Defaults to 1. + + order (OrderType, optional) : 排序方式枚举. Defaults to OrderType.TIME. + + credential (Credential, optional): 凭据。Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + if page_index <= 0: + raise ArgsException("page_index 必须大于或等于 1") + + api = API["comment"]["get"] + params = {"pn": page_index, "type": type_.value, "oid": oid, "sort": order.value} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_comments_lazy( + oid: int, + type_: CommentResourceType, + # pagination_str: str = "", + pn: int = 1, + ps: int = 20, + order: OrderType = OrderType.TIME, + credential: Union[Credential, None] = None, +) -> dict: + """ + 新版获取资源评论列表。 + + 第二次以及往后需要提供 `credential` 参数。 + + Args: + oid (int) : 资源 ID。 + + type_ (CommentsResourceType) : 资源类枚举。 + + pagination_str (str, optional) : 分页依据 Defaults to `{"offset":""}`. 弃用 #658 + + pn (int, optional) : 页码. Defaults to 1. + + ps (int, optional) : 每页数量. Defaults to 20. + + order (OrderType, optional) : 排序方式枚举. Defaults to OrderType.TIME. + + credential (Credential, optional): 凭据。Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["comment"]["reply_by_session_id"] + params = { + "oid": oid, + "type": type_.value, + "mode": order.value, + "next": pn - 1, + "ps": ps, + } + return await Api(**api, credential=credential).update_params(**params).result diff --git a/bilibili_api/creative_center.py b/bilibili_api/creative_center.py new file mode 100644 index 0000000000000000000000000000000000000000..b26314bf2de2231f5d8079c15945f85c6c8dab4c --- /dev/null +++ b/bilibili_api/creative_center.py @@ -0,0 +1,930 @@ +""" +bilibili_api.creative_center + +创作中心相关。 + +务必携带 Credential 信息,否则无法获取到数据。 +""" + +from enum import Enum +from typing import List, Union, Optional +from datetime import datetime + +from .video_zone import VideoZoneTypes +from .utils.utils import get_api +from .utils.credential import Credential +from .utils.network import Api + +API = get_api("creative_center") + + +class GraphPeriod(Enum): + """ + 统计图表的时间段。 + + + YESTERDAY: 昨天 + + WEEK: 近一周 + + MONTH: 近一月 + + THREE_MONTH: 近三月 + + TOTAL: 历史累计 + """ + + YESTERDAY = -1 + WEEK = 0 + MONTH = 1 + THREE_MONTH = 2 + TOTAL = 3 + + +class FanGraphPeriod(Enum): + """ + 粉丝统计图表的时间段。 + + + YESTERDAY: 昨天 + + WEEK: 近一周 + + MONTH: 近一月 + + THREE_MONTH: 近三月 + """ + + YESTERDAY = -1 + WEEK = 0 + MONTH = 1 + THREE_MONTH = 2 + + +class GraphType(Enum): + """ + 统计图表的类型。 + + + PLAY: 播放量 + + VISITOR: 访问量 + + FAN: 粉丝数 + + LIKE: 点赞数 + + FAV: 收藏数 + + SHARE: 分享数 + + COMMENT: 评论数 + + DAMKU: 弹幕数 + + COIN: 投币数 + + ELEC: 充电数 + """ + + PLAY = "play" + VISITOR = "visitor" + FAN = "fan" + LIKE = "like" + FAV = "fav" + SHARE = "share" + COMMENT = "comment" + DAMKU = "dm" + COIN = "coin" + ELEC = "elec" + + +class FanGraphType(Enum): + """ + 粉丝统计图表的类型。 + + + ALL_FANS: 粉丝总量 + + FAN: 新增粉丝 + + FOLLOW: 新增关注 + + UNFOLLOW: 取消关注 + """ + + ALL_FANS = "all_fans" + NEW_FANS = "fan" + FOLLOW = "follow" + UNFOLLOW = "unfollow" + + +class ArticleInfoType(Enum): + """ + 文章统计信息的类型。 + + + READ: 阅读 + + COMMENT: 评论 + + SHARE: 分享 + + COIN: 投币 + + FAV: 收藏 + + LIKE: 点赞 + """ + + READ = 1 + COMMENT = 2 + SHARE = 3 + COIN = 4 + FAV = 5 + LIKE = 6 + + +class Copyright(Enum): + """ + 稿件播放完成率对比的版权类型。 + + + ALL: 全部 + + ORIGINAL: 原创 + + REPRINT: 转载 + """ + + ALL = 0 + ORIGINAL = 1 + REPRINT = 2 + + +class UploadManagerOrder(Enum): + """ + 内容管理排序字段。 + + + CLICK: 点击 + + STOW: 收藏 + + SENDDATE: 上传日期 + + DM_COUNT: 弹幕数量 + + COMMENT_COUNT: 评论数量 + """ + + CLICK = "click" + STOW = "stow" + SENDDATE = "senddate" + DM_COUNT = "dm_count" + COMMENT_COUNT = "scores" # wtf the scores means + + +class UploadManagerStatus(Enum): + """ + 内容管理稿件状态字段。 + + + ALL: 全部稿件 + + PUBED: 已通过 + + IS_PUBING: 进行中 + + NOT_PUBED: 未通过 + """ + + ALL = "is_pubing,pubed,not_pubed" + PUBED = "pubed" + IS_PUBING = "is_pubing" + NOT_PUBED = "not_pubed" + + +class UploadManagerSort(Enum): + """ + 内容管理文章排序字段。 + + + CREATED_TIME: 创建日期 + + LIKE: 点赞 + + COMMENT: 评论 + + FAV: 收藏 + + COIN: 投币 + """ + + CREATED_TIME = 1 + LIKE = 2 + COMMENT = 3 + FAV = 5 + COIN = 6 + + +class UploadManagerArticleStatus(Enum): + """ + 内容管理文章状态字段。 + + + ALL: 全部稿件 + + PUBED: 已通过 + + IS_PUBING: 进行中 + + NOT_PUBED: 未通过 + """ + + ALL = 0 + PUBED = 2 + IS_PUBING = 1 + NOT_PUBED = 3 + + +class ArchiveType(Enum): + """ + 评论管理中的稿件类型。 + + + VIDEO: 视频 + + ARTICLE: 文章 + + AUDIO: 音频 + """ + + VIDEO = 1 # 视频 + ARTICLE = 12 # 文章 + AUDIO = 14 # 音频 + + +class CommentManagerOrder(Enum): + """ + 评论管理中的排序字段。 + + + RECENTLY: 最近 + + LIKE: 点赞 + + REPLY: 回复 + """ + + RECENTLY = 1 # 最近 + LIKE = 2 # 点赞 + REPLY = 3 # 回复 + + +class DanmakuMode(Enum): + """ + 弹幕模式。 + + + ROLL: 滚动 + + BOTTOM: 底端 + + TOP: 顶端 + + REVERSE: 逆向 + + ADVANCED: 高级 + + CODE: 代码 + + BAS: BAS 补充注释 + """ + + ROLL = 1 # 滚动 + BOTTOM = 4 # 底端 + TOP = 5 # 顶端 + REVERSE = 6 # 逆向 + ADVANCED = 7 # 高级 + CODE = 8 # 代码 + BAS = 9 # BAS + + +class DanmakuPool(Enum): + """ + 子弹幕池类型。 + + + NORMAL: 普通 + + SUBTITLE: 字幕 + + SPECIAL: 特殊 + """ + + NORMAL = 0 # 普通 + SUBTITLE = 1 # 字幕 + SPECIAL = 2 # 特殊 + +class DanmakuOrder(Enum): + """ + 弹幕排序依据 + + + CTIME: 发送时间 + + LIKE_COUNT: 点赞数 + """ + + CTIME = "ctime" + LIKE_COUNT = "like_count" + + +class DanmakuSort(Enum): + """ + 弹幕排序顺序 + + + DESC: 降序 + + ASC: 升序 + """ + + DESC = "desc" + ASC = "asc" + + +class DanmakuType(Enum): + """ + 弹幕筛选类型 + + + ALL: 全部 + + PROTECT: 保护弹幕 + """ + + ALL = 0 + PROTECT = 2 + + +async def get_compare(credential: Credential) -> dict: + """ + 获取对比数据。 + + Args: + credentials (Credential): Credential 凭据。 + + Returns: + dict: 视频对比数据。 + """ + api = API["overview"]["compare"] + return await Api(**api, credential=credential).result + + +async def get_graph( + credential: Credential, + period: GraphPeriod = GraphPeriod.WEEK, + graph_type: GraphType = GraphType.PLAY, +) -> dict: + """ + 获取统计图表数据。 + + Args: + credentials (Credential): Credential 凭据。 + + period (GraphPeriod): 时间段。 + + graph_type (GraphType): 图表类型。 + + Returns: + dict: 视频统计图表数据。 + """ + api = API["overview"]["graph"] + params = { + "period": period.value, + "s_locale": "zh_CN", + "type": graph_type.value, + } + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_overview( + credential: Credential, period: GraphPeriod = GraphPeriod.WEEK +) -> dict: + """ + 获取概览数据。 + + Args: + credentials (Credential): Credential 凭据。 + + period (GraphPeriod): 时间段。 + + Returns: + dict: 视频概览数据。 + """ + api = API["overview"]["num"] + # 不知道 tab 的作用是什么,但是不传会报错 + params = {"period": period.value, "s_locale": "zh_CN", "tab": 0} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_video_survey(credential: Credential) -> dict: + """ + 获取视频各分区中占比排行。 + + Args: + credentials (Credential): Credential 凭据。 + + Returns: + dict: 视频分区排行数据。 + """ + api = API["data-up"]["video"]["survey"] + params = {"type": 1} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_video_playanalysis( + credential: Credential, copyright: Copyright = Copyright.ALL +) -> dict: + """ + 获取稿件播放完成率对比。 + + Args: + credentials (Credential): Credential 凭据。 + + copyright (Copyright): 版权类型。 + + Returns: + dict: 稿件播放完成率对比数据。 + """ + api = API["data-up"]["video"]["playanalysis"] + params = {"copyright": copyright.value} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_video_source(credential: Credential) -> dict: + """ + 获取稿件播放来源分布。 + + Args: + credentials (Credential): Credential 凭据。 + + Returns: + dict: 视频来源分布数据。 + """ + api = API["data-up"]["video"]["source"] + params = {"s_locale": "zh_CN"} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_fan_overview( + credential: Credential, period: FanGraphPeriod = FanGraphPeriod.WEEK +) -> dict: + """ + 获取粉丝概览数据。 + + Args: + credentials (Credential): Credential 凭据。 + + period (FanGraphPeriod): 时间段。 + + Returns: + dict: 粉丝概览数据。 + """ + api = API["data-up"]["fan"]["overview"] + params = {"period": period.value} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_fan_graph( + credential: Credential, + period: FanGraphPeriod = FanGraphPeriod.WEEK, + graph_type: FanGraphType = FanGraphType.ALL_FANS, +) -> dict: + """ + 获取粉丝图表数据。 + + Args: + credentials (Credential): Credential 凭据。 + + period (FanGraphPeriod): 时间段。 + + graph_type (FanGraphType): 图表类型。 + + Returns: + dict: 粉丝图表数据。 + """ + api = API["data-up"]["fan"]["graph"] + params = {"period": period.value, "type": graph_type.value} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_article_overview(credential: Credential) -> dict: + """ + 获取文章概览数据。 + + Args: + credentials (Credential): Credential 凭据。 + + Returns: + dict: 文章概览数据。 + """ + api = API["data-up"]["article"]["overview"] + return await Api(**api, credential=credential).result + + +async def get_article_graph( + credential: Credential, graph_type: ArticleInfoType = ArticleInfoType.READ +) -> dict: + """ + 获取文章图表数据。 + + Args: + credentials (Credential): Credential 凭据。 + + graph_type (ArticleInfoType): 图表类型。 + + Returns: + dict: 文章图表数据。 + """ + + api = API["data-up"]["article"]["graph"] + params = {"type": graph_type.value} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_article_rank( + credential: Credential, rank_type: ArticleInfoType = ArticleInfoType.READ +) -> dict: + """ + 获取文章排行数据。 + + Args: + credentials (Credential): Credential 凭据。 + + rank_type (ArticleInfoType): 排行依据。 + + Returns: + dict: 文章排行数据。 + """ + + api = API["data-up"]["article"]["rank"] + params = {"type": rank_type.value} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_article_source(credential: Credential) -> dict: + """ + 获取文章阅读终端数据 + + Args: + credentials (Credential): Credential 凭据。 + + Returns: + dict: 文章阅读终端数据。 + """ + + api = API["data-up"]["article"]["source"] + return await Api(**api, credential=credential).result + + +""" +upload manager + +https://member.bilibili.com/platform/upload-manager +""" + + +async def get_video_draft_upload_manager_info(credential: Credential) -> dict: + """ + 获取内容管理视频草稿信息 + + Args: + credentials (Credential): Credential 凭据。 + + Returns: + dict: 内容管理视频草稿信息。 + """ + + api = API["upload-manager"]["video_draft"] + return await Api(**api, credential=credential).result + + +async def get_video_upload_manager_info( + credential: Credential, + is_interative: bool = False, + pn: int = 1, + ps: int = 10, + order: UploadManagerOrder = UploadManagerOrder.CLICK, + tid: Union[VideoZoneTypes, None, int] = None, + status: UploadManagerStatus = UploadManagerStatus.ALL, +) -> dict: + """ + 获取内容管理视频信息 + + Args: + credentials (Credential): Credential 凭据。 + + is_interative (bool): 是否为互动视频 + + pn (int): 页码 + + ps (int): 每页项数 + + tid: (VideoZoneTypes, None, int): 分区 + + status (UploadManagerStatus): 稿件状态 + + order (UploadManagerOrder): 稿件排序 + + Returns: + dict: 内容管理视频信息。 + """ + params = { + "pn": pn, + "ps": ps, + "coop": 1, + "status": status.value, + "order": order.value, + "interactive": 2 if is_interative else 1, + "tid": tid.value if isinstance(tid, Enum) else tid, + } + api = API["upload-manager"]["video"] + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_article_upload_manager_info( + credential: Credential, + status: UploadManagerArticleStatus = UploadManagerArticleStatus.ALL, + sort: UploadManagerSort = UploadManagerSort.CREATED_TIME, + pn: int = 1, +) -> dict: + """ + 获取内容管理文章信息 + + Args: + credentials (Credential): Credential 凭据。 + + pn (int): 页码 + + status (UploadManagerArticleStatus): 稿件状态 + + sort (UploadManagerSort): 稿件排序 + + Returns: + dict: 内容管理文章信息。 + """ + + params = {"pn": pn, "group": status.value, "sort": sort.value, "mobi_app": "pc"} + api = API["upload-manager"]["article"] + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_article_list_upload_manager_info(credential: Credential) -> dict: + """ + 获取内容管理文章信息 + + Args: + credentials (Credential): Credential 凭据。 + + Returns: + dict: 内容管理文集信息。 + """ + + api = API["upload-manager"]["article_list"] + return await Api(**api, credential=credential).result + + +""" +comment manager + +https://member.bilibili.com/platform/comment +""" + + +async def get_comments( + credential: Credential, + oid: Optional[int] = None, + keyword: Optional[str] = None, + archive_type: ArchiveType = ArchiveType.VIDEO, + order: CommentManagerOrder = CommentManagerOrder.RECENTLY, + filter: int = -1, + pn: int = 1, + ps: int = 10, + charge_plus_filter: bool = False, +) -> dict: + """ + 获取评论 + + Args: + credentials (Credential): Credential 凭据。 + + oid (Optional, int): 指定稿件 + + keyword (Optional, str): 关键词 + + archive_type (ArchiveType): 稿件类型 + + order (CommentManagerOrder): 排序字段 + + filter (int): 筛选器,作用未知 + + pn (int): 页码 + + ps (int): 每页项数 + + charge_plus_filter (bool): charge_plus_filter + + Returns: + dict: 评论管理评论信息。 + """ + + params = { + "order": order.value, + "filter": filter, + "type": archive_type.value, + "pn": pn, + "ps": ps, + "charge_plus_filter": charge_plus_filter, + } + + if keyword is not None: + params["keyword"] = keyword + if oid is not None: + params["oid"] = oid + + api = API["comment-manager"]["fulllist"] + return await Api(**api, credential=credential).update_params(**params).result + + +async def del_comments( + credential: Credential, + oid: Union[int, List[int]], + rpid: Union[int, List[int]], + archive_type: ArchiveType = ArchiveType.VIDEO, +): + """ + 删除评论 + + 每个评论对应一个 oid + + Args: + credentials (Credential): Credential 凭据。 + + oid (int, lsit): 指定稿件 + + rpid (int, lsit): 指定评论 + + archive_type (ArchiveType): 稿件类型 + """ + data = { + "oid": ",".join(oid) if isinstance(oid, list) else oid, + "type": archive_type.value, + "rpid": ",".join(rpid) if isinstance(rpid, list) else rpid, + "jsonp": "jsonp", + "csrf": credential.bili_jct, + } + + api = API["comment-manager"]["del"] + return await Api(**api, credential=credential).update_data(**data).result + + +""" +danmaku manager + +https://member.bilibili.com/platform/inter-active/danmu +""" + + +async def get_recently_danmakus( + credential: Credential, pn: int = 1, ps: int = 50 +) -> dict: + """ + 最近弹幕 + + Args: + credential (Credential): Credential 凭据。 + + pn (int): 页码。 + + ps (int): 每页项数。 + + Returns: + dict: 弹幕管理最近弹幕信息。 + """ + + params = {"pn": pn, "ps": ps} + + api = API["danmaku-manager"]["recent"] + return await Api(**api, credential=credential).update_params(**params).result + + + + + +async def get_danmakus( + credential: Credential, + oid: int, + select_type: DanmakuType = DanmakuType.ALL, + archive_type: ArchiveType = ArchiveType.VIDEO, + mids: Optional[Union[int, List[int]]] = None, + keyword: Optional[str] = None, + progress_from: Optional[int] = None, + progress_to: Optional[int] = None, + ctime_from: Optional[datetime] = None, + ctime_to: Optional[datetime] = None, + modes: Optional[Union[DanmakuMode, List[DanmakuMode]]] = None, + pools: Optional[Union[DanmakuPool, List[DanmakuPool]]] = None, + attrs=None, # 未知参数,我在高级筛选里面找不到 + order: DanmakuOrder = DanmakuOrder.CTIME, + sort: DanmakuSort = DanmakuSort.DESC, + pn: int = 1, + ps: int = 50, + cp_filter: bool = False, +) -> dict: + """ + 弹幕搜索 + + Args: + credential (Credential): Credential 凭据 + + oid (int): 稿件oid,用逗号分隔 + + select_type (DanmakuType): 弹幕类型 + + archive_type (ArchiveType): 稿件类型 + + mids (list[int], int): 用户mids,用逗号分隔或者直接 int + + keyword (str): 关键词 + + progress_from (int): 进度开始 + + progress_to (int): 进度结束 + + ctime_from (datetime.datetime): 创建时间起始 + + ctime_to (datetime.datetime): 创建时间结束 + + modes (DanmakuMode): 弹幕模式。 + + pool (DanmakuPool): 弹幕池 + + attrs (Unknown): 弹幕属性,未知参数 + + order (DanmakuOrder): 排序字段 + + sort (DanmakuSort): 排序方式 + + pn (int): 页码。 + + ps (int): 每页项数。 + + cp_filter (bool): 是否过滤CP弹幕。未知参数,默认为 False + + Returns: + dict: 弹幕搜索结果 + """ + params = { + "oid": oid, + "type": archive_type.value, + "mids": ",".join(mids) if isinstance(mids, list) else mids, + "select_type": select_type.value, + "keyword": keyword, + "progress_from": progress_from, + "progress_to": progress_to, + "ctime_from": ctime_from.strftime("%d-%m-%Y %H:%M:%S") + if ctime_from is not None + else None, + "ctime_to": ctime_to.strftime("%d-%m-%Y %H:%M:%S") + if ctime_to is not None + else None, + "modes": ( + ",".join([mode.value for mode in modes]) + if isinstance(modes, list) + else modes.value + ) + if modes is not None + else None, + "pool": ( + ",".join([pool.value for pool in pools]) + if isinstance(pools, list) + else pools.value + ) + if pools is not None + else None, + "attrs": attrs, + "order": order.value, + "sort": sort.value, + "pn": pn, + "ps": ps, + "cp_filter": cp_filter, + } + + api = API["danmaku-manager"]["search"] + return await Api(**api, credential=credential).update_params(**params).result + + +async def del_danmaku( + credential: Credential, oid: int, dmids: Union[int, List[int]] +) -> dict: + """ + 删除弹幕 + + Args: + oid (int): 稿件 oid + + dmids (list[int], int): 弹幕 id,可以传入列表和 int + """ + + return await edit_danmaku_state(credential=credential, oid=oid, dmids=dmids, state=1) + + +async def edit_danmaku_state( + credential: Credential, + oid: int, + dmids: Union[int, List[int]], + state: Optional[int] = None, +) -> dict: + """ + 操作弹幕状态 + + Args: + oid (int): 稿件 oid + + dmids (list[int], int): 弹幕 id,可以传入列表和 int + + state (int, Optional): 弹幕状态 1 删除 2 保护 3 取消保护 + + Returns: + dict: API 返回信息 + """ + data = { + "type": 1, + "oid": oid, + "dmids": ",".join(dmids) if isinstance(dmids, list) else dmids, + "state": state, + } + + api = API["danmaku-manager"]["state"] + return await Api(**api, credential=credential).update_data(**data).result + + +async def edit_danmaku_pool( + credential: Credential, + oid: int, + dmids: Union[int, List[int]], + is_subtitle: bool = True, +) -> dict: + """ + 操作弹幕池 + + Args: + oid (int): 稿件 oid + + dmids (list[int], int): 弹幕 id,可以传入列表和 int + + is_subtitle (bool): 是否为字幕 + + Returns: + dict: API 返回信息 + """ + data = { + "type": 1, + "oid": oid, + "dmids": ",".join(dmids) if isinstance(dmids, list) else dmids, + "pool": 1 if is_subtitle else 0, + } + + api = API["danmaku-manager"]["pool"] + return await Api(**api, credential=credential).update_data(**data).result diff --git a/bilibili_api/data/api/README.md b/bilibili_api/data/api/README.md new file mode 100644 index 0000000000000000000000000000000000000000..19be782055c6066c28bccd426ca8664b868872f3 --- /dev/null +++ b/bilibili_api/data/api/README.md @@ -0,0 +1,5 @@ +这里是储存了目前为止收集到的所有 API 的地方。 + +由于使用通用的 JSON 格式存储,你可以在其他语言中使用这些文件,方便移植。 + +你只需要标注一下该项目地址即可。无需通知作者。GPL 条约仍然有效。 diff --git a/bilibili_api/data/api/album.json b/bilibili_api/data/api/album.json new file mode 100644 index 0000000000000000000000000000000000000000..65182ad2d4b0f5f98787cd937a73e4157dca4191 --- /dev/null +++ b/bilibili_api/data/api/album.json @@ -0,0 +1,85 @@ +{ + "info": { + "detail": { + "url": "https://api.vc.bilibili.com/link_draw/v1/doc/detail", + "method": "GET", + "verify": false, + "params": { + "doc_id": "int: 相簿 id" + }, + "comment": "获取相簿详细信息" + }, + "homepage_painter_albums_list": { + "url": "https://api.vc.bilibili.com/link_draw/v2/Doc/index", + "method": "GET", + "verify": false, + "params": { + "type": "str: recommend 推荐,hot 最热,new 最新", + "page_num": "int: 页码", + "page_size": "int: 每页数据大小" + }, + "comment": "获取首页画友相簿列表" + }, + "homepage_photos_albums_list": { + "url": "https://api.vc.bilibili.com/link_draw/v2/Photo/index", + "method": "GET", + "verify": false, + "params": { + "type":"str: recommend 推荐", + "page_num": "int: 页码", + "page_size":"int: 每页数据大小" + } + }, + "homepage_recommended_painters": { + "url": "https://api.vc.bilibili.com/link_draw/v2/Doc/drawer", + "method": "GET", + "verify": false, + "params": { + "num": "int: 请求数量" + }, + "comment": "获取首页推荐画友" + }, + "homepage_recommended_photos_uppers": { + "url": "https://api.vc.bilibili.com/link_draw/v2/Photo/uper", + "method": "GET", + "verify": false, + "params": { + "num": "int: 请求数量" + }, + "comment": "获取首页推荐摄影 up" + }, + "painter_list": { + "url": "https://api.vc.bilibili.com/link_draw/v2/Doc/index", + "method": "GET", + "verify": false, + "params": { + "type":"str: recommend 推荐,new 最新", + "page_num":"int: 页码", + "page_size":"int: 每页数据大小" + }, + "comment": "获取画友列表" + }, + "photos_list": { + "url": "https://api.vc.bilibili.com/link_draw/v2/Photo/index", + "method": "GET", + "verify": false, + "params": { + "type": "str: recommend 推荐", + "page_num": "int: 页码", + "page_size": "int: 每页数据大小" + }, + "comment": "获取摄影列表" + }, + "user_albums": { + "url": "https://api.vc.bilibili.com/link_draw/v1/doc/others", + "method": "GET", + "params": { + "biz": "int: 分区,0 全部,1 画友,2 摄影", + "poster_uid": "int: uid", + "page_num": "int: 页码", + "page_size":"int: 每页数据大小" + }, + "comment": "获取指定用户的投稿的相簿" + } + } +} diff --git a/bilibili_api/data/api/app.json b/bilibili_api/data/api/app.json new file mode 100644 index 0000000000000000000000000000000000000000..87611d8fb620df59bb353cb6ed63d9218cc0e42f --- /dev/null +++ b/bilibili_api/data/api/app.json @@ -0,0 +1,29 @@ +{ + "splash": { + "list": { + "url": "https://app.bilibili.com/x/v2/splash/list", + "method": "GET", + "verify": false, + "params": { + "mobi_app": "str: android, iphone, ipad", + "platform": "str: android, ios", + "height": "int: 屏幕高度", + "width": "int: 屏幕宽度", + "birth": "str: 生日日期(四位数,例 0101)" + }, + "comment": "获取开屏启动画面" + }, + "brand": { + "url": "https://app.bilibili.com/x/v2/splash/brand/list", + "method": "GET", + "verify": false, + "params": { + "mobi_app": "str: android, iphone, ipad", + "platform": "str: android, ios", + "screen_height": "int: 屏幕高度", + "screen_width": "int: 屏幕宽度" + }, + "comment": "获取特殊开屏启动画面" + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/article-category.json b/bilibili_api/data/api/article-category.json new file mode 100644 index 0000000000000000000000000000000000000000..94f74db64dd7e5dcdcaea69e7844f077469d252c --- /dev/null +++ b/bilibili_api/data/api/article-category.json @@ -0,0 +1,15 @@ +{ + "info": { + "recommends": { + "url": "https://api.bilibili.com/x/article/recommends", + "method": "GET", + "params": { + "cid": "int: 专栏分类 id", + "sort": "int: 排序方式. 默认 0, 投稿时间 1, 点赞数 2, 评论数 3, 收藏数 4", + "pn": "不想解释了", + "ps": "翻译下其他的 json 文件就知道这个参数是什么意思了" + }, + "comment": "获取推荐文章" + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/article.json b/bilibili_api/data/api/article.json new file mode 100644 index 0000000000000000000000000000000000000000..d21aae950a9e58ae62739872d44799c89ac5320d --- /dev/null +++ b/bilibili_api/data/api/article.json @@ -0,0 +1,72 @@ +{ + "info": { + "view": { + "url": "https://api.bilibili.com/x/article/viewinfo", + "method": "GET", + "verify": false, + "params": { + "id": "int: cv 号" + }, + "comment": "专栏信息数据" + }, + "list": { + "url": "https://api.bilibili.com/x/article/list/web/articles", + "method": "GET", + "verify": false, + "params": { + "id": "int: id" + }, + "comment": "获取文集文章列表" + }, + "rank": { + "url": "https://api.bilibili.com/x/article/rank/list", + "method": "GET", + "params": { + "cid": "int: 取值时间. 1 一个月, 2 一周, 3 前天, 4 昨天" + }, + "comment": "获取全站专栏排行榜" + } + }, + "operate": { + "like": { + "url": "https://api.bilibili.com/x/article/like", + "method": "POST", + "verify": true, + "params": { + "id": "int: cv 号", + "type": "int: 1 点赞 2 取消" + }, + "comment": "专栏点赞" + }, + "add_favorite": { + "url": "https://api.bilibili.com/x/article/favorites/add", + "method": "POST", + "verify": true, + "params": { + "id": "int: cv 号" + }, + "comment": "专栏收藏" + }, + "del_favorite": { + "url": "https://api.bilibili.com/x/article/favorites/del", + "method": "POST", + "verify": true, + "params": { + "id": "int: cv 号" + }, + "comment": "专栏取消收藏" + }, + "coin": { + "url": "https://api.bilibili.com/x/web-interface/coin/add", + "method": "POST", + "verify": true, + "params": { + "aid": "int: cv 号", + "multiply": "int: 硬币数量,目前只能是 1 个", + "upid": "int: up 主的 uid", + "avtype": "const int: 2" + }, + "comment": "专栏投币" + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/audio.json b/bilibili_api/data/api/audio.json new file mode 100644 index 0000000000000000000000000000000000000000..a90d1544eda45713155e0a5b196db680e9e0adbf --- /dev/null +++ b/bilibili_api/data/api/audio.json @@ -0,0 +1,136 @@ +{ + "audio_info": { + "audio_list": { + "url": "https://api.bilibili.com/x/mv/list?type=1&genre=&lang=&keyword=2&pn=1&ps=30", + "method": "GET", + "verify": false, + "params": { + "type": "排序方式. 1 最新, 2 最火", + "genre": "类型, 见 https://api.bilibili.com/x/mv/tag", + "lang": "语言, 见 https://api.bilibili.com/x/mv/tag", + "keyword": "关键字", + "pn": "int: 页码", + "ps":"const int: 每页项数" + } + }, + "homepage_recommend": { + "url": "https://api.bilibili.com/x/web-show/res/locs?pf=0&ids=3715,3721,3727,3729,3737,3739,3745,3747,3749,3751,3756", + "method": "GET", + "comment": "获取首页推荐" + }, + "info": { + "url": "https://www.bilibili.com/audio/music-service-c/web/song/info", + "method": "GET", + "verify": false, + "params": { + "sid": "int: 音频 au 号" + }, + "comment": "获取音频信息" + }, + "tag": { + "url": "https://www.bilibili.com/audio/music-service-c/web/tag/song", + "method": "GET", + "verify": false, + "params": { + "sid": "int: 音频 au 号" + }, + "comment": "获取音频 tag" + }, + "user": { + "url": "https://www.bilibili.com/audio/music-service-c/web/stat/user", + "method": "GET", + "verify": false, + "params": { + "uid": "int: 用户 UID" + }, + "comment": "获取用户数据(收听数,粉丝数等)" + }, + "download_url": { + "url": "https://www.bilibili.com/audio/music-service-c/web/url", + "method": "GET", + "verify": false, + "params": { + "sid": "int: 音频 au 号", + "privilege": "const int: 2", + "quality": "const int: 2" + }, + "comment": "获取音频文件下载链接,目前音质貌似不可控" + } + }, + "audio_operate": { + "coin": { + "url": "https://www.bilibili.com/audio/music-service-c/web/coin/add", + "method": "POST", + "verify": true, + "data": { + "sid": "int: 歌单 ID", + "multiply": "int: 硬币数量,最大 2" + }, + "comment": "投币" + } + }, + "list_info": { + "info": { + "url": "https://www.bilibili.com/audio/music-service-c/web/menu/info", + "method": "GET", + "verify": false, + "params": { + "sid": "int: 歌单 ID" + }, + "comment": "获取歌单信息" + }, + "tag": { + "url": "https://www.bilibili.com/audio/music-service-c/web/tag/menu", + "method": "GET", + "verify": false, + "params": { + "sid": "int: 歌单 ID" + }, + "comment": "获取歌单 tag" + }, + "song_list": { + "url": "https://www.bilibili.com/audio/music-service-c/web/song/of-menu", + "method": "GET", + "verify": false, + "params": { + "sid": "int: 歌单 ID", + "pn": "int: 页码", + "ps": "const int: 100" + }, + "comment": "获取歌单歌曲列表" + }, + "hot": { + "url": "https://www.bilibili.com/audio/music-service-c/web/menu/hit", + "method": "GET", + "verify": false, + "params": { + "pn": "int: 页码", + "ps": "const int: 100" + }, + "comment": "获取热门歌曲列表" + } + }, + "list_operate": { + "set_favorite": { + "url": "https://www.bilibili.com/audio/music-service-c/web/collect/menu", + "method": "POST", + "verify": true, + "data": { + "sid": "int: 歌单 ID" + }, + "comment": "收藏歌单" + }, + "del_favorite": { + "url": "https://www.bilibili.com/audio/music-service-c/web/collect/menu", + "method": "DELETE", + "verify": true, + "params": { + "sid": "int: 歌单 ID" + }, + "data": { + "csrf": "csrf" + }, + "comment": "取消收藏歌单" + } + } +} diff --git a/bilibili_api/data/api/audio_uploader.json b/bilibili_api/data/api/audio_uploader.json new file mode 100644 index 0000000000000000000000000000000000000000..03825cee048a3ae3a59b0cdcdd705cddd4ae156b --- /dev/null +++ b/bilibili_api/data/api/audio_uploader.json @@ -0,0 +1,120 @@ +{ + "preupload": { + "url": "https://member.bilibili.com/preupload", + "method": "GET", + "verify": true, + "params": { + "profile": "uga/bup", + "name": "str: 音频文件名(带后缀)", + "size": "int: 音频大小", + "r": "const str: upos", + "ssl": "const int: 0", + "version": "const str: 2.6.4", + "build": "const str: 2060400" + }, + "comment": "获取上传配置" + }, + "lrc": { + "url": "https://www.bilibili.com/audio/music-service/songs/lrc", + "method": "POST", + "verify": true, + "data": { + "song_id": "str: song_id", + "lrc": "str: lcr 字幕" + }, + "comment": "lrc 字幕上传" + }, + "submit_songs": { + "url": "https://www.bilibili.com/audio/music-service/compilation/commit_songs", + "method": "POST", + "verify": true, + "data": { + "lyric_url": "str: lrc 字幕链接", + "song_id": "int: song_id", + "avid": "str: 关联 av号", + "tid": "int: 关联 tid", + "cid": "int: 关联 cid", + "title": "str: 标题", + "member_with_type": "list: 歌曲信息", + "song_tags": "list: tags", + "mid": "int: up_uid" + }, + "comment": "提交音频" + }, + "submit_compilation": { + "url": "https://www.bilibili.com/audio/music-service/compilation/commit_compilation", + "method": "POST", + "verify": true, + "data": { + "cover_url": "str: cover url", + "intro": "str: 介绍", + "is_synch": "const: unknown", + "song_counts": "int: 歌曲总数", + "song_ids": "list: song_ids", + "dict_items": "list: 分类 {'type_id': 125,'type_name': '电子',}", + "title": "str: 标题" + }, + "comment": "提交音频合集" + }, + "submit_single_song": { + "url": "https://www.bilibili.com/audio/music-service/songs", + "method": "POST", + "verify": true, + "data": { + "lyric_url": "str: lrc 字幕链接", + "cover_url": "str: cover url", + "song_id": "int: song_id", + "mid": "int: up_uid", + "cr_type": "const: unknown", + "music_type_id": "int: ?", + "avid": "str: 关联 av号", + "tid": "int: 关联 tid", + "cid": "int: 关联 cid", + "title": "str: 标题", + "intro": "str: 介绍", + "member_with_type": [ + { + "m_type": 127, + "members": [{ "name": "str: up_name", "mid": "int: up_uid" }] + } + ], + "song_tags": [{ "tagName": "str: 标签" }], + "create_time": "float: 时间戳 %.3f", + "activity_id": "int: activity_id", + "is_bgm": "int: 是否为 bgm", + "source": "int: ?" + }, + "comment": "单音频提交" + }, + "image": { + "url": "https://www.bilibili.com/audio/music-service/songs/image", + "method": "POST", + "verify": false, + "no_csrf": true, + "comment": "提交封面" + }, + "compilation_categories": { + "url": "https://www.bilibili.com/audio/music-service/compilation/compilation_categories", + "method": "GET", + "verify": false, + "comment": "歌单分类" + }, + "get_video_by_title": { + "url": "https://www.bilibili.com/audio/music-service/users/getvideoinfo/bytitle", + "method": "GET", + "verify": true, + "params": { + "title": "str: 标题", + "pagesize": "int: pagesize" + }, + "comment": "获取关联视频" + }, + "get_upinfo": { + "url": "https://www.bilibili.com/audio/music-service/users/get_upinfo", + "method": "GET", + "params": { + "param": "str: up" + }, + "comment": "根据 UID / id 获取信息" + } +} diff --git a/bilibili_api/data/api/bangumi.json b/bilibili_api/data/api/bangumi.json new file mode 100644 index 0000000000000000000000000000000000000000..9e9bae47361a678e702e682e84157c2854964de3 --- /dev/null +++ b/bilibili_api/data/api/bangumi.json @@ -0,0 +1,152 @@ +{ + "info": { + "timeline": { + "url": "https://api.bilibili.com/pgc/web/timeline", + "method": "GET", + "verify": false, + "params": { + "types": "int: 类型, 1. 番剧, 3. 影视, 4. 国创", + "before": "几天前开始, 0~7", + "after": "几天后结束, 0~7" + } + }, + "index": { + "url": "https://api.bilibili.com/pgc/season/index/result", + "method": "GET", + "verify": false, + "params": { + "order": "int: 排序字段", + "sort": "int: 排序方式", + "page": "int: 页数", + "season_type": "番剧类型", + "pagesize": "int: 每页数量", + "type": "int: unknown" + } + }, + "meta": { + "url": "https://api.bilibili.com/pgc/review/user", + "method": "GET", + "verify": false, + "params": { + "media_id": "int: 番剧的 media_id(URL 中的/mdxxxx)" + }, + "comment": "获取番剧信息" + }, + "episodes_list": { + "url": "https://api.bilibili.com/pgc/web/season/section", + "method": "GET", + "verify": false, + "params": { + "season_id": "int: 番剧的 season_id" + }, + "comment": "获取番剧剧集列表" + }, + "season_status": { + "url": "https://api.bilibili.com/pgc/web/season/stat", + "method": "GET", + "verify": false, + "params": { + "season_id": "int: 番剧的 season_id" + }, + "comment": "获取番剧播放量,追番等信息" + }, + "short_comment": { + "url": "https://api.bilibili.com/pgc/review/short/list", + "method": "GET", + "verify": false, + "params": { + "media_id": "int: 番剧的 media_id", + "ps": "const int: 20", + "sort": "int: 排序方式 0 默认 1 按时间倒序", + "cursor": "int: 循环获取用,第一次调用本 API 返回中的 next 值" + }, + "comment": "获取番剧短评" + }, + "long_comment": { + "url": "https://api.bilibili.com/pgc/review/long/list", + "method": "GET", + "verify": false, + "params": { + "media_id": "int: 番剧的 media_id", + "ps": "const int: 20", + "sort": "int: 排序方式 0 默认 1 按时间倒序", + "cursor": "int: 循环获取用,第一次调用本 API 返回中的 next 值" + }, + "comment": "获取番剧长评" + }, + "relate_video": { + "url": "https://api.bilibili.com/x/web-interface/tag/top?pn=10&ps=24&tid=8583026", + "method": "GET", + "verify": false, + "params": { + "tid": "int: 频道 ID", + "ps": "const int: 20", + "sort": "int: 排序方式 0 默认 1 按时间倒序", + "cursor": "int: 循环获取用,第一次调用本 API 返回中的 next 值" + }, + "comment": "获取番剧长评" + }, + "collective_info": { + "url": "https://api.bilibili.com/pgc/view/web/season", + "method": "GET", + "verify": false, + "params": { + "season_id": "int: B 站每个剧集会对应一个唯一 ID" + }, + "comment": "获取一个剧集的全面概括信息" + }, + "collective_info_oversea": { + "url": "https://bangumi.bilibili.com/view/web_api/season", + "method": "GET", + "verify": false, + "params": { + "season_id": "int: B 站每个剧集会对应一个唯一 ID,适用于港澳台番剧和内陆番剧" + }, + "comment": "获取一个剧集的全面概括信息" + }, + "playurl": { + "url": "https://api.bilibili.com/pgc/player/web/playurl", + "method": "GET", + "verify": false, + "params": { + "avid": "int: av 号", + "cid": "int: 分 P 编号", + "qn": "int: 视频质量编号,最高 127", + "otype": "const str: json", + "fnval": "const int: 4048", + "platform": "int: 平台" + }, + "comment": "视频下载的信息,下载链接需要提供 headers 伪装浏览器请求(Referer 和 User-Agent)" + } + }, + "operate": { + "follow_add": { + "url": "https://api.bilibili.com/pgc/web/follow/add", + "method": "POST", + "verify": true, + "data": { + "season_id": "int: 番剧的 season_id" + }, + "comment": "追番" + }, + "follow_del": { + "url": "https://api.bilibili.com/pgc/web/follow/del", + "method": "POST", + "verify": true, + "data": { + "season_id": "int: 番剧的 season_id" + }, + "comment": "取消追番" + }, + "follow_status": { + "url": "https://api.bilibili.com/pgc/web/follow/status/update", + "method": "POST", + "verify": true, + "data": { + "season_id": "int: 番剧的 season_id", + "status": "int: 1 想看 2 在看 3 已看" + }, + "comment": "追番状态" + } + } +} diff --git a/bilibili_api/data/api/black-room.json b/bilibili_api/data/api/black-room.json new file mode 100644 index 0000000000000000000000000000000000000000..7ca1819636f91d24ca94afa61a6e11d00497125f --- /dev/null +++ b/bilibili_api/data/api/black-room.json @@ -0,0 +1,76 @@ +{ + "black_room": { + "info": { + "url": "https://api.bilibili.com/x/credit/blocked/list", + "method": "GET", + "verify": false, + "params": { + "btype": "int: 违规来源", + "otype": "int: 违规类型", + "pn": "int: 页码" + }, + "comment": "获取小黑屋内容" + }, + "detail": { + "url": "https://api.bilibili.com/x/credit/blocked/info", + "method": "GET", + "verify": false, + "params": { + "id": "小黑屋 id" + }, + "comment": "获取小黑屋详细信息" + } + }, + "jury": { + "detail": { + "url": "https://api.bilibili.com/x/credit/v2/jury/case/info", + "method": "GET", + "verify": true, + "params": { + "case_id": "案件仲裁 id" + }, + "comment": "获取案件仲裁详细信息" + }, + "opinion": { + "url": "https://api.bilibili.com/x/credit/v2/jury/case/opinion", + "method": "GET", + "verify": true, + "params": { + "case_id": "案件仲裁 id", + "pn":"int: 页码", + "ps":"int: 每页数量" + }, + "comment": "获取案件仲裁观点" + }, + "next_case": { + "url": "https://api.bilibili.com/x/credit/v2/jury/case/next", + "method": "GET", + "verify": true, + "comment": "获取下一个案件仲裁" + }, + "vote": { + "url": "https://api.bilibili.com/x/credit/v2/jury/vote", + "method": "POST", + "verify": true, + "params": { + "case_id": "案件仲裁 id", + "vote": "int: 投票结果", + "content": "string: 投票理由", + "insiders": "int: 是否观看此类视频", + "anonymous": "int: 是否匿名投票", + "csrf": "string: csrf" + }, + "comment": "进行仲裁投票" + }, + "case_list": { + "url": "https://api.bilibili.com/x/credit/v2/jury/case/list?pn=1&ps=20", + "method": "GET", + "verify": true, + "params": { + "pn":"int: 页码", + "ps":"int: 每页数量" + }, + "comment": "获取案件仲裁列表" + } + } +} diff --git a/bilibili_api/data/api/channel-series.json b/bilibili_api/data/api/channel-series.json new file mode 100644 index 0000000000000000000000000000000000000000..274ee18282ca3eeef5992f921f9dc1e9071210ef --- /dev/null +++ b/bilibili_api/data/api/channel-series.json @@ -0,0 +1,19 @@ +{ + "operate": { + "fav": { + "url": "https://api.bilibili.com/x/v3/fav/season/fav", + "method": "POST", + "params": { + "season": "int: 合集 id" + }, + "comment": "订阅合集" + }, + "unfav": { + "url": "https://api.bilibili.com/x/v3/fav/season/unfav", + "method": "POST", + "params": { + "season": "int: 合集 id" + } + } + } +} diff --git a/bilibili_api/data/api/cheese.json b/bilibili_api/data/api/cheese.json new file mode 100644 index 0000000000000000000000000000000000000000..20a5617f896ec1201ffa9a701772383ce4b6e7ec --- /dev/null +++ b/bilibili_api/data/api/cheese.json @@ -0,0 +1,39 @@ +{ + "info": { + "meta": { + "url": "https://api.bilibili.com/pugv/view/web/season", + "method": "GET", + "verify": false, + "params": { + "season_id": "int: 季度 id,不与 bangumi 互通", + "ep_id": "int: 一个视频的 EPID,不与 bangumi 互通" + }, + "comment": "获取课程元数据。" + }, + "list": { + "url": "https://api.bilibili.com/pugv/view/web/ep/list", + "method": "GET", + "verify": false, + "params": { + "season_id": "int: 季度 id,不与 bangumi 互通", + "pn": "int: 第几页,defaults to 1", + "ps": "int: 每一页的视频数,defaults to 50" + }, + "comment": "获取全部视频" + }, + "playurl": { + "url": "https://api.bilibili.com/pugv/player/web/playurl", + "method": "GET", + "verify": false, + "params": { + "avid": "int: aid", + "ep_id": "int: epid", + "cid": "int: cid", + "qn": "127", + "fnval": 4048, + "fourk": 1 + }, + "comment": "获取下载链接" + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/client.json b/bilibili_api/data/api/client.json new file mode 100644 index 0000000000000000000000000000000000000000..c01d06dbae5ce0a59c674c112129bf509f1bbf04 --- /dev/null +++ b/bilibili_api/data/api/client.json @@ -0,0 +1,8 @@ +{ + "zone": { + "url": "https://api.bilibili.com/x/web-interface/zone", + "method": "GET", + "verify": false, + "comment": "通过 IP 获取地理位置" + } +} diff --git a/bilibili_api/data/api/common.json b/bilibili_api/data/api/common.json new file mode 100644 index 0000000000000000000000000000000000000000..5e14aff1cb14d530eac69d5485a1bb80248426c3 --- /dev/null +++ b/bilibili_api/data/api/common.json @@ -0,0 +1,226 @@ +{ + "nickname": { + "check_nickname": { + "url": "https://passport.bilibili.com/web/generic/check/nickname", + "method": "GET", + "verify": false, + "params": { + "nickName": "string: 昵称" + }, + "comment": "检验昵称是否可用" + } + }, + "comment": { + "send": { + "url": "https://api.bilibili.com/x/v2/reply/add", + "method": "POST", + "verify": true, + "data": { + "oid": "int: 各种类型 id", + "type": "int: 1 视频,12 专栏,11 画册动态,17 文字动态,14 音频,19 歌单。下同。", + "message": "str: 评论内容", + "plat": "const int: 1", + "root": "int: 根评论 rpid,即在谁的评论下面回复,为空时在 oid 下直接评论", + "parent": "int: 父评论 rpid,即回复谁的评论,为空时在 oid 下直接评论" + }, + "comment": "发送评论" + }, + "like": { + "url": "https://api.bilibili.com/x/v2/reply/action", + "method": "POST", + "verify": true, + "data": { + "oid": "int: av 号", + "type": "int: 同 comment.send", + "action": "int bool: 1 点赞 0 取消点赞", + "rpid": "int: 评论编号" + }, + "comment": "点赞/取消点赞评论" + }, + "hate": { + "url": "https://api.bilibili.com/x/v2/reply/hate", + "method": "POST", + "verify": true, + "data": { + "oid": "int: av 号", + "type": "int: 同 comment.send", + "action": "int bool: 1 踩 0 取消踩", + "rpid": "int: 评论编号" + }, + "comment": "踩/取消踩评论" + }, + "pin": { + "url": "https://api.bilibili.com/x/v2/reply/top", + "method": "POST", + "verify": true, + "data": { + "oid": "int: av 号", + "type": "int: 同 comment.send", + "action": "int bool: 1 置顶 0 取消置顶", + "rpid": "int: 评论编号" + }, + "comment": "置顶/取消置顶评论" + }, + "del": { + "url": "https://api.bilibili.com/x/v2/reply/del", + "method": "POST", + "verify": true, + "data": { + "oid": "int: av 号", + "type": "int: 同 comment.send", + "rpid": "int: 评论编号" + }, + "comment": "删除评论" + }, + "get": { + "url": "https://api.bilibili.com/x/v2/reply", + "method": "GET", + "verify": false, + "params": { + "pn": "int: 页码", + "type": "", + "oid": "int: 动态时画册 id 或动态 id", + "sort": "int: 排序方式,2 按热度 0 按时间" + }, + "comment": "获取评论,分页模式" + }, + "sub_reply": { + "url": "https://api.bilibili.com/x/v2/reply/reply", + "method": "GET", + "verify": false, + "params": { + "pn": "int: 页码", + "ps": "const int: 10", + "type": "", + "oid": "int: id", + "root": "int: 根评论 ID" + }, + "comment": "获取评论的回复评论" + }, + "reply_lazy_loading": { + "url": "https://api.bilibili.com/x/v2/reply/main", + "method": "GET", + "verify": false, + "params": { + "next": "int: 评论页选择", + "ps": "int: 每页评论数", + "type": "CommentResourceType:资源类型枚举", + "oid": "int: id", + "mode": "int: 默认为 3。0 3:仅按热度 1:按热度+按时间 2:仅按时间" + }, + "comment": "获取评论的主评论,慢加载模式" + }, + "reply_by_session_id": { + "url": "https://api.bilibili.com/x/v2/reply/wbi/main", + "method": "GET", + "verify": false, + "wbi": true, + "params": { + "oid": "int: id", + "type": "", + "mode": "int: 默认为 3。0 3:仅按热度 1:按热度+按时间 2:仅按时间", + "pagination_str": "str: 分页依据", + "next": "int: 页码 0 第一页", + "ps": "const int: 1~30" + }, + "comment": "新版评论区" + }, + "report": { + "url": "https://api.bilibili.com/x/v2/reply/report", + "method": "POST", + "verify": true, + "data": { + "type": "num: 评论区类型代码,必要。类型代码见 https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/comment/readme.md#%E8%AF%84%E8%AE%BA%E5%8C%BA%E7%B1%BB%E5%9E%8B%E4%BB%A3%E7%A0%81", + "oid": "int: 目标评论区id,必要", + "rpid": "int: 目标评论rpid,必要", + "reason": "int: 举报原因,见 https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/comment/action.md#%E4%B8%BE%E6%8A%A5%E8%AF%84%E8%AE%BA", + "content": "str: 其他举报备注内容", + "csrf": "str: CSRF Token(位于cookie),Cookie方式必要" + }, + "comment": "举报评论" + } + }, + "dynamic_share": { + "url": "https://api.vc.bilibili.com/dynamic_repost/v1/dynamic_repost/share", + "method": "POST", + "verify": true, + "data": { + "uid": "int: up uid,经测试可为 0", + "type": "int: 8 视频(rid=av),64 专栏(rid=cv),256 音频(rid=au),2048 自定义分享,4097 番剧(rid=ep),", + "share_uid": "int: 自己的 uid,经测试可为 0", + "content": "str: 动态内容", + "rid": "int: 视频是 aid,专栏是 cvid,以此类推", + "csrf,csrf_token": "str: 同时提供这两个" + }, + "params": { + "#if (data.type == 2048)": { + "sketch[title]": "str: 标题", + "sketch[biz_type]": "const int: 131", + "sketch[cover_url]": "str: 图片链接", + "sketch[target_url]": "str: 跳转链接,仅限站内,间接站外跳转:https://game.bilibili.com/linkfilter/?url=" + } + }, + "comment": "str: 站内资源分享到动态" + }, + "online": { + "url": "https://api.bilibili.com/x/web-interface/online", + "method": "GET", + "verify": false, + "comment": "获取在线人数" + }, + "favorite": { + "get_favorite_list_old": { + "url": "https://api.bilibili.com/medialist/gateway/base/created", + "method": "GET", + "verify": true, + "params": { + "up_mid": "int: 用户 uid", + "type": "int: 12 音频", + "pn": "int: 页码", + "ps": "const int: 100", + "rid": "int: 音频(au)" + }, + "comment": "获取媒体收藏情况,必须使用 Referer" + }, + "get_favorite_list": { + "url": "https://api.bilibili.com/x/v3/fav/folder/created/list-all", + "method": "GET", + "verify": true, + "params": { + "up_mid": "int: 用户 uid", + "#if 需要获取媒体收藏情况": { + "type": "int: 2 视频", + "rid": "int: 视频(aid)" + } + }, + "comment": "获取收藏夹列表信息" + }, + "get_favorite_list_content": { + "url": "https://api.bilibili.com/x/v3/fav/resource/list", + "method": "GET", + "verify": true, + "params": { + "media_id": "int: 收藏夹 id", + "ps": "const int: 20", + "pn": "int: 页码", + "keyword": "str: 搜索关键词", + "order": "str: 排序依据。mtime 最近收藏,mtime 最多播放,mtime 最新投稿", + "type": "const int: 0", + "tid": "int: 分区 ID,0 为全部" + }, + "comment": "获取收藏夹内容" + }, + "operate_favorite": { + "url": "https://api.bilibili.com/medialist/gateway/coll/resource/deal", + "method": "POST", + "verify": true, + "data": { + "rid": "int: 视频 aid,音频 aid", + "type": "int: 2 视频,12 音频", + "add_media_ids": "commaSeparatedList[int]: 添加收藏(多个收藏夹时半角逗号分隔)", + "del_media_ids": "commaSeparatedList[int]: 移除收藏(多个收藏夹时半角逗号分隔)" + }, + "comment": "收藏夹修改" + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/creative_center.json b/bilibili_api/data/api/creative_center.json new file mode 100644 index 0000000000000000000000000000000000000000..b294d2cabda79c8eb669205f6c9e1431a4ab5f87 --- /dev/null +++ b/bilibili_api/data/api/creative_center.json @@ -0,0 +1,247 @@ +{ + "overview": { + "compare": { + "url": "https://member.bilibili.com/x/web/data/v2/overview/compare", + "method": "GET", + "verify": true, + "comment": "获取对比数据" + }, + "graph": { + "url": "https://member.bilibili.com/x/web/data/v2/overview/stat/graph", + "method": "GET", + "verify": true, + "params": { + "period": "int: 统计周期", + "s_locale": "str: zh_CN", + "type": "str: 统计类型" + }, + "comment": "获取统计图表数据" + }, + "num": { + "url": "https://member.bilibili.com/x/web/data/v2/overview/stat/num", + "method": "GET", + "verify": true, + "params": { + "period": "int: 统计周期", + "s_locale": "str: zh_CN", + "tab": "int: Unknown" + }, + "comment": "获取统计数据" + } + }, + "data-up": { + "video": { + "survey": { + "url": "https://member.bilibili.com/x/web/data/survey", + "method": "GET", + "verify": true, + "params": { + "type": "int: 统计类型" + }, + "comment": "获取各分区中占比排行" + }, + "playanalysis": { + "url": "https://member.bilibili.com/x/web/data/playanalysis?copyright=0&t=1676161628464", + "method": "GET", + "verify": true, + "params": { + "copyright": "int: 版权" + }, + "comment": "获取稿件播放完成率对比数据" + }, + "source": { + "url": "https://member.bilibili.com/x/web/data/v2/overview/source", + "method": "GET", + "verify": true, + "params": { + "s_locale": "str: zh_CN" + }, + "comment": "获取稿件播放来源分布数据" + } + }, + "fan": { + "overview": { + "url": "https://member.bilibili.com/x/web/data/v2/fans/stat/num", + "method": "GET", + "verify": true, + "params": { + "period": "int: 统计周期" + }, + "comment": "获取粉丝数据" + }, + "graph": { + "url": "https://member.bilibili.com/x/web/data/v2/fans/stat/graph?type=all_fans&period=2", + "method": "GET", + "verify": true, + "params": { + "period": "int: 统计周期", + "type": "str: 统计类型" + }, + "comment": "获取粉丝数据图表" + } + }, + "article": { + "overview": { + "url": "https://member.bilibili.com/x/web/data/article", + "method": "GET", + "verify": true, + "comment": "获取文章概览信息" + }, + "graph": { + "url": "https://member.bilibili.com/x/web/data/article/thirty", + "method": "GET", + "verify": true, + "params": { + "type": "int: 图表类型" + }, + "comment": "获取文章数据图表" + }, + "rank": { + "url": "https://member.bilibili.com/x/web/data/article/rank", + "method": "GET", + "verify": true, + "params": { + "type": "int: 图表类型" + }, + "comment": "获取来源稿件数据" + }, + "source": { + "url": "https://member.bilibili.com/x/web/data/article/source", + "method": "GET", + "verify": true, + "comment": "获取文章阅读终端数据" + } + } + }, + "upload-manager": { + "video_draft": { + "url": "https://member.bilibili.com/x/vupre/web/draft/list", + "method": "GET", + "verify": true, + "comment": "获取内容管理视频草稿信息" + }, + "video": { + "url": "https://member.bilibili.com/x/web/archives", + "method": "GET", + "verify": true, + "params": { + "pn": "int: 页码", + "ps": "int: 每页项数", + "coop": "int: unknown", + "status": "str: is_pubing,pubed,not_pubed", + "order": "str: click, stow, senddate, dm_count, scores", + "interactive": "int: 1 为视频 2 为 互动视频", + "tid": "int: 分区id" + }, + "comment": "获取内容管理视频信息" + }, + "article": { + "url": "https://api.bilibili.com/x/article/creative/article/list?", + "method": "GET", + "verify": true, + "params": { + "group": "int: 0 全部 1 进行中 2 已通过 3 未通过", + "sort": "int: 1 创建时间 2 点赞 3 评论 5 收藏 6 投币", + "pn": "int: 页码", + "mobi_app": "str: pc" + }, + "comment": "获取内容管理文章信息" + }, + "article_list": { + "url": "https://api.bilibili.com/x/article/creative/article/list?", + "method": "GET", + "verify": true, + "comment": "获取内容管理文集信息" + } + }, + "comment-manager": { + "fulllist": { + "url": "https://api.bilibili.com/x/v2/reply/up/fulllist", + "method": "GET", + "verify": true, + "params": { + "oid": "int: 稿件 oid", + "order": "int: 排序", + "pn": "int: 页码", + "ps":"int: 每页项数", + "type": "int: 稿件类型", + "filter": "int: -1", + "charge_plus_filter": "bool: false", + "keyword": "str: 关键词" + }, + "comment": "获取评论管理评论信息" + }, + "del": { + "url": "https://api.bilibili.com/x/v2/reply/del", + "method": "POST", + "verify": true, + "data": { + "oid": "str: 稿件 oid,用逗号分隔", + "type": "int: 稿件类型", + "rpid": "str: 评论 rpid,用逗号分隔", + "csrf": "str: csrf" + }, + "comment": "评论管理删除评论" + } + }, + "danmaku-manager": { + "search": { + "url": "https://api.bilibili.com/x/v2/dm/search", + "method": "GET", + "verify": true, + "params": { + "oid": "str: 稿件oid,用逗号分隔", + "type": "int: 稿件类型", + "mids": "str: 用户mids,用逗号分隔", + "keyword": "str: 关键词", + "progress_from": "int: 进度开始", + "progress_to": "int: 进度结束", + "ctime_from": "str: 创建时间起始", + "ctime_to": "str: 创建时间结束", + "modes": "int: 弹幕模式", + "pool": "int: 弹幕池", + "attrs": "str: 弹幕属性", + "order": "str: 排序字段", + "sort": "str: 排序方式", + "pn": "int: 页码", + "ps": "int: 每页项数", + "cp_filter": "bool: 是否过滤CP弹幕" + }, + "comment": "弹幕搜索" + }, + "recent": { + "url": "https://api.bilibili.com/x/v2/dm/recent", + "method": "GET", + "verify": true, + "params": { + "pn": "int: 页码", + "ps": "int: 每页项数" + }, + "comment": "最近弹幕" + }, + "state": { + "url": "https://api.bilibili.com/x/v2/dm/edit/state", + "method": "POST", + "verify": true, + "data": { + "oid":"int: 稿件 oid", + "type": "int: 稿件类型 1", + "dmids": "str: 弹幕id,用逗号分隔", + "state": "int: 1 删除 2 保护 3 取消保护" + }, + "comment": "操作弹幕" + }, + "pool": { + "url": "https://api.bilibili.com/x/v2/dm/edit/pool", + "method": "POST", + "verify": true, + "data": { + "oid":"int: 稿件 oid", + "type": "int: 稿件类型 1", + "dmids": "str: 弹幕id,用逗号分隔", + "pool": "int: 弹幕池 0 普通 1 字幕" + }, + "comment": "操作弹幕池" + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/credential.json b/bilibili_api/data/api/credential.json new file mode 100644 index 0000000000000000000000000000000000000000..4baafd1f16cdab9d502e7a97e174bc8dfe585e82 --- /dev/null +++ b/bilibili_api/data/api/credential.json @@ -0,0 +1,53 @@ +{ + "info": { + "check_cookies": { + "url": "https://passport.bilibili.com/x/passport-login/web/cookie/info", + "method": "GET", + "verify": true, + "comment": "检查是否需要刷新 Cookie" + }, + "valid": { + "url": "https://api.bilibili.com/x/web-interface/nav", + "method": "GET", + "verify": false, + "ignore_code": true, + "comment": "(本质作用为获取 cookies 信息)如果 code = 0 则 cookies 有效" + }, + "spi": { + "url": "https://api.bilibili.com/x/frontend/finger/spi", + "method": "GET", + "verify": false, + "comment": "获取 buvid3 / buvid4" + } + }, + "operate": { + "get_refresh_csrf": { + "url": "https://www.bilibili.com/correspond/1/{correspondPath}", + "method": "GET", + "verify": true, + "comment": "获取刷新 CSRF,记得替换 correspondPath" + }, + "refresh_cookies": { + "url": "https://passport.bilibili.com/x/passport-login/web/cookie/refresh", + "method": "POST", + "verify": true, + "data": { + "refresh_csrf": "refresh_csrf", + "csrf": "Cookie 中的 bili_jct 字段", + "source": "main_web", + "refresh_token": "Cookie 中的 ac_time_value 字段" + }, + "comment": "刷新 Cookies" + }, + "confirm_refresh": { + "url": "https://passport.bilibili.com/x/passport-login/web/confirm/refresh", + "method": "POST", + "verify": true, + "data": { + "csrf": "从新的 cookie 中获取", + "refresh_token": "在刷新前 localStorage 中的ac_time_value获取,并非刷新后返回的值" + }, + "comment": "确认刷新" + } + } +} diff --git a/bilibili_api/data/api/dynamic.json b/bilibili_api/data/api/dynamic.json new file mode 100644 index 0000000000000000000000000000000000000000..768a79658d92694d2d476737f5efbf2dcfc63c17 --- /dev/null +++ b/bilibili_api/data/api/dynamic.json @@ -0,0 +1,278 @@ +{ + "send": { + "upload_img": { + "url": "https://api.bilibili.com/x/dynamic/feed/draw/upload_bfs", + "method": "POST", + "verify": true, + "data": { + "biz": "const str: draw", + "category": "const str: daily" + }, + "files": { + "file_up": "二进制 img" + }, + "comment": "上传图片" + }, + "schedule": { + "url": "https://api.vc.bilibili.com/dynamic_draft/v1/dynamic_draft/add_draft", + "method": "POST", + "verify": true, + "data": { + "type": "int: 4 为有图动态,2 为无图动态", + "publish_time": "int: 发布时间戳", + "request(if type=4)": "见 instant_draw.data,无 setting", + "request(if type=2)": "见 instant_text.data" + }, + "comment": "发布定时动态" + }, + "instant_draw": { + "url": "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/create_draw", + "method": "POST", + "verify": true, + "data": { + "biz": "const int: 3", + "category": "const int: 3", + "type": "const int: 0", + "pictures": [ + { + "img_src": "str: 图片地址", + "img_width": "int: 图片宽度", + "img_height": "int: 图片高度" + } + ], + "title": "", + "tags": "", + "description": "str: 动态文字内容", + "content": "str: 动态文字内容", + "from": "const str: create.dynamic.web", + "up_choose_comment": "const int : 0", + "extension": "const str: {\"emoji_type\":1,\"from\":{\"emoji_type\":1},\"flag_cfg\":{}}", + "at_uids": "commaSeparatedList[int]: 艾特用户 UID 列表,半角逗号分隔", + "at_control": [ + { + "location": "int: @符号起始位置,0 为第一个字符", + "type": "const int: 1", + "length": "int: @区域长度(2 + 用户名字符串长度)", + "data": "int: 用户 UID" + } + ], + "setting": { + "copy_forbidden": "const int: 0", + "cachedTime": "const int: 0" + } + } + }, + "instant_text": { + "url": "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/create", + "method": "POST", + "verify": true, + "data": { + "dynamic_id": "const int: 0", + "type": "const int: 4", + "rid": "const int: 0", + "content": "str: 动态文本内容", + "extension": "const str: {\"emoji_type\":1}", + "at_uids": "commaSeparatedList[int]: 艾特用户 UID 列表,半角逗号分隔", + "ctrl": [ + { + "location": "int: @符号起始位置,0 为第一个字符", + "type": "const int: 1", + "length": "int: @区域长度(2 + 用户名字符串长度)", + "data": "int: 用户 UID" + } + ] + } + }, + "instant": { + "url": "https://api.bilibili.com/x/dynamic/feed/create/dyn", + "method": "POST", + "verify": true, + "data": { + "content": { + "contents": [ + "#: 动态信息", + {"raw_text": "纯文本", "biz_id": "", "type": 1}, + {"raw_text": "表情包", "biz_id": "", "type": 9}, + {"raw_text": "@人", "biz_id": "UID", "type": 2}, + {"raw_text": "投票", "biz_id": "投票 id", "type": 4} + ] + }, + "scene": "int: 1 纯文本, 2 带图", + "pics": [ + { + "img_src":"str: 图片地址", + "img_height":"int: 图片高度", + "img_width":"int: 图片宽度", + "img_size":"int: 图片大小 (kb)" + } + ], + "topic": { + "id": "int: 话题 id" + } + } + }, + "sub_check": { + "url": "https://api.bilibili.com/x/dynamic/feed/create/submit_check", + "method": "POST", + "verify": true, + "comment": "动态发送预检测" + } + }, + "operate": { + "delete": { + "url": "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/rm_dynamic", + "method": "POST", + "verify": true, + "data": { + "dynamic_id": "int: 动态 ID" + }, + "comment": "删除动态" + }, + "like": { + "url": "https://api.vc.bilibili.com/dynamic_like/v1/dynamic_like/thumb", + "method": "POST", + "verify": true, + "data": { + "dynamic_id": "int: 动态 ID", + "up": "int: 1 点赞 2 取消", + "uid": "int: 自己 uid" + }, + "comment": "点赞" + }, + "repost": { + "url": "https://api.vc.bilibili.com/dynamic_repost/v1/dynamic_repost/repost", + "method": "POST", + "verify": true, + "data": { + "dynamic_id": "int: 动态 ID", + "content": "str: 内容", + "extension": "const str: {\"emoji_type\":1}" + }, + "comment": "转发" + } + }, + "info": { + "attention_new_dynamic": { + "url": "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/w_dyn_uplist", + "method": "GET", + "verify": true, + "comment": "获取发布新动态的关注者" + }, + "attention_live": { + "url": "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/w_live_users", + "method": "GET", + "params": { + "size": "int: 用户数量" + }, + "verify": true, + "comment": "获取正在直播的关注者" + }, + "repost": { + "url": "https://api.vc.bilibili.com/dynamic_repost/v1/dynamic_repost/repost_detail", + "method": "GET", + "verify": false, + "params": { + "dynamic_id": "int: 动态 ID", + "offset": "int: 每页第一条动态 ID" + }, + "comment": "动态转发信息,最多获取 560 条左右" + }, + "likes": { + "url": "https://api.vc.bilibili.com/dynamic_like/v1/dynamic_like/spec_item_likes", + "method": "GET", + "verify": false, + "params": { + "dynamic_id": "int: 动态 ID", + "pn": "页码", + "ps": "每页数量" + }, + "comment": "动态点赞信息" + }, + "detail": { + "url": "https://api.bilibili.com/x/polymer/web-dynamic/v1/detail", + "method": "GET", + "verify": false, + "params": { + "timezone_offset":"int: 时区偏移量", + "dynamic_id": "int: 动态 ID", + "features":"str: 默认 itemOpusStyle" + }, + "comment": "动态详细信息" + }, + "reaction": { + "url": "https://api.bilibili.com/x/polymer/web-dynamic/v1/detail/reaction", + "method": "GET", + "verify": false, + "params": { + "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", + "method": "GET", + "verify": true, + "comment": "获取动态页 UP 主信息列表" + }, + "dynamic_page_info": { + "url": "https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all", + "method": "GET", + "verify": true, + "params": { + "timezone_offset": "int: 时区偏移量", + "type": "str: 动态分类类型", + "page": "int: 页码", + "features": "str: 默认 itemOpusStyle", + "offset": "int: 每页最后一条动态 ID", + "host_mid": "int: UP 主 UID" + }, + "comment": "获取动态页信息" + }, + "hot_dynamics": { + "url": "https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/hot", + "method": "GET", + "verify": false, + "params": { + "page": "int: 页码. Defaults to 1. " + }, + "comment": "获取热门动态" + } + }, + "schedule": { + "list": { + "url": "https://api.vc.bilibili.com/dynamic_draft/v1/dynamic_draft/get_drafts", + "method": "GET", + "verify": false, + "comment": "获取待发送定时动态列表" + }, + "publish_now": { + "url": "https://api.vc.bilibili.com/dynamic_draft/v1/dynamic_draft/publish_now", + "method": "POST", + "verify": false, + "data": { + "draft_id": "int: 定时动态 ID" + }, + "comment": "立即发送定时动态" + }, + "modify": { + "url": "https://api.vc.bilibili.com/dynamic_draft/v1/dynamic_draft/modify_draft", + "method": "POST", + "verify": false, + "data": { + "draft_id": "int: 定时动态 ID", + "#include dynamic.send.schedule": "剩余参数见 dynamic.send.schedule" + }, + "comment": "修改待发定时动态" + }, + "delete": { + "url": "https://api.vc.bilibili.com/dynamic_draft/v1/dynamic_draft/rm_draft", + "method": "POST", + "verify": false, + "data": { + "draft_id": "int: 定时动态 ID" + }, + "comment": "删除待发定时动态" + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/emoji.json b/bilibili_api/data/api/emoji.json new file mode 100644 index 0000000000000000000000000000000000000000..355abd3558bb19a7481b2c7e196082a5c012796e --- /dev/null +++ b/bilibili_api/data/api/emoji.json @@ -0,0 +1,11 @@ +{ + "list": { + "url": "https://api.bilibili.com/x/emote/user/panel/web", + "method": "GET", + "verify": false, + "params": { + "business": "使用场景 reply/dynamic" + }, + "comment": "获取表情包" + } +} diff --git a/bilibili_api/data/api/favorite-list.json b/bilibili_api/data/api/favorite-list.json new file mode 100644 index 0000000000000000000000000000000000000000..61926eec90d3401162ea35699be974f2e1123da3 --- /dev/null +++ b/bilibili_api/data/api/favorite-list.json @@ -0,0 +1,178 @@ +{ + "info": { + "list_list": { + "url": "https://api.bilibili.com/x/v3/fav/folder/created/list-all", + "method": "GET", + "verify": false, + "params": { + "up_mid": "int: 用户 UID", + "type": "int?: 资源类型,2 视频。", + "rid": "int?: 资源 ID" + }, + "comment": "获取收藏夹列表。提供 type 和 rid 时会同时提供对该资源收藏情况。" + }, + "info": { + "url": "https://api.bilibili.com/x/v3/fav/folder/info", + "method": "GET", + "verify": false, + "params": { + "media_id": "int: 收藏夹 ID" + }, + "comment": "获取收藏夹信息。" + }, + "list_content": { + "url": "https://api.bilibili.com/x/v3/fav/resource/list", + "method": "GET", + "verify": false, + "params": { + "media_id": "int: 收藏夹 ID", + "pn": "int: 页码。", + "ps": "int: 每页数量,固定 20。", + "keyword": "str?: 关键词搜索。", + "order": "str: 排序方式。mtime 最近收藏,view 最多播放,pubtime 最新投稿。", + "type": "int: 收藏夹类型。目前固定 0。", + "tid": "int: 分区 ID。0 为全部分区。" + }, + "comment": "获取收藏夹列表内容。" + }, + "list_content_id_list": { + "url": "https://api.bilibili.com/x/v3/fav/resource/ids", + "method": "GET", + "verify": false, + "params": { + "media_id": "int: 收藏夹 ID" + }, + "comment": "获取收藏夹所有内容的 ID。" + }, + "list_topics": { + "url": "https://app.bilibili.com/x/topic/web/fav/list", + "method": "GET", + "verify": true, + "params": { + "page_num": "int: 页码。", + "page_size": "int: 每页数量,固定 16。" + }, + "comment": "获取自己的话题收藏夹内容。" + }, + "list_articles": { + "url": "https://api.bilibili.com/x/article/favorites/list/all", + "method": "GET", + "verify": true, + "params": { + "pn": "int: 页码。", + "ps": "int: 每页数量,固定 16。" + }, + "comment": "获取自己的专栏收藏夹内容。" + }, + "list_courses": { + "url": "https://api.bilibili.com/pugv/app/web/favorite/page", + "method": "GET", + "verify": true, + "params": { + "pn": "int: 页码。", + "ps": "int: 每页数量,固定 10。", + "mid": "int: 自己的 UID。" + }, + "comment": "获取自己的课程收藏夹内容。" + }, + "list_notes": { + "url": "https://api.bilibili.com/x/note/list", + "method": "GET", + "verify": true, + "params": { + "pn": "int: 页码。", + "ps": "int: 每页数量,固定 10。" + }, + "comment": "获取自己的笔记收藏夹内容。" + }, + "collected": { + "url": "https://api.bilibili.com/x/v3/fav/folder/collected/list", + "method": "GET", + "verify": true, + "params": { + "pn":"int: 页码。", + "ps":"int: 每页数量", + "up_mid":"int: 用户 UID" + }, + "comment": "获取自己的收藏/订阅的收藏夹/合集" + } + }, + "operate": { + "new": { + "url": "https://api.bilibili.com/x/v3/fav/folder/add", + "method": "POST", + "verify": true, + "data": { + "title": "str: 收藏夹标题。", + "intro": "str: 收藏夹简介。", + "privacy": "int bool: 是否为私有。", + "cover": "str: 暂时为空" + }, + "comment": "新建收藏夹。" + }, + "modify": { + "url": "https://api.bilibili.com/x/v3/fav/folder/edit", + "method": "POST", + "verify": true, + "data": { + "title": "str: 收藏夹标题。", + "intro": "str: 收藏夹简介。", + "privacy": "int bool: 是否为私有。", + "cover": "str: 暂时为空", + "media_id": "int: 收藏夹 ID。" + }, + "comment": "修改收藏夹信息。" + }, + "delete": { + "url": "https://api.bilibili.com/x/v3/fav/folder/del", + "method": "POST", + "verify": true, + "data": { + "media_ids": "commaSeparatedList[int]: 收藏夹 ID。" + }, + "comment": "删除收藏夹。" + }, + "content_copy": { + "url": "https://api.bilibili.com/x/v3/fav/resource/copy", + "method": "POST", + "verify": true, + "data": { + "src_media_id": "int: 源收藏夹 ID。", + "tar_media_id": "int: 目标收藏夹 ID。", + "mid": "int: 自己的 UID。", + "resources": "commaSeparatedList[str]: 要操作的资源,格式:'资源 ID:资源类型'。视频类型为 2。如:'83051349:2'" + }, + "comment": "复制资源到另一收藏夹。" + }, + "content_move": { + "url": "https://api.bilibili.com/x/v3/fav/resource/move", + "method": "POST", + "verify": true, + "data": { + "src_media_id": "int: 源收藏夹 ID。", + "tar_media_id": "int: 目标收藏夹 ID。", + "resources": "commaSeparatedList[str]: 要操作的资源,格式:'资源 ID:资源类型'。视频类型为 2。如:'83051349:2'" + }, + "comment": "移动资源到另一收藏夹。" + }, + "content_rm": { + "url": "https://api.bilibili.com/x/v3/fav/resource/batch-del", + "method": "POST", + "verify": true, + "data": { + "media_id": "int: 收藏夹 ID。", + "resources": "commaSeparatedList[str]: 要操作的资源,格式:'资源 ID:资源类型'。视频类型为 2。如:'83051349:2'" + }, + "comment": "删除收藏夹中的资源。" + }, + "content_clean": { + "url": "https://api.bilibili.com/x/v3/fav/resource/clean", + "method": "POST", + "verify": true, + "data": { + "media_id": "int: 收藏夹 ID。" + }, + "comment": "清理失效内容。" + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/game.json b/bilibili_api/data/api/game.json new file mode 100644 index 0000000000000000000000000000000000000000..9488dfdd6a1bc329b0c76d6e9ec29e81da61a82b --- /dev/null +++ b/bilibili_api/data/api/game.json @@ -0,0 +1,83 @@ +{ + "info": { + "info": { + "url": "https://line1-h5-pc-api.biligame.com/game/detail/gameinfo", + "method": "GET", + "verify": false, + "params": { + "game_base_id": "游戏 id" + }, + "comment": "获取游戏简介" + }, + "UP": { + "url": "https://line1-h5-pc-api.biligame.com/game/detail/account", + "method": "GET", + "verify": false, + "params": { + "game_base_id": "游戏 id" + }, + "comment": "获取游戏官方账户" + }, + "detail": { + "url": "https://line1-h5-pc-api.biligame.com/game/detail/content", + "method": "GET", + "verify": false, + "params": { + "game_base_id": "游戏 id" + }, + "comment": "获取游戏详情" + }, + "wiki": { + "url": "https://line1-h5-pc-api.biligame.com/game/detail/wiki", + "method": "GET", + "verify": false, + "params": { + "game_base_id": "游戏 id" + }, + "comment": "获取游戏教程" + }, + "videos": { + "url": "https://line1-h5-pc-api.biligame.com/game/detail/get_video_v2", + "method": "GET", + "params": { + "game_base_id": "游戏 id" + }, + "comment": "获取游戏视频" + }, + "score": { + "url": "https://line1-h5-pc-api.biligame.com/game/comment/summary", + "method": "GET", + "params": { + "game_base_id": "游戏 id" + }, + "comment": "获取游戏分数" + }, + "comment": { + "url": "https://line1-h5-pc-api.biligame.com/game/comment/recommend", + "method": "GET", + "params": { + "game_base_id": "游戏 id" + }, + "comment": "获取游戏的评论" + }, + "rank": { + "url": "https://le3-api.game.bilibili.com/pc/game/ranking/page_ranking_list", + "method": "GET", + "params": { + "ranking_type": "int: 1 热度榜 5 预约榜 6 新游榜 2 口碑榜 7 B指榜 11 端游榜 ", + "page_num": "int: page num", + "page_size": "int: page size" + }, + "comment": "游戏榜单" + }, + "start_test": { + "url": "https://le3-api.game.bilibili.com/pc/game/ranking/page_start_test_list", + "method": "GET", + "params": { + "x-fix-page-num": "const int: 1", + "page_num": "int: page num", + "page_size": "int: page size" + } + } + } +} diff --git a/bilibili_api/data/api/homepage.json b/bilibili_api/data/api/homepage.json new file mode 100644 index 0000000000000000000000000000000000000000..82f59d1957b28e65dc16d7649d141ab67ec754fa --- /dev/null +++ b/bilibili_api/data/api/homepage.json @@ -0,0 +1,39 @@ +{ + "info": { + "top_photo": { + "url": "https://api.bilibili.com/x/web-show/page/header", + "method": "GET", + "params": { + "resource_id": "int: 142" + }, + "verify": false, + "comment": "获取主页最上方的图像" + }, + "links": { + "url": "https://api.bilibili.com/x/web-show/res/locs", + "method": "GET", + "params": { + "pf": "int: 0", + "ids": "int: 4694" + }, + "verify": false, + "comment": "获取主页左面的链接列表" + }, + "popularize": { + "url": "https://api.bilibili.com/x/web-show/res/locs", + "method": "GET", + "params": { + "pf": "int: 0", + "ids": "int: 34" + }, + "verify": false, + "comment": "获取推广的项目" + }, + "videos": { + "url": "https://api.bilibili.com/x/web-interface/index/top/rcmd", + "method": "GET", + "verify": false, + "comment": "获取主页推荐的视频" + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/hot.json b/bilibili_api/data/api/hot.json new file mode 100644 index 0000000000000000000000000000000000000000..9ca2bb5501c95029e1c1d0db0936d05dbd2a9c96 --- /dev/null +++ b/bilibili_api/data/api/hot.json @@ -0,0 +1,11 @@ +{ + "buzzwords": { + "url": "https://api.bilibili.com/x/v2/dm/buzzword/list", + "method": "GET", + "params": { + "type_id": "int: 4", + "pn": "int: 页码. Defaults to 1. ", + "ps": "int: 每页的数据大小. Defaults to 20. " + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/interactive_video.json b/bilibili_api/data/api/interactive_video.json new file mode 100644 index 0000000000000000000000000000000000000000..6099ccde08cf6330b89a85af9123c00b1ed1be3e --- /dev/null +++ b/bilibili_api/data/api/interactive_video.json @@ -0,0 +1,45 @@ +{ + "info": { + "videolist": { + "url": "https://member.bilibili.com/x/vupre/web/archive/view", + "method": "GET", + "verify": true, + "params": { + "bvid": "str: bv 号" + }, + "comment": "视频列表数据" + }, + "edge_info": { + "url": "https://api.bilibili.com/x/stein/edgeinfo_v2", + "method": "GET", + "verify": false, + "params": { + "bvid": "str: BV 号", + "graph_version": "int: 剧情图版本", + "edge_id": "int?: 节点 ID" + }, + "comment": "互动视频节点信息" + } + }, + "operate": { + "savestory": { + "url": "https://api.bilibili.com/x/stein/graph/save", + "method": "POST", + "verify": true, + "data": { + "preview": "int: 0 不清楚是什么", + "data": "故事树信息" + }, + "comment": "保存故事树" + }, + "mark_score": { + "url": "https://api.bilibili.com/x/stein/mark", + "method": "POST", + "verify": true, + "data": { + "mark": "int: 星数", + "bvid":"str: BV 号" + } + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/live-area.json b/bilibili_api/data/api/live-area.json new file mode 100644 index 0000000000000000000000000000000000000000..a37f9b11008fb920750eaed16adf5b187d263908 --- /dev/null +++ b/bilibili_api/data/api/live-area.json @@ -0,0 +1,15 @@ +{ + "info": { + "list": { + "url": "https://api.live.bilibili.com/xlive/web-interface/v1/second/getList", + "method": "GET", + "params": { + "platform": "str: web", + "parent_area_id": "int: 主分区 id", + "area_id": "int: 分区 id,如果要获取主分区所有数据则为 0", + "page": "int: 第几页", + "sort_type": "str: 排序方式,live_time 最新, 不填写则为综合" + } + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/live.json b/bilibili_api/data/api/live.json new file mode 100644 index 0000000000000000000000000000000000000000..d2d57f65ef582451bbc5304bef740ff682390afd --- /dev/null +++ b/bilibili_api/data/api/live.json @@ -0,0 +1,440 @@ +{ + "info": { + "start": { + "url": "https://api.live.bilibili.com/room/v1/Room/startLive", + "method": "POST", + "verify": false, + "params": { + "room_id": "int: 房间号", + "platform": "pc/web/...", + "area_v2": "int: 直播间新版分区id, 可参考https://github.com/withsalt/BilibiliLiveTools#%E7%9B%B4%E6%92%AD%E5%88%86%E5%8C%BA", + "csrf,csrf_token": "要给两个" + }, + "comment": "开始直播" + }, + "stop": { + "url": "https://api.live.bilibili.com/room/v1/Room/stopLive", + "method": "POST", + "verify": false, + "params": { + "room_id": "int: 房间号", + "platform": "pc/web/...", + "csrf,csrf_token": "要给两个" + }, + "comment": "关闭直播" + }, + "room_play_info": { + "url": "https://api.live.bilibili.com/xlive/web-room/v1/index/getRoomPlayInfo", + "method": "GET", + "verify": false, + "params": { + "room_id": "int: 房间号" + }, + "comment": "获取房间信息(真实房间号,封禁情况等)" + }, + "danmu_info": { + "url": "https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo", + "method": "GET", + "verify": true, + "params": { + "id": "int: 真实房间号" + }, + "comment": "获取聊天弹幕服务器配置信息(websocket)" + }, + "room_info": { + "url": "https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom", + "method": "GET", + "verify": false, + "params": { + "room_id": "int: 真实房间号" + }, + "comment": "获取直播间信息(标题,简介等)" + }, + "user_info_in_room": { + "url": "https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByUser", + "method": "GET", + "verify": true, + "params": { + "room_id": "int: 真实房间号" + }, + "comment": "获取自己在直播间的信息(粉丝勋章等级,直播用户等级等)" + }, + "area_info": { + "url": "https://api.live.bilibili.com/room/v1/Area/getList", + "method": "GET", + "verify": false, + "comment": "获取直播间分区信息" + }, + "user_info": { + "url": "https://api.live.bilibili.com/xlive/web-ucenter/user/get_user_info", + "method": "GET", + "verify": true, + "comment": "获取直播用户等级等信息" + }, + "user_guards": { + "url": "https://api.live.bilibili.com/xlive/web-ucenter/user/guards", + "method": "GET", + "verify": true, + "params": { + "page": "页码", + "page_size": "每页数量, 过多可能报错 默认:10" + }, + "comment": "获取用户开通的大航海列表" + }, + "bag_list": { + "url": "https://api.live.bilibili.com/xlive/web-room/v1/gift/bag_list", + "method": "GET", + "verify": "true", + "comment": "获取自己的礼物包裹" + }, + "dahanghai": { + "url": "https://api.live.bilibili.com/xlive/app-room/v1/guardTab/topList", + "method": "GET", + "verify": false, + "params": { + "roomid": "int: 真实房间号", + "page": "int: 页码", + "ruid": "int: 全称 room_uid,从 room_play_info 里头的 uid 可以找到", + "page_size": 29 + }, + "comment": "获取大航海列表" + }, + "gaonengbang": { + "url": "https://api.live.bilibili.com/xlive/general-interface/v1/rank/getOnlineGoldRank", + "method": "GET", + "verify": false, + "params": { + "roomId": "int: 真实房间号", + "page": "int: 页码", + "ruid": "int: 全称 room_uid,从 room_play_info 里头的 uid 可以找到", + "pageSize": 50 + }, + "comment": "获取高能榜" + }, + "fan_model": { + "url": "https://api.live.bilibili.com/xlive/app-ucenter/v1/fansMedal/panel", + "method": "GET", + "verify": true, + "params": { + "roomId": "int: 房间号,不是必须的", + "page": "int: 页码,不是必须的", + "target_id": "int: 主播Uid,不是必须的", + "pageSize": "int: 默认 10 是必须的,否则不返回有效数据" + }, + "comment": "获取房间的粉丝牌,自己的粉丝牌等数据" + }, + "live_info": { + "url": "https://api.live.bilibili.com/xlive/web-ucenter/user/live_info", + "method": "GET", + "verify": false, + "comment": "获取自己粉丝牌,粉丝勋章,大航海等数据" + }, + "general_info": { + "url": "https://api.live.bilibili.com/xlive/fuxi-interface/general/half/initial", + "method": "GET", + "verify": true, + "params": { + "actId": "未知,大航海信息:100061", + "roomId": "真实房间号", + "uid": "直播者uid", + "csrf,csrf_token": "要给两个" + }, + "comment": "获取自己在当前房间的大航海信息, 是否开通,等级,当前经验,同时可获得自己开通的所有航海日志" + }, + "update_news": { + "url": "https://api.live.bilibili.com/xlive/app-blink/v1/index/updateRoomNews", + "method": "POST", + "verify": true, + "params": { + "roomId": "真实房间号", + "uid": "直播者uid", + "content": "内容, 最多60字符", + "csrf": "csrf", + "csrf_token": "csrf" + }, + "comment": "更新房间公告" + }, + "seven_rank": { + "url": "https://api.live.bilibili.com/rankdb/v1/RoomRank/webSevenRank", + "method": "GET", + "verify": false, + "params": { + "roomid": "int: 真实房间号", + "ruid": "int: 全称 room_uid,从 room_play_info 里头的 uid 可以找到" + }, + "comment": "获取七日榜" + }, + "fans_medal_rank": { + "url": "https://api.live.bilibili.com/rankdb/v1/RoomRank/webMedalRank", + "method": "GET", + "verify": false, + "params": { + "roomid": "int: 真实房间号", + "ruid": "int: 全称 room_uid,从 room_play_info 里头的 uid 可以找到" + }, + "comment": "获取粉丝勋章排行榜" + }, + "black_list": { + "url": "https://api.live.bilibili.com/xlive/web-ucenter/v1/banned/GetSilentUserList", + "method": "POST", + "verify": true, + "params": { + "room_id": "int: 真实房间号", + "ps": "const int: 1" + }, + "comment": "获取房间黑名单列表,登录账号需要是该房间房管" + }, + "room_play_url": { + "url": "https://api.live.bilibili.com/xlive/web-room/v1/playUrl/playUrl", + "method": "GET", + "verify": false, + "params": { + "cid": "int: 真实房间号", + "platform": "const str: web", + "qn": "int: 清晰度编号,原画 10000,蓝光 400,超清 250,高清 150,流畅 80", + "https_url_req": "const int: 1", + "ptype": "const int: 16" + }, + "comment": "获取房间直播流列表" + }, + "room_play_info_v2": { + "url": "https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo", + "method": "GET", + "verify": false, + "params": { + "room_id": "int: 真实房间号", + "protocol": "int: 流协议,0 为 FLV 流,1 为 HLS 流。默认:0,1", + "format": "int: 容器格式,0 为 flv 格式;1 为 ts 格式(仅限 hls 流);2 为 fmp4 格式(仅限 hls 流)。默认:0,2", + "codec": "int: 视频编码,0 为 avc 编码,1 为 hevc 编码。默认:0,1", + "qn": "int: 清晰度编号,原画:10000(建议),4K:800,蓝光(杜比):401,蓝光:400,超清:250,高清:150,流畅:80,默认:0", + "platform": "const str: web", + "ptype": "const int: 16" + }, + "comment": "获取房间信息及可用清晰度列表" + }, + "popular_ticket": { + "url": "https://api.live.bilibili.com/xlive/general-interface/v1/rank/getUserPopularTicketsNum", + "method": "GET", + "verify": true, + "params": { + "ruid": "int: 全称 room_uid,从 room_play_info 里头的uid可以找到", + "source": "const str: live" + }, + "comment": "获取自己在该直播间的人气票信息" + }, + "gift_common": { + "url": "https://api.live.bilibili.com/xlive/web-room/v1/giftPanel/giftData", + "method": "GET", + "verify": false, + "params": { + "room_id": "int: 显示房间号", + "platform": "const str: pc", + "source": "const str: live", + "area_id": "int: 子分区 ID 可以不用填", + "area_parent_id": "int: 父分区 ID 可以不用填, 获取分区 ID 可使用 get_area_info 方法" + }, + "comment": "获取该直播间通用礼物的信息,此 API 只返回 gift_id ,不包含礼物 price 参数" + }, + "gift_special": { + "url": "https://api.live.bilibili.com//xlive/web-room/v1/giftPanel/tabRoomGiftList", + "method": "GET", + "verify": false, + "params": { + "room_id": "int: 显示房间号", + "platform": "const str: pc", + "source": "const str: live", + "tab_id": "int: 礼物tab编号,2 为特权礼物,3 为定制礼物", + "build": "int: 未知作用, 默认 1", + "area_id": "int: 子分区 ID 可以不用填", + "area_parent_id": "int: 父分区 ID 可以不用填, 获取分区id可使用 get_area_info 方法" + }, + "comment": "获取该直播间特殊礼物的信息" + }, + "gift_config": { + "url": "https://api.live.bilibili.com/xlive/web-room/v1/giftPanel/giftConfig", + "method": "GET", + "verify": false, + "params": { + "room_id": "int: 显示房间号 可以不用填", + "platform": "const str: pc", + "source": "const str: live", + "area_id": "int: 子分区 ID 可以不用填", + "area_parent_id": "int: 父分区 ID 可以不用填, 获取分区id可使用 get_area_info 方法" + }, + "comment": "获取所有礼物信息,三个字段可以不用填,但填了有助于减小返回内容的大小,置空返回约 2.7w 行,填了三个对应值返回约 1.4w 行" + }, + "followers_live_info": { + "url": "https://api.live.bilibili.com/xlive/app-interface/v1/relation/liveAnchor", + "method": "GET", + "verify": false, + "params": { + "filterRule": "int: 0 ,未知", + "need_recommend": "int: 是否接受推荐直播间, 0为不接受, 1为接受" + }, + "comment": "获取关注列表中正在直播的直播间信息, 包括房间直播热度, 房间名称及标题, 清晰度, 是否官方认证等信息." + }, + "followers_unlive_info": { + "url": "https://api.live.bilibili.com/xlive/app-interface/v1/relation/unliveAnchor", + "method": "GET", + "verify": false, + "params": { + "page": "int: 页码", + "pagesize": "每页数量,过多可能报错 默认:30" + }, + "comment": "获取关注列表中未在直播的直播间信息, 包括上次开播时间, 上次开播的类别, 直播间公告, 是否有录播等." + } + }, + "operate": { + "send_danmaku": { + "url": "https://api.live.bilibili.com/msg/send", + "method": "POST", + "verify": true, + "params": { + "roomid": "int: 真实房间号", + "color": "int: 十进制颜色,有权限限制", + "fontsize": "int: 字体大小,默认 25", + "mode": "int: 弹幕模式,1 飞行 5 顶部 4 底部", + "msg": "str: 弹幕信息", + "rnd": "int: 当前时间戳", + "bubble": "int: 默认 0,功能不知", + "reply_mid": "int: uid", + "csrf,csrf_token": "str: 要给两个" + }, + "comment": "发送直播间弹幕,有的参数不确定因为自己不搞这块没权限发一些样式的弹幕" + }, + "send_popular_ticket": { + "url": "https://api.live.bilibili.com/xlive/general-interface/v1/rank/popularRankFreeScoreIncr", + "method": "POST", + "verify": true, + "params": { + "ruid": "int: 全称 room_uid,从 room_play_info 里头的 uid 可以找到", + "csrf,csrf_token": "要给两个", + "visit_id": "str: 空" + }, + "comment": "在直播间中赠送所有免费人气票" + }, + "add_block": { + "url": "https://api.live.bilibili.com/xlive/web-ucenter/v1/banned/AddSilentUser", + "method": "POST", + "verify": true, + "params": { + "room_id": "int: 真实房间号", + "tuid": "int: 封禁用户 UID", + "mobile_app": "str: 设备类型", + "visit_id": "str: 空" + }, + "comment": "封禁用户" + }, + "del_block": { + "url": "https://api.live.bilibili.com/xlive/web-ucenter/v1/banned/DelSilentUser", + "method": "POST", + "verify": true, + "params": { + "room_id": "int: 真实房间号", + "tuid": "int: 封禁用户 UID" + }, + "comment": "解封用户" + }, + "sign_up_dahanghai": { + "url": "https://api.live.bilibili.com/xlive/activity-interface/v2/userTask/UserTaskSignUp", + "method": "POST", + "verify": true, + "params": { + "task_id": "int: 任务 id,签到:1447,可能还有别的", + "uid": "int: 真实房间号", + "csrf,csrf_token": "要给两个" + }, + "comment": "航海日志签到" + }, + "send_gift_from_bag": { + "url": "https://api.live.bilibili.com/xlive/revenue/v1/gift/sendBag", + "method": "POST", + "verify": true, + "params": { + "uid": "int: 赠送用户的 UID", + "bag_id": "int: 礼物包裹的id", + "gift_id": "int: 礼物id", + "gift_num": "int: 赠送数量", + "platform": "const str: pc", + "send_ruid": "int: 未知作用,默认:0", + "storm_beat_id": "int: 未知作用,默认:0", + "price": "int: 礼物单价,背包中的礼物价值默认:0", + "biz_code": "const str: live", + "biz_id": "int: room_display_id 房间显示 ID", + "ruid": "int: 全称 room_uid,从 room_play_info 里头的 UID 可以找到", + "csrf,csrf_token": "要给两个" + }, + "comment": "在直播间中赠送包裹中的礼物,包裹信息可用 get_self_bag 方法获取" + }, + "send_gift_gold": { + "url": "https://api.live.bilibili.com/xlive/revenue/v1/gift/sendGold", + "method": "POST", + "verify": true, + "params": { + "uid": "int: 赠送用户的 UID", + "gift_id": "int: 礼物 ID", + "ruid": "int: 全称 room_uid,从 room_play_info 里头的uid可以找到", + "send_ruid": "int: 未知作用,默认:0", + "gift_num": "int: 赠送数量", + "coin_type": "const str: gold", + "bag_id": "int: 0", + "platform": "const str: pc", + "biz_code": "const str: Live", + "biz_id": "int: room_display_id 房间显示 ID", + "rnd": "int: 当前时间戳", + "storm_beat_id": "int: 未知作用,默认:0", + "price": "int: 礼物单价", + "visit_id": "void: 空", + "csrf,csrf_token": "要给两个" + }, + "comment": "在直播间中赠送金瓜子礼物" + }, + "send_gift_silver": { + "url": "https://api.live.bilibili.com/xlive/revenue/v1/gift/sendSilver", + "method": "POST", + "verify": true, + "params": { + "uid": "int: 赠送用户的 UID", + "gift_id": "int: 礼物 ID 辣条的 ID 为 1", + "ruid": "int: 全称 room_uid,从 room_play_info 里头的 UID 可以找到", + "send_ruid": "int: 未知作用,默认:0", + "gift_num": "int: 赠送数量", + "coin_type": "const str: silver", + "bag_id": "int: 0", + "platform": "const str: pc", + "biz_code": "const str: Live", + "biz_id": "int: room_display_id 房间显示id", + "rnd": "int: 当前时间戳", + "storm_beat_id": "int: 未知作用,默认:0", + "price": "int: 礼物单价 辣条单价为100", + "visit_id": "int: 空", + "csrf,csrf_token": "要给两个" + }, + "comment": "在直播间中赠送银瓜子礼物" + }, + "receive_reward": { + "url": "https://api.live.bilibili.com/xlive/activity-interface/v2/spec-act/sep-guard/receive/awards", + "method": "POST", + "verify": true, + "params": { + "ruid": "int: 房间真实id", + "receive_type": "int: 领取类型, 全部领取:2", + "csrf,csrf_token": "要给两个" + }, + "comment": "领取航海日志奖励" + }, + "create_reserve": { + "url": "https://api.bilibili.com/x/activity/up/reserve/create", + "method": "POST", + "verify": true, + "data": { + "type": "int: 2", + "title": "str: 预约标题", + "live_plan_start_time": "int: 预约开始时间,时间戳", + "stime": "int: 预约结束时间,时间戳", + "from": "int: 1" + }, + "comment": "创建预约" + } + } +} diff --git a/bilibili_api/data/api/login.json b/bilibili_api/data/api/login.json new file mode 100644 index 0000000000000000000000000000000000000000..84ff4be85029e5d1cdc281992a048a8fc0495727 --- /dev/null +++ b/bilibili_api/data/api/login.json @@ -0,0 +1,139 @@ +{ + "qrcode": { + "web": { + "get_qrcode_and_token": { + "url": "https://passport.bilibili.com/x/passport-login/web/qrcode/generate?source=main-fe-header", + "method": "GET", + "verify": false, + "comment": "请求二维码及登录密钥" + }, + "get_events": { + "url": "https://passport.bilibili.com/x/passport-login/web/qrcode/poll", + "method": "GET", + "verify": false, + "params": { + "qrcode_key": "str: 登陆密钥", + "source": "main-fe-header" + }, + "comment": "获取最新信息" + } + }, + "old": { + "get_qrcode_and_token": { + "url": "https://passport.bilibili.com/qrcode/getLoginUrl", + "method": "GET", + "verify": false, + "comment": "旧请求二维码及登录密钥" + }, + "get_events": { + "url": "https://passport.bilibili.com/qrcode/getLoginInfo", + "method": "POST", + "verify": false, + "data": { + "oauthKey": "str: 登陆密钥" + }, + "comment": "旧获取最新信息" + } + }, + "tv": { + "get_qrcode_and_auth_code": { + "url": "https://passport.bilibili.com/x/passport-tv-login/qrcode/auth_code", + "method": "POST", + "verify": false, + "data": { + "appkey": "str: appkey", + "sign": "str: 签名", + "ts": "str: 时间戳", + "local_id": "int: 0" + }, + "comment": "tv 请求二维码及登录密钥" + }, + "get_events": { + "url": "https://passport.bilibili.com/x/passport-tv-login/qrcode/poll", + "method": "POST", + "verify": false, + "data": { + "appkey": "str: appkey", + "sign": "str: 签名", + "ts": "str: 时间戳", + "local_id": "int: 0", + "auth_code": "str: 登陆密钥" + }, + "comment": "tv 获取最新信息" + } + } + }, + "password": { + "get_token": { + "url": "https://passport.bilibili.com/x/passport-login/web/key", + "method": "GET", + "verify": false, + "comment": "获取登录盐值、加密公钥" + }, + "captcha": { + "url": "https://passport.bilibili.com/x/passport-login/captcha", + "method": "GET", + "verify": false, + "comment": "获取极验信息" + }, + "login": { + "url": "https://passport.bilibili.com/x/passport-login/web/login", + "method": "POST", + "verify": false, + "data": { + "username": "string: 账号", + "password": "string: 加密的密码", + "keep": "bool: true", + "key": "string: 密钥", + "token": "string: 极验信息中的登陆密钥 token", + "challenge": "string: 极验 challenge", + "validate": "string: 极验 validate", + "seccode": "string: 极验 seccode" + }, + "comment": "登录" + } + }, + "sms": { + "send": { + "url": "https://passport.bilibili.com/x/passport-login/web/sms/send", + "method": "POST", + "verify": false, + "data": { + "tel": "string: 手机号码", + "cid": "int: 地区对应的 id (contries_codes.json 中对应的一个地区的 id 项,不是 country_id。", + "source": "string: main_web", + "token": "string: 极验信息中的登陆密钥 token", + "challenge": "string: 极验 challenge", + "validate": "string: 极验 validate", + "seccode": "string: 极验 seccode" + }, + "comment": "发送验证码" + }, + "login": { + "url": "https://passport.bilibili.com/x/passport-login/web/login/sms", + "method": "POST", + "verify": false, + "data": { + "tel": "string: 手机号码", + "cid": "int: 地区对应的 id (contries_codes.json 中对应的一个地区的 id 项,不是 country_id。", + "code": "string: 验证码", + "source": "string: main_web", + "captcha_key": "string: 发送验证码时产生的返回值", + "keep": "bool: true", + "go_url": "跳转 url" + }, + "comment": "验证码登录" + } + }, + "safecenter": { + "check_info": { + "url": "https://api.bilibili.com/x/safecenter/user/info", + "method": "GET", + "verify": false, + "params": { + "tmp_code": "str: 验证标记代码" + }, + "comment": "获取验证信息" + } + } +} diff --git a/bilibili_api/data/api/manga.json b/bilibili_api/data/api/manga.json new file mode 100644 index 0000000000000000000000000000000000000000..93176d45ab47e13d229bebf4f989070ca57d4b3a --- /dev/null +++ b/bilibili_api/data/api/manga.json @@ -0,0 +1,114 @@ +{ + "info": { + "detail": { + "url": "https://manga.bilibili.com/twirp/comic.v1.Comic/ComicDetail?device=pc&platform=web", + "method": "POST", + "verify": false, + "params": { + "comic_id": "int: 漫画 id" + }, + "comment": "获取漫画详情信息" + }, + "episode_info": { + "url": "https://manga.bilibili.com/twirp/comic.v1.Comic/GetEpisode?device=pc&platform=web", + "method": "POST", + "verify": false, + "params": { + "id": "int: 章节 id" + }, + "comment": "获取漫画某一章节/某一话的信息。" + }, + "episode_images": { + "url": "https://manga.bilibili.com/twirp/comic.v1.Comic/GetImageIndex?device=pc&platform=web", + "method": "POST", + "verify": false, + "params": { + "ep_id": "int: 章节 id" + }, + "comment": "获取漫画某一章节/某一话的图片的链接。" + }, + "image_token": { + "url": "https://manga.bilibili.com/twirp/comic.v1.Comic/ImageToken?device=pc&platform=web", + "method": "POST", + "verify": false, + "params": { + "urls": "str: 内容如下:[\"图片链接\"]" + }, + "comment": "获取漫画单张图片的 token" + }, + "index": { + "url": "https://manga.bilibili.com/twirp/comic.v1.Comic/ClassPage", + "method": "POST", + "verify": true, + "params": { + "device": "str: 设备", + "platform": "str: 平台" + }, + "data": { + "area_id": "int: 地区 id", + "style_id": "int: 风格 id", + "is_finish": "int: 是否完结", + "order": "int: 排序方式", + "page_num": "int: 页码", + "page_size": "int: 每页数量", + "is_free": "int: 是否免费" + }, + "comment": "获取漫画索引" + }, + "index_params": { + "url": "https://manga.bilibili.com/twirp/comic.v1.Comic/AllLabel?device=pc&platform=web", + "method": "POST", + "verify": true, + "comment": "获取漫画索引的参数" + }, + "update": { + "url": "https://manga.bilibili.com/twirp/comic.v1.Comic/GetDailyPush", + "method": "POST", + "verify": true, + "params": { + "device": "str: 设备", + "platform": "str: 平台" + }, + "data": { + "date": "str: 日期,格式为 YYYY-MM-DD", + "page_num": "int: 页码", + "page_size": "int: 每页数量" + }, + "comment": "获取漫画更新推荐" + }, + "home_recommend": { + "url": "https://manga.bilibili.com/twirp/comic.v1.Comic/HomeRecommend", + "method": "POST", + "verify": true, + "params": { + "device": "str: 设备", + "platform": "str: 平台" + }, + "data": { + "seed": "str: unknown param", + "page_num": "int: 页码" + }, + "comment": "获取漫画首页推荐" + } + }, + "operate": { + "add_favorite": { + "url": "https://manga.bilibili.com/twirp/bookshelf.v1.Bookshelf/AddFavorite?device=pc&platform=web", + "method": "POST", + "verify": true, + "params": { + "comic_ids": "int: 漫画 id" + }, + "comment": "追漫" + }, + "del_favorite": { + "url": "https://manga.bilibili.com/twirp/bookshelf.v1.Bookshelf/DeleteFavorite?device=pc&platform=web", + "method": "POST", + "verify": true, + "params": { + "comic_ids": "int: 漫画 id" + }, + "comment": "取消追漫" + } + } +} diff --git a/bilibili_api/data/api/music.json b/bilibili_api/data/api/music.json new file mode 100644 index 0000000000000000000000000000000000000000..25aadf157e41dfdebbf2e5733458f101cc66fb52 --- /dev/null +++ b/bilibili_api/data/api/music.json @@ -0,0 +1,20 @@ +{ + "info": { + "detail": { + "url": "https://api.bilibili.com/x/copyright-music-publicity/bgm/detail", + "method": "GET", + "params": { + "music_id": "str: 音乐 id" + }, + "comment": "获取音乐信息" + }, + "video_recommend_list": { + "url": "https://api.bilibili.com/x/copyright-music-publicity/bgm/recommend_list", + "method": "GET", + "params": { + "music_id": "str: 音乐 id" + }, + "comment": "获取音乐推荐视频" + } + } +} diff --git a/bilibili_api/data/api/note.json b/bilibili_api/data/api/note.json new file mode 100644 index 0000000000000000000000000000000000000000..4ac8c364b900820f48ec05adb7632b22abf140a0 --- /dev/null +++ b/bilibili_api/data/api/note.json @@ -0,0 +1,27 @@ +{ + "private":{ + "detail":{ + "url": "https://api.bilibili.com/x/note/info", + "method": "GET", + "verify": true, + "params": { + "oid": "oid", + "oid_type": "oid_type", + "note_id": "note_id" + }, + "comment": "私有笔记详细信息" + + } + }, + "public":{ + "detail":{ + "url": "https://api.bilibili.com/x/note/publish/info", + "method": "GET", + "verify": true, + "params": { + "cvid": "cvid" + }, + "comment": "公开笔记详细信息" + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/opus.json b/bilibili_api/data/api/opus.json new file mode 100644 index 0000000000000000000000000000000000000000..d2db35ba8d9222369cbd401979397c94f88e5b28 --- /dev/null +++ b/bilibili_api/data/api/opus.json @@ -0,0 +1,35 @@ +{ + "info": { + "detail": { + "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/data/api/rank.json b/bilibili_api/data/api/rank.json new file mode 100644 index 0000000000000000000000000000000000000000..0ea2057d42aa3fad54eb60ed16a82e5eb52a0ac1 --- /dev/null +++ b/bilibili_api/data/api/rank.json @@ -0,0 +1,195 @@ +{ + "info": { + "hot": { + "url": "https://api.bilibili.com/x/web-interface/popular", + "method": "GET", + "verify": false, + "params": { + "ps": "int: 每页视频数", + "pn": "int: 页码" + }, + "comment": "小破站的热门视频排行" + }, + "weekly_series": { + "url": "https://api.bilibili.com/x/web-interface/popular/series/list", + "method": "GET", + "verify": false, + "comment": "小破站每周必看全部信息(简介)" + }, + "weekly_details": { + "url": "https://api.bilibili.com/x/web-interface/popular/series/one", + "method": "GET", + "verify": false, + "params": { + "number": "int: 第几周" + }, + "comment": "小破站每周必看一期的详细信息" + }, + "history_popular": { + "url": "https://api.bilibili.com/x/web-interface/popular/precious", + "method": "GET", + "verify": false, + "params": { + "page_size": "int: 就设置 85 好了(每页的大小)", + "page": "int: 页码" + }, + "comment": "获取入站必刷 85 个视频" + }, + "v2_ranking": { + "url": "https://api.bilibili.com/x/web-interface/ranking/v2", + "method": "GET", + "verify": false, + "params": { + "rid": "int: 频道的 tid", + "type": "string: all" + }, + "comment": "获取各个分区的排行榜" + }, + "pgc_ranking": { + "url": "https://api.bilibili.com/pgc/web/rank/list", + "method": "GET", + "verify": false, + "params": { + "day": "int: 天数 3/7", + "season_type": "int: 类型" + }, + "comment": "获取番剧等排行榜" + }, + "music_weekly_series": { + "url": "https://api.bilibili.com/x/copyright-music-publicity/toplist/all_period", + "method": "GET", + "verify": false, + "params": { + "list_type": "const int: 1" + }, + "comment": "获取全站音乐榜每周信息(不包括具体的音频列表)" + }, + "music_weekly_details": { + "url": "https://api.bilibili.com/x/copyright-music-publicity/toplist/detail", + "method": "GET", + "verify": false, + "params": { + "list_id": "int: 第几周" + }, + "comment": "获取全站音乐榜一周的详细信息(不包括具体的音频列表)" + }, + "music_weekly_content": { + "url": "https://api.bilibili.com/x/copyright-music-publicity/toplist/music_list", + "method": "GET", + "verify": false, + "params": { + "list_id": "int: 第几周" + }, + "comment": "获取全站音乐榜一周的音频列表" + }, + "VIP_rank": { + "url": "https://api.bilibili.com/x/vip/vip_center/hotlist?=279", + "method": "GET", + "verify": false, + "params": { + "rank_id": "int: 排行榜 rank_id" + }, + "comment": "获取大会员中心的新热榜单" + }, + "manga_rank_type": { + "url": "https://manga.bilibili.com/twirp/comic.v1.Comic/ListRank", + "method": "POST", + "verify": false, + "params": { + "device": "str: 设备", + "platform": "str: 平台" + }, + "data": {}, + "comment": "获取漫画排行榜类型,data 字段为空字典" + }, + "manga_rank": { + "url": "https://manga.bilibili.com/twirp/comic.v1.Comic/GetRankInfo", + "method": "POST", + "verify": false, + "params": { + "device": "str: 设备", + "platform": "str: 平台" + }, + "data": { + "id": "int: 排行榜 id" + }, + "comment": "获取漫画排行榜" + }, + "live_hot_rank": { + "url": "https://api.live.bilibili.com/xlive/web-interface/v1/index/getHotRankList", + "method": "GET", + "verify": false, + "comment": "获取直播人气排行榜" + }, + "live_sailing_rank": { + "url": "https://api.live.bilibili.com/room/v2/Index/getNewRankTop?type=guard", + "method": "GET", + "verify": false, + "params": { + "type": "str: guard" + }, + "comment": "获取直播大航海主播排行榜" + }, + "live_energy_user_rank": { + "url": "https://api.live.bilibili.com/xlive/general-interface/v1/rank/getFeedingMonthlyRankEnergy", + "method": "GET", + "verify": false, + "params": { + "date": "str: month, pre_month", + "page": "int: 页码", + "page_size": "int: 页大小" + }, + "comment": "获取直播用户能量榜" + }, + "live_web_top": { + "url": "https://api.live.bilibili.com/rankdb/v1/Rank2018/getWebTop", + "method": "GET", + "verify": false, + "params": { + "type": "str: 榜单类型", + "page": "int: 页码", + "page_size": "int: 页大小", + "is_trend": "int: 1 Unknown" + }, + "comment": "获取直播通用排行榜" + }, + "live_medal_level_rank": { + "url": "https://api.live.bilibili.com/xlive/general-interface/v1/Rank/GetTotalMedalLevelRank", + "method": "GET", + "verify": false, + "params": { + "page": "int: 页码", + "page_size": "int: 页大小" + }, + "comment": "获取直播勋章等级排行榜" + }, + "playlet_rank_phase": { + "url": "https://api.bilibili.com/pgc/activity/rank/ugc/playlet/queryList", + "method": "POST", + "verify": false, + "data": {}, + "comment": "获取短剧榜期数" + }, + "playlet_rank_info": { + "url": "https://api.bilibili.com/pgc/activity/rank/ugc/playlet/queryInfo", + "method": "POST", + "verify": false, + "data": { + "phaseID": "int: 期数" + }, + "comment": "获取短剧榜信息" + } + }, + "operate": { + "subscribe": { + "url": "https://api.bilibili.com/x/copyright-music-publicity/toplist/subscribe/update", + "method": "POST", + "verify": true, + "params": { + "list_id": "int: 1", + "state": "int: 1 订阅 2 取消" + }, + "comment": "订阅全站音乐榜" + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/search.json b/bilibili_api/data/api/search.json new file mode 100644 index 0000000000000000000000000000000000000000..42a2bc3aaac153ca880825f27087d111d35ceaf5 --- /dev/null +++ b/bilibili_api/data/api/search.json @@ -0,0 +1,96 @@ +{ + "search": { + "web_search": { + "url": "https://api.bilibili.com/x/web-interface/search/all/v2", + "method": "GET", + "verify": false, + "params": { + "keyword": "str: 搜索用的关键字", + "page": "int: 页码" + }, + "comment": "在首页以关键字搜索,只指定关键字,其他参数不指定" + }, + "web_search_by_type": { + "url": "https://api.bilibili.com/x/web-interface/search/type", + "method": "GET", + "verify": false, + "params": { + "keyword": "str: 搜索用的关键字", + "search_type": "str: 搜索时限定类型:视频(video)、番剧(media_bangumi)、影视(media_ft)、直播(live)、专栏(article)、话题(topic)、用户(bili_user)", + "page": "int: 页码" + }, + "comment": "搜索关键字时限定类型,可以指定排序号" + }, + "default_search_keyword": { + "url": "https://api.bilibili.com/x/web-interface/search/default", + "method": "GET", + "verify": false, + "comment": "获取默认的搜索内容" + }, + "hot_search_keywords": { + "url": "https://s.search.bilibili.com/main/hotword", + "method": "GET", + "verify": false, + "comment": "获取热搜" + }, + "app_hot_search_keywords": + { + "url": "https://app.bilibili.com/x/v2/search/trending/ranking?limit=30", + "method": "GET", + "verify": false, + "params": { + "limit": "int: 热搜数量" + }, + "comment": "获取APP接口热搜" + }, + "suggest": { + "url": "https://s.search.bilibili.com/main/suggest", + "method": "GET", + "verify": false, + "comment": "获取搜索建议" + }, + "game": { + "url": "https://line1-h5-pc-api.biligame.com/game/wiki/search", + "method": "GET", + "params": { + "keyword": "str: 搜索用的关键字" + }, + "verify": false, + "comment": "搜索游戏" + }, + "manga": { + "url": "https://manga.bilibili.com/twirp/comic.v1.Comic/Search?device=pc&platform=web", + "method": "POST", + "verify":true, + "data": { + "key_word": "str: 搜索用的关键词", + "page_num": "int: 页码", + "page_size": "int: 每一页的数据大小" + }, + "comment": "搜索漫画" + }, + "cheese": { + "url": "https://api.bilibili.com/pugv/app/web/seasonSeek?classification_id=-1", + "method": "GET", + "verify": false, + "params": { + "page": "int: 页码", + "page_size": "int: 每一页的数据大小", + "word": "str: 搜索关键词", + "sort_type": "int: 排序方式. 综合 -1;销量最高 1;最新上架 2;售价最低 3" + }, + "comment": "搜索课程" + }, + "channel": { + "url": "https://api.bilibili.com/x/web-interface/web/channel/search", + "method": "GET", + "verify": false, + "params": { + "page": "int: 页码", + "page_size": "int: 每一页的数据大小", + "keyword": "str: 搜索关键词" + }, + "comment": "搜索频道" + } + } +} diff --git a/bilibili_api/data/api/session.json b/bilibili_api/data/api/session.json new file mode 100644 index 0000000000000000000000000000000000000000..369e6b4f61372a386bf21dfa7e6ed8c1f07778e0 --- /dev/null +++ b/bilibili_api/data/api/session.json @@ -0,0 +1,103 @@ +{ + "session": { + "fetch": { + "url": "https://api.vc.bilibili.com/svr_sync/v1/svr_sync/fetch_session_msgs", + "method": "GET", + "verify": true, + "params": { + "talker_id": "int: 私聊时为用户UID 应援团时为团号", + "session_type": "int: 会话类型,1 私聊 2 应援团", + "begin_seqno": "int: 起始 Seqno 可由具体消息获得" + }, + "comment": "获取指定用户的近三十条消息" + }, + "new": { + "url": "https://api.vc.bilibili.com/session_svr/v1/session_svr/new_sessions", + "method": "GET", + "verify": true, + "params": { + "begin_ts": "int: 起始时间戳" + }, + "comment": "获取新消息" + }, + "get": { + "url": "https://api.vc.bilibili.com/session_svr/v1/session_svr/get_sessions", + "method": "GET", + "verify": true, + "params": { + "session_type": "int: 1: 私聊, 2: 通知, 3: 应援团, 4: 全部", + "group_fold": "int: 默认为 1", + "unfollow_fold": "int: 默认为 0", + "sort_rule": "int: 默认为 2", + "build": "int: 默认为 0", + "mobi_app": "web" + }, + "comment": "获取已有消息" + }, + "get_session_detail": { + "url": "https://api.vc.bilibili.com/session_svr/v1/session_svr/session_detail", + "method": "GET", + "verify": true, + "params": { + "talker_id": "int: 私聊时为用户UID 应援团时为团号", + "session_type": "int: 会话类型" + }, + "comment": "获取会话详情" + }, + "likes": { + "url": "https://api.bilibili.com/x/msgfeed/like", + "method": "GET", + "verify": true, + "comment": "获取点赞" + }, + "unread": { + "url": "https://api.bilibili.com/x/msgfeed/unread", + "method": "GET", + "verify": true, + "comment": "获取未读的信息" + }, + "replies": { + "url": "https://api.bilibili.com/x/msgfeed/reply", + "method": "GET", + "verify": true, + "comment": "获取收到的回复" + }, + "at": { + "url": "https://api.bilibili.com/x/msgfeed/at", + "method": "GET", + "verify": true, + "comment": "获取未读 AT" + }, + "system_msg": { + "url": "https://message.bilibili.com/x/sys-msg/query_user_notify", + "method": "GET", + "verify": true, + "params": { + "page_size": "int: 要获取的信息数量" + }, + "comment": "获取系统信息" + }, + "session_settings": { + "url": "https://api.vc.bilibili.com/link_setting/v1/link_setting/get", + "method": "GET", + "verify": true, + "comment": "获取消息设置" + } + }, + "operate": { + "send_msg": { + "url": "https://api.vc.bilibili.com/web_im/v1/web_im/send_msg", + "method": "POST", + "verify": true, + "data": { + "msg[sender_uid]": "int: 自己的 UID", + "msg[receiver_id]": "int: 对方 UID", + "msg[receiver_type]": "const int: 1", + "msg[msg_type]": "int: 消息类型", + "msg[msg_status]": "const int: 0", + "msg[content]": "str: 消息内容" + }, + "comment": "给用户发信息" + } + } + } diff --git a/bilibili_api/data/api/show.json b/bilibili_api/data/api/show.json new file mode 100644 index 0000000000000000000000000000000000000000..4f1d24a23474d95cfa186e1903965d3bd9871784 --- /dev/null +++ b/bilibili_api/data/api/show.json @@ -0,0 +1,47 @@ +{ + "info": { + "get": { + "url": "https://show.bilibili.com/api/ticket/project/get", + "method": "GET", + "verify": false, + "ignore_code": true, + "params": { + "id": "展出id" + }, + "comment": "展出信息" + }, + "buyer_info": { + "url": "https://show.bilibili.com/api/ticket/buyer/list", + "method": "GET", + "verify": true, + "ignore_code": true, + "comment": "读取用户所有购买人身份" + }, + "token": { + "url": "https://show.bilibili.com/api/ticket/order/prepare", + "method": "POST", + "verify": true, + "ignore_code": true, + "no_csrf": true, + "data": { + "project_id": "项目id", + "screen_id": "展出id", + "sku_id": "票id" + }, + "comment": "获取购票 token" + } + }, + "operate": { + "order": { + "url": "https://show.bilibili.com/api/ticket/order/createV2", + "method": "POST", + "verify": true, + "no_csrf": true, + "ignore_code": true, + "params": { + "project_id": "展出id" + }, + "comment": "创建订单" + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/topic.json b/bilibili_api/data/api/topic.json new file mode 100644 index 0000000000000000000000000000000000000000..fe66d7e7f8f74679cba7e8e9ff1ff7d80d146647 --- /dev/null +++ b/bilibili_api/data/api/topic.json @@ -0,0 +1,80 @@ +{ + "info": { + "info": { + "url": "https://app.bilibili.com/x/topic/web/details/top", + "method": "GET", + "params": { + "topic_id": "int: 话题 id" + }, + "verify": false, + "comment": "获取话题简介" + }, + "cards": { + "url": "https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/topic", + "method": "GET", + "params": { + "topic_id": "int: 话题 id", + "page_size": "int: 数据数量", + "sort_by": "int: 排序方式 1推荐 2热门 3最新", + "source": "Web", + "features": "opusBigCover" + }, + "verify": false, + "comment": "获取话题详情" + }, + "dynamic_page_topics": { + "url": "https://app.bilibili.com/x/topic/web/dynamic/rcmd", + "method": "GET", + "verify": false, + "params": { + "page_size": "int: 数据数量" + }, + "comment": "获取动态页话题" + }, + "search": { + "url": "https://app.bilibili.com/x/topic/pub/search", + "method": "GET", + "params": { + "keyword": "str: 搜索关键词", + "page_num": "int: 页码", + "page_size": "int: 数据数量", + "content": "Unknown", + "upload_id": "Unknown" + }, + "verify": false, + "comment": "搜索话题" + } + }, + "operate": { + "like": { + "url": "https://app.bilibili.com/x/topic/like", + "method": "POST", + "verify": true, + "data": { + "topic_id": "int: 话题 id", + "action": "str: like / cancel_like", + "business": "str: topic", + "up_mid": "int: 自己的 uid" + }, + "comment": "设置点赞话题" + }, + "add_favorite": { + "url": "https://app.bilibili.com/x/topic/fav/sub/add", + "method": "POST", + "verify": true, + "data": { + "topic_id": "int: 话题 id" + }, + "comment": "收藏话题" + }, + "cancel_favorite": { + "url": "https://app.bilibili.com/x/topic/fav/sub/cancel", + "method": "POST", + "verify": true, + "params": { + "topic_id": "int: 话题 id" + }, + "comment": "取消收藏话题" + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/toview.json b/bilibili_api/data/api/toview.json new file mode 100644 index 0000000000000000000000000000000000000000..cc3ebcd648451120630030dbe1897a4f699d0ae4 --- /dev/null +++ b/bilibili_api/data/api/toview.json @@ -0,0 +1,41 @@ +{ + "info": { + "list": { + "url": "https://api.bilibili.com/x/v2/history/toview", + "method": "GET", + "verify": true, + "comment": "获取稍后再看列表" + } + }, + "operate": { + "add": { + "url": "https://api.bilibili.com/x/v2/history/toview/add", + "method": "POST", + "verify": true, + "data": { + "aid": "int: aid", + "csrf": "string: bili_jct" + }, + "comment": "添加视频至稍后再看列表" + }, + "del": { + "url": "https://api.bilibili.com/x/v2/history/toview/del", + "method": "POST", + "verify": true, + "data": { + "aid": "int: aid", + "viewed": "bool: 是否删除已观看的视频" + }, + "comment": "删除稍后再看列表中的视频" + }, + "clear": { + "url": "https://api.bilibili.com/x/v2/history/toview/clear", + "method": "POST", + "verify": true, + "data": { + "csrf": "string: bili_jct" + }, + "comment": "清空稍后再看列表" + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/user.json b/bilibili_api/data/api/user.json new file mode 100644 index 0000000000000000000000000000000000000000..20fec20a989136743a27ef25afd2850fb89236a7 --- /dev/null +++ b/bilibili_api/data/api/user.json @@ -0,0 +1,651 @@ +{ + "info": { + "login_log": { + "url": "https://api.bilibili.com/x/member/web/login/log", + "method": "GET", + "verify": true + }, + "moral_log": { + "url": "https://api.bilibili.com/x/member/web/moral/log", + "method": "GET", + "verify": true + }, + "exp_log": { + "url": "https://api.bilibili.com/x/member/web/exp/log", + "method": "GET", + "verify": true + }, + "name_to_uid": { + "url": "https://api.vc.bilibili.com/dynamic_mix/v1/dynamic_mix/name_to_uid", + "method": "GET", + "verify": false, + "params": { + "names": "string: 多个名称, 用,分割" + } + }, + "my_info": { + "url": "https://api.bilibili.com/x/space/myinfo", + "method": "GET", + "verify": true, + "comment": "获取自己的信息" + }, + "edit_my_info": { + "url": "https://api.bilibili.com/x/member/web/update", + "method": "POST", + "verify": true, + "data": { + "birthday": "string: 用户生日", + "sex": "string: 用户性别", + "uname": "string: 用户昵称", + "usersign": "string: 用户签名" + }, + "comment": "修改自己的信息" + }, + "info": { + "url": "https://api.bilibili.com/x/space/wbi/acc/info", + "method": "GET", + "verify": false, + "wbi": true, + "params": { + "mid": "int: uid" + }, + "comment": "用户基本信息" + }, + "space_notice": { + "url": "http://api.bilibili.com/x/space/notice", + "method": "GET", + "verify": false, + "params": { + "mid": "int: uid" + }, + "comment": "用户个人空间公告" + }, + "user_tag": { + "url": "https://api.bilibili.com/x/space/tag/sub/list", + "method": "GET", + "verify": false, + "params": { + "vmid": "int: uid", + "pn": "int: 页码", + "ps": "int: 每页项数" + }, + "comment": "用户关注的 TAG / 话题,认证方式:SESSDATA" + }, + "user_top_videos": { + "url": "https://api.bilibili.com/x/space/top/arc", + "method": "GET", + "verify": false, + "params": { + "vmid": "int: uid" + }, + "comment": "用户置顶视频" + }, + "masterpiece": { + "url": "https://api.bilibili.com/x/space/masterpiece", + "method": "GET", + "verify": false, + "params": { + "vmid": "int: uid" + }, + "comment": "用户代表作" + }, + "relation_stat": { + "url": "https://api.bilibili.com/x/relation/stat", + "method": "GET", + "verify": false, + "params": { + "vmid": "int: uid" + }, + "comment": "获取用户关系信息(关注数,粉丝数,悄悄关注,黑名单数)" + }, + "upstat": { + "url": "https://api.bilibili.com/x/space/upstat", + "method": "GET", + "verify": false, + "params": { + "mid": "int: uid" + }, + "comment": "视频播放量,文章阅读量,总点赞数" + }, + "user_medal": { + "url": "https://api.live.bilibili.com/xlive/web-ucenter/user/MedalWall", + "method": "GET", + "verify": true, + "params": { + "target_id": "int: uid" + }, + "comment": "读取用户粉丝牌详细信息,如果隐私则不可以" + }, + "live": { + "url": "https://api.bilibili.com/x/space/wbi/acc/info", + "method": "GET", + "verify": false, + "wbi": true, + "params": { + "mid": "int: uid" + }, + "comment": "直播间基本信息" + }, + "video": { + "url": "https://api.bilibili.com/x/space/wbi/arc/search", + "method": "GET", + "verify": false, + "wbi": true, + "params": { + "mid": "int: uid", + "ps": "const int: 30", + "tid": "int: 分区 ID,0 表示全部", + "pn": "int: 页码", + "keyword": "str: 关键词,可为空", + "order": "str: pubdate 上传日期,click 播放量,stow 收藏量", + "dm_img_list": "[]", + "dm_img_str": "V2ViR0wgMS4wIChPcGVuR0wgRVMgMi4wIENocm9taXVtKQ", + "dm_cover_img_str": "QU5HTEUgKEludGVsLCBJbnRlbChSKSBVSEQgR3JhcGhpY3MgNjMwICgweDAwMDAzRTlCKSBEaXJlY3QzRDExIHZzXzVfMCBwc181XzAsIEQzRDExKUdvb2dsZSBJbmMuIChJbnRlbC" + }, + "comment": "搜索用户视频" + }, + "media_list": { + "url": "https://api.bilibili.com/x/v2/medialist/resource/list", + "method": "GET", + "verify": false, + "wbi": true, + "params": { + "mobi_app": "str: 定值 web", + "type": "int: 视频类型,通过uid获取用户视频时为1", + "biz_id": "int: uid", + "oid": "int: 起始视频 aid, 默认为列表开头", + "otype": "int: oid类型, 接受任意值不影响结果", + "ps": "int: 每页视频数量, 最大为100", + "direction": "bool: 相对于给定oid的查询方向,true:向列表末尾方向,false:向列表开头方向", + "desc": "bool: 列表是否逆序排列", + "sort_field": "int: 用于排序的栏 1 发布时间,2 播放量,3 收藏量", + "tid": "int: 分区 ID,0 表示全部, 1 部分(未知),不接受2及以上", + "with_current": "bool: 返回的列表中是否包含给定oid自身" + }, + "comment": "以medialist形式获取用户视频列表" + }, + "reservation": { + "url": "https://api.bilibili.com/x/space/reservation", + "method": "GET", + "verify": false, + "wbi": true, + "params": { + "vmid": "int: uid" + }, + "comment": "获取用户空间预约" + }, + "album": { + "url": "https://api.vc.bilibili.com/link_draw/v1/doc/doc_list", + "method": "GET", + "verify": false, + "params": { + "uid": "int: uid 此项必须", + "page_size": "int: 每页项数 此项必须", + "page_num": "int: 页码", + "biz": "str: 全部:all 绘画:draw 摄影:photo 日常:daily 默认为 all" + }, + "comment": "相簿" + }, + "audio": { + "url": "https://api.bilibili.com/audio/music-service/web/song/upper", + "method": "GET", + "verify": false, + "params": { + "uid": "int: uid", + "ps": "const int: 30", + "pn": "int: 页码", + "order": "int: 1 最新发布,2 最多播放,3 最多收藏" + }, + "comment": "音频" + }, + "article": { + "url": "https://api.bilibili.com/x/space/wbi/article", + "method": "GET", + "verify": false, + "params": { + "mid": "int: uid", + "ps": "const int: 30", + "pn": "int: 页码", + "sort": "str: publish_time 最新发布,publish_time 最多阅读,publish_time 最多收藏" + }, + "comment": "专栏" + }, + "article_lists": { + "url": "https://api.bilibili.com/x/article/up/lists", + "method": "GET", + "verify": false, + "params": { + "mid": "int: uid", + "sort": "int: 0 最近更新,1 最多阅读" + }, + "comment": "专栏文集" + }, + "dynamic": { + "url": "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history", + "method": "GET", + "verify": false, + "params": { + "host_uid": "int: uid", + "offset_dynamic_id": "int: 动态偏移用,第一页为 0", + "need_top": "int bool: 是否显示置顶动态" + }, + "comment": "用户动态信息" + }, + "dynamic_new": { + "url": "https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/space", + "method": "GET", + "verify": false, + "params": { + "host_mid": "int: uid", + "offset": "int: 动态偏移用,第一页为 0", + "timezone_offset": "int: -400", + "features": "str: itemOpusStyle" + }, + "comment": "用户动态信息" + }, + "bangumi": { + "url": "https://api.bilibili.com/x/space/bangumi/follow/list", + "method": "GET", + "verify": false, + "params": { + "vmid": "int: uid", + "pn": "int: 页码", + "ps": "const int: 15", + "type": "int: 1 追番,2 追剧", + "follow_status": "0 全部 1 想看 2 在看 3 看过" + }, + "comment": "用户追番列表" + }, + "followings": { + "url": "https://api.bilibili.com/x/relation/followings", + "method": "GET", + "verify": true, + "params": { + "vmid": "int: uid", + "ps": "const int: 20", + "pn": "int: 页码", + "order": "str: desc 倒序, asc 正序" + }, + "comment": "获取用户关注列表(不是自己只能访问前 5 页)" + }, + "all_followings": { + "url": "https://account.bilibili.com/api/member/getCardByMid", + "method": "GET", + "verify": false, + "params": { + "mid": "int: uid" + }, + "comment": "获取用户所有关注(需要用户公开信息)" + }, + "all_followings2": { + "url": "https://api.bilibili.com/x/relation/followings", + "method": "GET", + "verify": true, + "params": { + "vmid": "int: uid", + "order": "str: desc 倒序, asc 正序", + "order_type": "str: 按照关注顺序排列:留空 按照最常访问排列:attention", + "pn": "int: 页码", + "ps": "const int: 100" + }, + "comment": "获取用户关注" + }, + "followers": { + "url": "https://api.bilibili.com/x/relation/followers", + "method": "GET", + "verify": true, + "params": { + "vmid": "int: uid", + "ps": "const int: 20", + "pn": "int: 页码", + "order": "str: desc 倒序, asc 正序" + }, + "comment": "获取用户粉丝列表(不是自己只能访问前 5 页,是自己也不能获取全部的样子)" + }, + "top_followers": { + "url": "https://member.bilibili.com/x/web/data/fan", + "method": "GET", + "verify": false, + "params": { + "t": "int: since when in timestamp(msec)", + "csrf,csrf_token": "要给两个" + }, + "comment": "粉丝排行" + }, + "overview": { + "url": "https://api.bilibili.com/x/space/navnum", + "method": "GET", + "verify": false, + "params": { + "mid": "int: uid", + "jsonp": "const str: jsonp" + }, + "comment": "获取用户的简易订阅和投稿信息(主要是这些的数量统计)" + }, + "self_subscribe_group": { + "url": "https://api.bilibili.com/x/relation/tags", + "method": "GET", + "verify": true, + "params": {}, + "comment": "获取自己的关注分组列表,用于操作关注" + }, + "get_user_in_which_subscribe_groups": { + "url": "https://api.bilibili.com/x/relation/tag/user", + "method": "GET", + "verify": true, + "params": { + "fid": "int: uid" + }, + "comment": "获取用户在哪一个分组" + }, + "history": { + "url": "https://api.bilibili.com/x/v2/history", + "method": "GET", + "verify": true, + "params": { + "pn": "int: 页码", + "ps": "const int: 100" + }, + "comment": "用户浏览历史记录(旧版)" + }, + "history_new": { + "url": "https://api.bilibili.com/x/web-interface/history/cursor", + "method": "GET", + "verify": true, + "params": { + "type": "all:全部类型(默认)archive:稿件 live:直播 article:文章", + "view_at": "int: 时间戳,获取此时间戳之前的历史记录", + "business": "历史记录截止目标业务类型 默认为空 archive:稿件 pgc:剧集(番剧 / 影视) live:直播 article-list:文集 article:文章", + "max": "历史记录截止目标 oid", + "ps": "const int: 100" + }, + "comment": "用户浏览历史记录" + }, + "channel_list": { + "url": "https://api.bilibili.com/x/polymer/space/seasons_series_list", + "method": "GET", + "verify": false, + "params": { + "mid": "int: uid", + "page_num": "int: 开始项", + "page_size": "int: 开始项后面的项数" + }, + "comment": "查看用户合集的列表(新版)" + }, + "channel_video_series": { + "url": "https://api.bilibili.com/x/series/archives", + "method": "GET", + "verify": false, + "params": { + "mid": "int: uid", + "series_id": "int: series_id", + "pn": "int: 页码", + "ps": "const int: 100" + }, + "comment": "查看列表内视频(旧版)" + }, + "channel_video_season": { + "url": "https://api.bilibili.com/x/polymer/space/seasons_archives_list", + "method": "GET", + "verify": false, + "params": { + "mid": "int: uid", + "season_id": "int: season_id", + "sort_reverse": "bool: 是否升序排序,否则默认排序", + "page_num": "int: 页码", + "page_size": "int: 每一页的项数" + }, + "comment": "查看用户合集中的视频(新版)" + }, + "pugv": { + "url": "https://api.bilibili.com/pugv/app/web/season/page", + "method": "GET", + "verify": false, + "params": { + "mid": "int: uid" + }, + "comment": "查看用户的课程" + }, + "get_coins": { + "url": "https://account.bilibili.com/site/getCoin", + "method": "GET", + "verify": true, + "comment": "获取硬币数量" + }, + "events": { + "url": "https://member.bilibili.com/x2/creative/h5/calendar/event", + "method": "GET", + "params": { + "ts": "int: 时间戳" + }, + "verify": true, + "comment": "获取事件" + }, + "public_notes": { + "url": "https://api.bilibili.com/x/note/publish/list/user", + "method": "GET", + "verify": true, + "params": { + "pn": "int: 页码", + "ps": "int: 每页项数" + }, + "comment": "获取用户的公开笔记信息" + }, + "all_notes": { + "url": "https://api.bilibili.com/x/note/list", + "method": "GET", + "verify": true, + "params": { + "pn": "int: 页码", + "ps": "int: 每页项数" + }, + "comment": "获取用户的笔记信息" + }, + "get_special_followings": { + "url": "https://api.bilibili.com/x/relation/tag/special", + "method": "GET", + "params": { + "pn": "int: 页码", + "ps": "int: 每页项数" + }, + "verify": true, + "comment": "获取自己的特别关注" + }, + "get_whisper_followings": { + "url": "https://api.bilibili.com/x/relation/whispers", + "method": "GET", + "params": { + "pn": "int: 页码", + "ps": "int: 每页项数" + }, + "verify": true, + "comment": "获取自己的悄悄关注" + }, + "get_friends": { + "url": "https://api.bilibili.com/x/relation/friends", + "method": "GET", + "verify": true, + "comment": "获取与自己互粉的人" + }, + "get_black_list": { + "url": "https://api.bilibili.com/x/relation/blacks", + "method": "GET", + "params": { + "pn": "int: 页码", + "ps": "int: 每页项数" + }, + "verify": true, + "comment": "获取自己的黑名单列表" + }, + "get_same_followings": { + "url": "https://api.bilibili.com/x/relation/same/followings", + "method": "GET", + "verify": true, + "params": { + "vmid": "int: uid", + "pn": "int: 页码", + "ps": "int: 每页项数" + }, + "comment": "获取指定用户和自己共同关注的 up 主" + }, + "jury": { + "url": "https://api.bilibili.com/x/credit/v2/jury/jury", + "method": "GET", + "verify": true, + "comment": "获取自己风纪委员信息" + }, + "elec_user_monthly": { + "url": "https://api.bilibili.com/x/ugcpay-rank/elec/month/up", + "method": "GET", + "verify": true, + "params": { + "up_mid": "int: uid 号" + }, + "comment": "获取空间充电公示列表" + }, + "uplikeimg": { + "url": "https://api.bilibili.com/x/web-interface/view/uplikeimg", + "method": "GET", + "verify": true, + "params": { + "aid": "int: av 号", + "vmid": "int: up uid 号" + }, + "comment": "视频三连特效" + }, + "relation": { + "url": "https://api.bilibili.com/x/space/wbi/acc/relation", + "method": "GET", + "params": { + "mid": "int: uid" + }, + "verify": true, + "wbi": true, + "comment": "获取与某用户的关系" + } + }, + "operate": { + "modify": { + "url": "https://api.bilibili.com/x/relation/modify", + "method": "POST", + "verify": true, + "data": { + "fid": "int: UID", + "act": "int: 1 关注 2 取关 3 悄悄关注 5 拉黑 6 取消拉黑 7 移除粉丝", + "re_src": "const int: 11" + }, + "comment": "用户关系操作" + }, + "set_space_notice": { + "url": "http://api.bilibili.com/x/space/notice/set", + "method": "POST", + "verify": true, + "params": { + "notice": "str: text ,不必要", + "csrf": "str: CSRF Token(位于 cookie),必要" + }, + "comment": "修改用户个人空间公告" + }, + "create_subscribe_group": { + "url": "https://api.bilibili.com/x/relation/tag/create", + "method": "POST", + "verify": true, + "data": { + "tag": "str: 分组名" + }, + "comment": "添加关注分组" + }, + "del_subscribe_group": { + "url": "https://api.bilibili.com/x/relation/tag/del", + "method": "POST", + "verify": true, + "data": { + "tagid": "int: 分组 id" + }, + "comment": "删除关注分组" + }, + "rename_subscribe_group": { + "url": "https://api.bilibili.com/x/relation/tag/update", + "method": "POST", + "verify": true, + "data": { + "tagid": "int: 分组 id", + "name": "str: 新的分组名" + }, + "comment": "重命名分组" + }, + "set_user_subscribe_group": { + "url": "https://api.bilibili.com/x/relation/tags/addUsers", + "method": "POST", + "verify": true, + "data": { + "fids": "int: UID", + "tagids": "commaSeparatedList[int]: 分组的 tagids,逗号分隔" + }, + "comment": "移动用户到关注分组" + } + }, + "channel_series": { + "info": { + "url": "https://api.bilibili.com/x/series/series", + "method": "GET", + "verify": false, + "params": { + "series_id": "int: series_id" + }, + "comment": "获取简介" + }, + "season_info": { + "url": "https://api.bilibili.com/x/space/fav/season/list", + "method": "GET", + "verify": false, + "params": { + "season_id": "int: season_id" + }, + "comment": "获取简介" + }, + "del_channel_aids_series": { + "url": "https://api.bilibili.com/x/series/series/delArchives", + "method": "POST", + "verify": true, + "params": { + "mid": "int: uid", + "series_id": "int: series_id", + "aids": "int: aid 列表" + } + }, + "add_channel_aids_series": { + "url": "https://api.bilibili.com/x/series/series/addArchives", + "method": "POST", + "verify": true, + "params": { + "mid": "int: uid", + "series_id": "int: series_id", + "aids": "int: aid 列表" + } + }, + "del_channel_series": { + "url": "https://api.bilibili.com/x/series/series/delete", + "method": "POST", + "verify": true, + "query": { + "mid": "int: uid", + "series_id": "int: series_id", + "aids": "list: 所有 aid 列表", + "csrf": "string: bili_jct 的值" + } + }, + "create": { + "url": "https://api.bilibili.com/x/series/series/createAndAddArchives", + "method": "POST", + "verify": true, + "params": { + "mid": "int: uid", + "aids": "int: aid 列表", + "name": "str", + "keywords": "str,str", + "description": "str" + } + } + } +} diff --git a/bilibili_api/data/api/video.json b/bilibili_api/data/api/video.json new file mode 100644 index 0000000000000000000000000000000000000000..66c1a2ad2051292d97520d399a317770154c7b9b --- /dev/null +++ b/bilibili_api/data/api/video.json @@ -0,0 +1,509 @@ +{ + "info": { + "stat": { + "url": "https://api.bilibili.com/x/web-interface/archive/stat", + "method": "GET", + "verify": false, + "params": { + "aid": "int: av 号", + "bvid": "string: BV 号" + }, + "comment": "视频数据" + }, + "info": { + "url": "https://api.bilibili.com/x/web-interface/view", + "method": "GET", + "verify": false, + "params": { + "aid": "int: av 号", + "bvid": "string: BV 号" + }, + "comment": "视频详细信息" + }, + "detail": { + "url": "https://api.bilibili.com/x/web-interface/wbi/view/detail", + "method": "GET", + "verify": false, + "params": { + "aid": "int: av 号", + "bvid": "string: BV 号", + "need_operation_card": "int: 0", + "need_elec": "int: 0" + } + }, + "cid_info": { + "url": "https://hd.biliplus.com/api/cidinfo", + "method": "GET", + "verify": false, + "params": { + "cid": "int:分 P CID" + }, + "comment": "获取 cid 对应的视频" + }, + "tags": { + "url": "https://api.bilibili.com/x/web-interface/view/detail/tag", + "method": "GET", + "verify": false, + "params": { + "aid": "int: av 号", + "bvid": "string: BV 号" + }, + "comment": "视频标签信息" + }, + "chargers": { + "url": "https://api.bilibili.com/x/web-interface/elec/show", + "method": "GET", + "verify": false, + "params": { + "aid": "int: av 号", + "bvid": "string: BV 号", + "mid": "int: 用户 UID" + }, + "comment": "视频充电信息" + }, + "video_snapshot_pvideo": { + "url": "https://api.bilibili.com/pvideo", + "method": "GET", + "verify": false, + "params": { + "aid": "int: av 号" + }, + "comment": "视频预览快照(web)" + }, + "video_snapshot": { + "url": "https://api.bilibili.com/x/player/videoshot", + "method": "GET", + "verify": false, + "params": { + "aid": "int: av 号", + "bvid": "string: BV 号", + "cid": "int:分 P CID", + "index": "int:json 数组截取时间表1为需要,0不需要" + }, + "comment": "视频快照(web)" + }, + "pages": { + "url": "https://api.bilibili.com/x/player/pagelist", + "method": "GET", + "verify": false, + "params": { + "aid": "int: av 号", + "bvid": "string: BV 号" + }, + "comment": "分 P 列表" + }, + "playurl": { + "url": "https://api.bilibili.com/x/player/playurl", + "method": "GET", + "verify": false, + "params": { + "avid": "int: av 号", + "cid": "int: 分 P 编号", + "qn": "int: 视频质量编号,最高 127", + "otype": "const str: json", + "fnval": "const int: 4048", + "platform": "int: 平台" + }, + "comment": "视频下载的信息,下载链接需要提供 headers 伪装浏览器请求(Referer 和 User-Agent)" + }, + "related": { + "url": "https://api.bilibili.com/x/web-interface/archive/related", + "method": "GET", + "verify": false, + "params": { + "aid": "int: av 号", + "bvid": "string: BV 号" + }, + "comment": "获取关联视频" + }, + "relation": { + "url": "https://api.bilibili.com/x/web-interface/archive/relation", + "method": "GET", + "verify": true, + "params": { + "aid": "int: av 号", + "bvid": "string: BV 号" + }, + "comment": "获取用户与视频关联的信息" + }, + "has_liked": { + "url": "https://api.bilibili.com/x/web-interface/archive/has/like", + "method": "GET", + "verify": true, + "params": { + "aid": "int: av 号", + "bvid": "string: BV 号" + }, + "comment": "是否已点赞" + }, + "get_pay_coins": { + "url": "https://api.bilibili.com/x/web-interface/archive/coins", + "method": "GET", + "verify": true, + "params": { + "aid": "int: av 号", + "bvid": "string: BV 号" + }, + "comment": "是否已投币" + }, + "has_favoured": { + "url": "https://api.bilibili.com/x/v2/fav/video/favoured", + "method": "GET", + "verify": true, + "params": { + "aid": "int: av 号" + }, + "comment": "是否已收藏" + }, + "media_list": { + "url": "https://api.bilibili.com/x/v3/fav/folder/created/list-all", + "method": "GET", + "verify": true, + "params": { + "rid": "int: av 号", + "up_mid": "int: up 主的 uid", + "type": "const int: 2" + }, + "comment": "获取收藏夹列表信息,用于收藏操作" + }, + "get_player_info": { + "url": "https://api.bilibili.com/x/player/v2", + "method": "GET", + "verify": true, + "data": { + "aid": "int: av 号。与 bvid 任选其一", + "cid": "int: 分 P id", + "bvid": "int: bv 号。与 aid 任选其一", + "ep_id": "int: 番剧分集 id" + }, + "comment": "获取视频上一次播放的记录,字幕和地区信息。需要 分集的 cid, 返回数据中含有json字幕的链接" + }, + "pbp": { + "url": "https://bvc.bilivideo.com/pbp/data", + "method": "GET", + "verify": false, + "params": { + "cid": "int: 分 P 编号", + "bvid": "string: BV 号", + "aid": "int: av 号" + } + }, + "is_forbid": { + "url": "https://api.bilibili.com/x/note/is_forbid", + "method": "GET", + "verify": true, + "params": { + "aid": "int: av 号" + }, + "comment": "是否允许笔记" + }, + "private_notes": { + "url": "https://api.bilibili.com/x/note/list/archive", + "method": "GET", + "verify": true, + "params": { + "oid": "int: av 号", + "oid_type": "int: oid_type" + }, + "comment": "列出稿件私有笔记列表" + }, + "public_notes": { + "url": "https://api.bilibili.com/x/note/publish/list/archive", + "method": "GET", + "verify": true, + "params": { + "oid": "int: av 号", + "oid_type": "int: oid_type", + "pn": "int: 页码", + "ps": "int: 每页项数" + }, + "comment": "列出稿件公开笔记列表" + }, + "video_online_broadcast_servers": { + "method": "GET", + "verify": true, + "url": "https://api.bilibili.com/x/web-interface/broadcast/servers?platform=pc", + "comment": "获取视频在线人数实时监测服务器列表" + }, + "ai_conclusion": { + "url": "https://api.bilibili.com/x/web-interface/view/conclusion/get", + "method": "GET", + "wbi": true, + "params": { + "aid": "int: av 号", + "bvid": "string: BV 号", + "cid": "int: cid", + "up_mid": "int: up_mid" + }, + "comment": "ai 总结" + }, + "online": { + "url": "https://api.bilibili.com/x/player/online/total", + "method": "GET", + "verify": false, + "params": { + "aid": "int: av 号", + "bvid": "int: bvid", + "cid": "int: cid" + }, + "comment": "在线人数检测" + } + }, + "operate": { + "like": { + "url": "https://api.bilibili.com/x/web-interface/archive/like", + "method": "POST", + "verify": true, + "data": { + "aid": "int: av 号", + "bvid": "string: BV 号", + "like": "int: 1 是点赞,2 是取消点赞" + }, + "comment": "给视频点赞/取消点赞 " + }, + "coin": { + "url": "https://api.bilibili.com/x/web-interface/coin/add", + "method": "POST", + "verify": true, + "data": { + "aid": "int: av 号", + "bvid": "string: BV 号", + "multiply": "int: 几个币", + "select_like": "int bool: 是否同时点赞" + }, + "comment": "给视频投币" + }, + "share": { + "url": "https://api.bilibili.com/x/web-interface/share/add", + "method": "POST", + "verify": true, + "data": { + "aid": "int: av 号", + "bvid": "string: BV 号", + "csrf": "str: csrf" + }, + "comment": "视频分享" + }, + "add_tag": { + "url": "https://api.bilibili.com/x/tag/archive/add", + "method": "POST", + "verify": true, + "data": { + "aid": "int: av 号", + "tag_name": "str: 标签名" + }, + "comment": "添加标签" + }, + "del_tag": { + "url": "https://api.bilibili.com/x/tag/archive/del", + "method": "POST", + "verify": true, + "data": { + "aid": "int: av 号", + "tag_id": "int: 标签 id" + }, + "comment": "删除标签" + }, + "yjsl": { + "url": "https://api.bilibili.com/x/web-interface/archive/like/triple", + "method": "POST", + "verify": true, + "params": { + "aid": "int: av 号", + "bvid": "string: BV 号" + }, + "comment": "阿婆最爱的一键三连欧" + }, + "subscribe_tag": { + "url": "https://api.bilibili.com/x/tag/subscribe/add", + "method": "POST", + "verify": true, + "data": { + "tag_id": "int: 标签 id" + }, + "comment": "订阅标签" + }, + "unsubscribe_tag": { + "url": "https://api.bilibili.com/x/tag/subscribe/cancel", + "method": "POST", + "verify": true, + "data": { + "tag_id": "int: 标签 id" + }, + "comment": "取消订阅标签" + }, + "appeal": { + "url": "https://api.bilibili.com/x/web-interface/archive/appeal", + "method": "POST", + "verify": true, + "data": { + "aid": "int: av 号", + "tid": "int: 投诉理由 tid, 见 https://api.bilibili.com/x/web-interface/archive/appeal/tags", + "desc": "str: 理由详细描述", + "attach": "str: 附件路径", + "#一些 kwargs": "翻源代码理解吧" + }, + "comment": "投诉稿件" + }, + "favorite": { + "url": "https://api.bilibili.com/x/v3/fav/resource/deal", + "method": "POST", + "verify": true, + "data": { + "rid": "int: av 号。", + "type": "const int: 2", + "add_media_ids": "commaSeparatedList[int]: 要添加到的收藏夹 ID。", + "del_media_ids": "commaSeparatedList[int]: 要移出的收藏夹 ID。" + }, + "comment": "设置视频收藏状态" + }, + "submit_subtitle": { + "url": "https://api.bilibili.com/x/v2/dm/subtitle/draft/save", + "method": "POST", + "verify": true, + "data": { + "type": 1, + "oid": "int: 分 P id", + "lan": "str: 字幕语言代码,参考 http://www.lingoes.cn/zh/translator/langcode.htm", + "data": { + "font_size": "float: 字体大小,默认 0.4", + "font_color": "str: 字体颜色,默认 \"#FFFFFF\"", + "background_alpha": "float: 背景不透明度,默认 0.5", + "background_color": "str: 背景颜色,默认 \"#9C27B0\"", + "Stroke": "str: 描边,目前作用未知,默认为 \"none\"", + "body": [ + { + "from": "int: 字幕开始时间(秒)", + "to": "int: 字幕结束时间(秒)", + "location": "int: 字幕位置,默认为 2", + "content": "str: 字幕内容" + } + ] + }, + "submit": "bool: 是否提交,不提交为草稿", + "sign": "bool: 是否署名", + "bvid": "str: 视频 BV 号" + }, + "comment": "上传字幕" + } + }, + "danmaku": { + "get_danmaku": { + "url": "https://api.bilibili.com/x/v2/dm/web/seg.so", + "method": "GET", + "verify": false, + "params": { + "oid": "int: video_info 中的 cid,即分 P 的编号", + "type": "const int: 1", + "segment_index": "int: 分片序号", + "pid": "int: av 号" + }, + "comment": "获取弹幕列表" + }, + "get_history_danmaku": { + "url": "https://api.bilibili.com/x/v2/dm/web/history/seg.so", + "method": "GET", + "verify": true, + "params": { + "oid": "int: video_info 中的 cid,即分 P 的编号", + "type": "const int: 1", + "date": "str: 历史弹幕日期,格式:YYYY-MM-DD" + }, + "comment": "获取历史弹幕列表" + }, + "view": { + "url": "https://api.bilibili.com/x/v2/dm/web/view", + "method": "GET", + "verify": false, + "params": { + "type": 1, + "oid": "int: 分 P 的编号", + "pid": "int: av 号" + }, + "comment": "获取弹幕设置、特殊弹幕" + }, + "get_history_danmaku_index": { + "url": "https://api.bilibili.com/x/v2/dm/history/index", + "method": "GET", + "verify": true, + "params": { + "oid": "int: 分 P 的编号", + "type": "const int: 1", + "month": "str: 年月 (yyyy-mm)" + }, + "comment": "存在历史弹幕的日期" + }, + "has_liked_danmaku": { + "url": "https://api.bilibili.com/x/v2/dm/thumbup/stats", + "method": "GET", + "verify": true, + "params": { + "oid": "int: video_info 中的 cid,即分 P 的编号", + "ids": "commaSeparatedList[int]: 弹幕 id,多个以逗号分隔" + }, + "comment": "是否已点赞弹幕" + }, + "send_danmaku": { + "url": "https://api.bilibili.com/x/v2/dm/post", + "method": "POST", + "verify": true, + "data": { + "type": "const int: 1", + "oid": "int: 分 P 编号", + "msg": "int: 弹幕内容", + "bvid": "int: bvid", + "progress": "int: 发送时间(毫秒)", + "color": "int: 颜色(十六进制转十进制)", + "fontsize": "int: 字体大小(小 18 普通 25 大 36)", + "pool": "int bool: 字幕弹幕(1 是 0 否)", + "mode": "int: 模式(滚动 1 顶部 5 底部 4)", + "plat": "const int: 1" + }, + "comment": "发送弹幕" + }, + "like_danmaku": { + "url": "https://api.bilibili.com/x/v2/dm/thumbup/add", + "method": "POST", + "verify": true, + "data": { + "dmid": "int: 弹幕 ID", + "oid": "int: 分 P 编号", + "op": "int: 1 点赞 2 取消点赞", + "platform": "const str: web_player" + }, + "comment": "点赞弹幕" + }, + "edit_danmaku": { + "url": "https://api.bilibili.com/x/v2/dm/edit/state", + "method": "POST", + "verify": true, + "data": { + "type": "const int: 1", + "dmids": "int: 弹幕 ID", + "oid": "int: 视频 cid", + "state": "int: 1 删除 2 保护 3 取消保护" + }, + "comment": "编辑弹幕" + }, + "snapshot": { + "url": "http://api.bilibili.com/x/v2/dm/ajax", + "method": "GET", + "verify": false, + "params": { + "aid": "int or string: av 号或 BV 号" + }, + "comment": "获取弹幕快照" + }, + "recall": { + "url": "http://api.bilibili.com/x/dm/recall", + "method": "GET", + "verify": false, + "data": { + "dmid": "int: 弹幕 ID", + "cid": "int: 分 P 编号", + "csrf": "cookies: bili_jct" + }, + "comment": "撤回弹幕" + } + } +} diff --git a/bilibili_api/data/api/video_tag.json b/bilibili_api/data/api/video_tag.json new file mode 100644 index 0000000000000000000000000000000000000000..999c050cc2cb4a8b408fabaea483bda3e796e07d --- /dev/null +++ b/bilibili_api/data/api/video_tag.json @@ -0,0 +1,39 @@ +{ + "info": { + "tag_info": { + "url": "https://api.bilibili.com/x/tag/info", + "method": "GET", + "params": { + "tag_name": "str: 标签名", + "tag_id": "int: 标签 id", + "#": "上面两个参数任选一个即可" + }, + "comment": "获取标签详情" + }, + "get_similar": { + "url": "https://api.bilibili.com/x/tag/change/similar", + "method": "GET", + "params": { + "tag_id": "int: 标签 id" + }, + "comment": "获取相关的标签" + }, + "get_list": { + "url": "https://api.vc.bilibili.com/topic_svr/v1/topic_svr/topic_new", + "method": "GET", + "params": { + "topic_id": "int: 标签 id" + }, + "comment": "获取相关的动态/视频" + }, + "get_history_list": { + "url": "https://api.vc.bilibili.com/topic_svr/v1/topic_svr/topic_history", + "method": "GET", + "params": { + "topic_id": "int: 标签 id", + "offset_dynamic_id": "int: 起始视频/动态的dynamic_id(不包含自身)" + }, + "comment": "获取从指定dynamic_id视频的后一位开始的相关的动态/视频, 先用get_list获取dynamic_id。" + } + } +} diff --git a/bilibili_api/data/api/video_uploader.json b/bilibili_api/data/api/video_uploader.json new file mode 100644 index 0000000000000000000000000000000000000000..f448ebb9f1598b43e1278204865b4cdab7b8c12d --- /dev/null +++ b/bilibili_api/data/api/video_uploader.json @@ -0,0 +1,155 @@ +{ + "pre": { + "url": "https://member.bilibili.com/x/vupre/web/archive/pre", + "method": "GET", + "verify": true, + "params": { + "lang": "const: cn" + }, + "comment": "获取视频上传基本前置信息" + }, + "check_tag_name": { + "url": "https://member.bilibili.com/x/vupre/web/topic/tag/check", + "method": "GET", + "verify": true, + "params": { + "t": "str: tag_name" + }, + "comment": "检查 tag_name 是否合法" + }, + "available_topics": { + "url": "https://member.bilibili.com/x/vupre/web/topic/type", + "method": "GET", + "verify": true, + "params": { + "type_id":"int: 分区 ID", + "pn": "int: 分页", + "ps": "int: 每页项数" + }, + "comment": "根据分区获取可用话题,最多200个可获取" + }, + "preupload": { + "url": "https://member.bilibili.com/preupload", + "method": "GET", + "verify": true, + "params": { + "profile": "ugcfr/pc3", + "name": "str: 视频文件名(带后缀)", + "size": "int: 视频大小", + "r": "const str: upos", + "ssl": "const int: 0", + "version": "const str: 2.10.4", + "build": "const str: 2100400", + "upcdn": "const str: bda2", + "probe_version": "const str: 20211012" + }, + "comment": "获取上传配置" + }, + "cover_up": { + "url": "https://member.bilibili.com/x/vu/web/cover/up", + "method": "POST", + "verify": true, + "data": { + "cover": "str: 封面 dataURI. ", + "#": "cover 字段格式为: data:image/jpeg;base64,${图片 base64 信息}" + }, + "comment": "上传封面" + }, + "probe": { + "url": "https://member.bilibili.com/preupload", + "method": "GET", + "params": { + "r": "const str: probe" + }, + "comment": "获取线路" + }, + "submit": { + "url": "https://member.bilibili.com/x/vu/web/add/v3", + "method": "POST", + "verify": true, + "data": { + "act_reserve_create": "const int: 0", + "copyright": "int, 投稿类型。1 自制,2 转载。", + "source": "str: 视频来源。投稿类型为转载时注明来源,为原创时为空。", + "cover": "str: 封面 URL", + "desc": "str: 视频简介。", + "desc_format_id": "const int: 0", + "dynamic": "str: 动态信息。", + "interactive": "const int: 0", + "no_reprint": "int: 显示未经作者授权禁止转载,仅当为原创视频时有效。1 为启用,0 为关闭。", + "open_elec": "int: 是否展示充电信息。1 为是,0 为否。", + "origin_state": "const int: 0", + "subtitles # 字幕设置": { + "lan": "str: 字幕投稿语言,不清楚作用请将该项设置为空", + "open": "int: 是否启用字幕投稿,1 or 0" + }, + "tag": "str: 视频标签。使用英文半角逗号分隔的标签组。示例:标签 1,标签 1,标签 1", + "tid": "int: 分区 ID。可以使用 channel 模块进行查询。", + "title": "str: 视频标题", + "up_close_danmaku": "bool: 是否关闭弹幕。", + "up_close_reply": "bool: 是否关闭评论。", + "up_selection_reply": "bool: 是否开启评论精选", + "videos # 分 P 列表": [ + { + "title": "str: 标题", + "desc": "str: 简介", + "filename": "str: preupload 时返回的 filename" + } + ], + "dtime": "int?: 可选,定时发布时间戳(秒)" + } + }, + "missions": { + "url": "https://member.bilibili.com/x/vupre/app/h5/mission/type/all", + "method": "GET", + "verify": false, + "params": { + "tid": "int: 分区 ID" + }, + "comment": "获取活动列表" + }, + "upload_args": { + "url": "https://member.bilibili.com/x/vupre/web/archive/view?topic_grey=1", + "method": "GET", + "verify": false, + "params": { + "bvid": "str: BVID" + }, + "comment": "获取已上传视频的配置" + }, + "edit": { + "url": "https://member.bilibili.com/x/vu/web/edit", + "method": "POST", + "verify": true, + "data": { + "act_reserve_create": "const int: 0", + "copyright": "int, 投稿类型。1 自制,2 转载。", + "source": "str: 视频来源。投稿类型为转载时注明来源,为原创时为空。", + "cover": "str: 封面 URL", + "desc": "str: 视频简介。", + "desc_format_id": "const int: 0", + "dynamic": "str: 动态信息。", + "interactive": "const int: 0", + "no_reprint": "int: 显示未经作者授权禁止转载,仅当为原创视频时有效。1 为启用,0 为关闭。", + "open_elec": "int: 是否展示充电信息。1 为是,0 为否。", + "origin_state": "const int: 0", + "subtitles # 字幕设置": { + "lan": "str: 字幕投稿语言,不清楚作用请将该项设置为空", + "open": "int: 是否启用字幕投稿,1 or 0" + }, + "tag": "str: 视频标签。使用英文半角逗号分隔的标签组。示例:标签 1,标签 1,标签 1", + "tid": "int: 分区 ID。可以使用 channel 模块进行查询。", + "title": "str: 视频标题", + "up_close_danmaku": "bool: 是否关闭弹幕。", + "up_close_reply": "bool: 是否关闭评论。", + "up_selection_reply": "bool: 是否开启评论精选", + "videos # 分 P 列表": [ + { + "title": "str: 标题", + "desc": "str: 简介", + "filename": "str: preupload 时返回的 filename" + } + ] + } + } +} diff --git a/bilibili_api/data/api/video_zone.json b/bilibili_api/data/api/video_zone.json new file mode 100644 index 0000000000000000000000000000000000000000..cc5db91f88108620b08e7932ac32569e5d300dd3 --- /dev/null +++ b/bilibili_api/data/api/video_zone.json @@ -0,0 +1,41 @@ +{ + "ranking": { + "get_top10": { + "url": "https://api.bilibili.com/x/web-interface/ranking/region", + "method": "GET", + "verify": false, + "params": { + "rid": "int: tid,分区 id", + "day": "int: 3,7" + }, + "comment": "获取分区前十排行榜" + } + }, + "count": { + "url": "https://api.bilibili.com/x/web-interface/online", + "method": "GET", + "verify": false, + "comment": "获取每个分区当日最新投稿数量" + }, + "new": { + "url": "https://api.bilibili.com/x/web-interface/dynamic/region", + "method": "GET", + "verify": false, + "params": { + "ps": "int: 每页项数", + "pn": "int: 页码", + "rid": "int: tid,分区 id" + }, + "comment": "获取分区最新投稿视频" + }, + "get_hot_tags": + { + "url": "https://api.bilibili.com/x/tag/hots", + "method": "GET", + "params":{ + "rid": "int: tid,分区 id" + }, + "verify": false, + "comment": "获取热门标签" + } +} diff --git a/bilibili_api/data/api/vote.json b/bilibili_api/data/api/vote.json new file mode 100644 index 0000000000000000000000000000000000000000..e6a6c7bd461140dab635d4acef53732ca8e752c7 --- /dev/null +++ b/bilibili_api/data/api/vote.json @@ -0,0 +1,46 @@ +{ + "info": { + "vote_info": { + "url": "https://api.vc.bilibili.com/vote_svr/v1/vote_svr/vote_info", + "method": "GET", + "verify": false, + "params": { + "vote_id": "int: 投票 ID" + }, + "comment": "获取投票信息" + } + }, + "operate": { + "create": { + "url": "https://api.vc.bilibili.com/vote_svr/v1/vote_svr/create_vote", + "method": "POST", + "verify": true, + "data": { + "info[title]": "string: 投票标题", + "info[desc]": "string: 投票描述", + "info[type]": "int: 投票类型 0:文字投票 1:图片投票", + "info[choice_cnt]": "int: 最多选几项", + "info[duration]": "int: 投票持续时间", + "info[options][n][desc]": "string: 选项n描述", + "info[options][n][img_url]": "string: 选项n图片" + }, + "comment": "创建投票" + }, + "update": { + "url": "https://api.vc.bilibili.com/vote_svr/v1/vote_svr/update_vote", + "method": "POST", + "verify": true, + "data": { + "info[title]": "string: 投票标题", + "info[desc]": "string: 投票描述", + "info[type]": "int: 投票类型 0:文字投票 1:图片投票", + "info[choice_cnt]": "int: 最多选几项", + "info[duration]": "int: 投票持续时间", + "info[options][n][desc]": "string: 选项n描述", + "info[options][n][img_url]": "string: 选项n图片", + "info[vote_id]": "int: vote_id" + }, + "comment": "更新投票内容" + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/api/watchroom.json b/bilibili_api/data/api/watchroom.json new file mode 100644 index 0000000000000000000000000000000000000000..64d59b04890916e80751891c8ded02e410c90aa6 --- /dev/null +++ b/bilibili_api/data/api/watchroom.json @@ -0,0 +1,132 @@ +{ + "info": { + "info": { + "url": "https://api.bilibili.com/pgc/freya/web/room/info", + "method": "GET", + "verify": true, + "params": { + "room_id": "int: 放映室id", + "platform": "str: web" + }, + "comment": "放映室信息" + }, + "season": { + "url": "https://api.bilibili.com/pgc/view/web/freya/season", + "method": "GET", + "verify": true, + "params": { + "season_id":"int: 番剧的 season_id", + "ep_id": "int: 剧集的 ep_id", + "room_id":"int: 放映室id", + "platform": "str: web", + "csrf": "str: csrf" + }, + "comment": "获取正在放映的影片" + } + }, + "operate": { + "match": { + "url": "https://api.bilibili.com/pgc/freya/web/room/match", + "method": "POST", + "verify": true, + "data": { + "fail_fast": "int", + "season_id": "int: 番剧的 season_id", + "from_type": "int", + "season_type": "int", + "platform": "str: web", + "csrf": "str: csrf" + }, + "comment": "匹配放映室" + }, + "create": { + "url": "https://api.bilibili.com/pgc/freya/web/room/create", + "method": "POST", + "verify": true, + "data": { + "season_id": "int: 番剧的 season_id", + "episode_id": "int: 剧集id", + "is_open": "int: 是否公开 0 不公开 1 公开", + "platform": "str: web", + "csrf": "str: csrf" + }, + "comment": "创建放映室" + }, + "join": { + "url": "https://api.bilibili.com/pgc/freya/web/room/join", + "method": "POST", + "verify": true, + "data": { + "room_id": "int: 放映室id", + "platform": "str: web", + "csrf": "str: csrf", + "token": "str: 分享时的 token" + }, + "comment": "加入放映室" + }, + "open": { + "url": "https://api.bilibili.com/pgc/freya/web/room/modify/info", + "method": "POST", + "verify": true, + "data": { + "room_id": "int: 放映室id", + "is_open": "int: 是否公开 0 不公开 1 公开", + "platform": "str: web", + "csrf": "str: csrf" + }, + "comment": "开启放映室" + }, + "progress": { + "url": "https://api.bilibili.com/pgc/freya/web/room/modify/progress", + "method": "POST", + "verify": true, + "data": { + "room_id": "int: 放映室id", + "status": "状态 1 播放中 0 暂停中 2 已结束", + "progress": "int: 进度 s", + "platform": "str: web", + "csrf": "str: csrf" + }, + "comment": "修改放映室进度" + }, + "kickout": { + "url": "https://api.bilibili.com/pgc/freya/web/room/kickout", + "method": "POST", + "verify": true, + "data": { + "room_id": "int: 放映室id", + "mid": "int: 被踢出用户uid", + "platform": "str: web", + "csrf": "str: csrf" + }, + "comment": "踢出放映室" + }, + "season": { + "url": "https://api.bilibili.com/pgc/freya/web/room/modify/season", + "method": "POST", + "verify": true, + "data": { + "room_id": "int: 放映室id", + "season_id": "int: 番剧的 season_id", + "episode_id": "int: 剧集id", + "platform": "str: web", + "csrf": "str: csrf" + }, + "comment": "修改放映室播放内容" + }, + "send": { + "url": "https://api.bilibili.com/pgc/freya/web/im/msg/send", + "method": "POST", + "verify": true, + "data": { + "room_id": "int: 放映室id", + "content_type": "int: 0", + "content": "str: 发送内容", + "req_id": "int: 时间戳", + "platform": "str: web", + "csrf": "str: csrf" + }, + "comment": "发送弹幕" + } + } +} \ No newline at end of file diff --git a/bilibili_api/data/appeal.json b/bilibili_api/data/appeal.json new file mode 100644 index 0000000000000000000000000000000000000000..f030b98a852a92958d3aeef3e60e70c9148b4473 --- /dev/null +++ b/bilibili_api/data/appeal.json @@ -0,0 +1,231 @@ +{ + "code": 0, + "message": "0", + "ttl": 1, + "data": [ + { + "tid": 2, + "business": 1, + "weight": 100, + "round": 1, + "state": 1, + "name": "违法违禁", + "remark": "为帮助审核人员更快处理,补充违规内容出现位置", + "ctime": "2018-08-13T15:41:20+08:00", + "mtime": "2018-08-13T15:41:20+08:00", + "controls": null + }, + { + "tid": 3, + "business": 1, + "weight": 90, + "round": 1, + "state": 1, + "name": "色情", + "remark": "为帮助审核人员更快处理,补充违规内容出现位置", + "ctime": "2018-08-13T15:41:20+08:00", + "mtime": "2018-08-13T15:41:20+08:00", + "controls": null + }, + { + "tid": 4, + "business": 1, + "weight": 80, + "round": 1, + "state": 1, + "name": "低俗", + "remark": "为帮助审核人员更快处理,补充违规内容出现位置", + "ctime": "2018-08-13T15:41:20+08:00", + "mtime": "2018-08-13T15:41:20+08:00", + "controls": null + }, + { + "tid": 5, + "business": 1, + "weight": 70, + "round": 1, + "state": 1, + "name": "赌博诈骗", + "remark": "为帮助审核人员更快处理,补充违规内容出现位置", + "ctime": "2018-08-13T15:41:20+08:00", + "mtime": "2018-08-13T15:41:20+08:00", + "controls": null + }, + { + "tid": 6, + "business": 1, + "weight": 60, + "round": 1, + "state": 1, + "name": "血腥暴力", + "remark": "为帮助审核人员更快处理,补充违规内容出现位置", + "ctime": "2018-08-13T15:41:20+08:00", + "mtime": "2018-08-13T15:41:20+08:00", + "controls": null + }, + { + "tid": 7, + "business": 1, + "weight": 50, + "round": 1, + "state": 1, + "name": "人身攻击", + "remark": "为帮助审核人员更快处理,补充违规内容出现位置", + "ctime": "2018-08-13T15:41:20+08:00", + "mtime": "2018-08-13T15:41:20+08:00", + "controls": null + }, + { + "tid": 8, + "business": 1, + "weight": 40, + "round": 1, + "state": 1, + "name": "与站内其他视频撞车", + "remark": "为帮助审核人员更快处理, 请描述撞车信息", + "ctime": "2018-08-13T15:41:20+08:00", + "mtime": "2018-08-23T00:30:04+08:00", + "controls": [ + { + "tid": 8, + "bid": 1, + "name": "撞车对象", + "title": "撞车对象", + "component": "input", + "placeholder": "BVID", + "required": 1 + } + ] + }, + { + "tid": 10000, + "business": 1, + "weight": 10, + "round": 1, + "state": 1, + "name": "青少年不良信息", + "remark": "为帮助审核人员更快处理, 请补充违规内容出现位置", + "ctime": "2018-08-13T15:41:20+08:00", + "mtime": "2018-08-13T15:41:20+08:00", + "controls": null + }, + { + "tid": 10013, + "business": 1, + "weight": 37, + "round": 1, + "state": 1, + "name": "不良封面/标题", + "remark": "为帮助审核人员更快处理, 请描述详细信息", + "ctime": "2019-04-17T19:18:09+08:00", + "mtime": "2019-04-17T20:42:25+08:00", + "controls": null + }, + { + "tid": 10014, + "business": 1, + "weight": 8, + "round": 1, + "state": 1, + "name": "涉政谣言", + "remark": "为帮助审核人员更快处理,请补充谣言内容出现位置", + "ctime": "2022-09-15T17:23:44+08:00", + "mtime": "2022-09-16T09:56:07+08:00", + "controls": null + }, + { + "tid": 10015, + "business": 1, + "weight": 7, + "round": 1, + "state": 1, + "name": "涉社会事件谣言", + "remark": "为帮助审核人员更快处理,请补充谣言内容出现位置", + "ctime": "2022-09-15T17:25:56+08:00", + "mtime": "2022-09-16T09:56:07+08:00", + "controls": null + }, + { + "tid": 10016, + "business": 1, + "weight": 6, + "round": 1, + "state": 1, + "name": "疫情谣言", + "remark": "为帮助审核人员更快处理,请补充谣言内容出现位置", + "ctime": "2022-09-15T17:26:40+08:00", + "mtime": "2022-09-16T09:56:08+08:00", + "controls": null + }, + { + "tid": 10017, + "business": 1, + "weight": 5, + "round": 1, + "state": 1, + "name": "虚假不实信息", + "remark": "为帮助审核人员更快处理,请补充不实内容出现位置", + "ctime": "2022-09-15T17:28:16+08:00", + "mtime": "2022-09-16T09:56:08+08:00", + "controls": null + }, + { + "tid": 1, + "business": 1, + "weight": 1, + "round": 2, + "state": 1, + "name": "有其他问题", + "remark": "为帮助审核人员更快处理,请补充问题类型和出现位置等详细信息", + "ctime": "2018-08-13T15:41:20+08:00", + "mtime": "2018-08-13T15:41:20+08:00", + "controls": null + }, + { + "tid": 9, + "business": 1, + "weight": 30, + "round": 2, + "state": 1, + "name": "引战", + "remark": "为帮助审核人员更快处理, 请补充引战的话题和出现位置", + "ctime": "2018-08-13T15:41:20+08:00", + "mtime": "2018-08-13T15:41:20+08:00", + "controls": null + }, + { + "tid": 10, + "business": 1, + "weight": 20, + "round": 2, + "state": 1, + "name": "不能参加充电", + "remark": "为帮助审核人员更快处理, 请补充问题类型和出现位置等详细信息", + "ctime": "2018-08-13T15:41:20+08:00", + "mtime": "2018-08-23T11:35:28+08:00", + "controls": null + }, + { + "tid": 52, + "business": 1, + "weight": 35, + "round": 2, + "state": 1, + "name": "转载/自制类型错误", + "remark": "为帮助审核人员更快处理, 请补充原创作品出处", + "ctime": "2018-08-13T15:41:20+08:00", + "mtime": "2018-08-13T15:41:20+08:00", + "controls": [ + { + "tid": 52, + "bid": 1, + "name": "出处", + "title": "原创视频出处", + "component": "link", + "placeholder": "请填写链接", + "required": 1 + } + ] + } + ] +} \ No newline at end of file diff --git a/bilibili_api/data/article-color/article-color.css b/bilibili_api/data/article-color/article-color.css new file mode 100644 index 0000000000000000000000000000000000000000..fb76c2bc3b44ef2149f80d080b485b1e8f1be5e7 --- /dev/null +++ b/bilibili_api/data/article-color/article-color.css @@ -0,0 +1,111 @@ +.normal-article-holder .color-blue-01 { + color: #56c1fe +} + +.normal-article-holder .color-lblue-01 { + color: #73fdea +} + +.normal-article-holder .color-green-01 { + color: #89fa4e +} + +.normal-article-holder .color-yellow-01 { + color: #fff359 +} + +.normal-article-holder .color-pink-01 { + color: #ff968d +} + +.normal-article-holder .color-purple-01 { + color: #ff8cc6 +} + +.normal-article-holder .color-blue-02 { + color: #02a2ff +} + +.normal-article-holder .color-lblue-02 { + color: #18e7cf +} + +.normal-article-holder .color-green-02 { + color: #60d837 +} + +.normal-article-holder .color-yellow-02 { + color: #fbe231 +} + +.normal-article-holder .color-pink-02 { + color: #ff654e +} + +.normal-article-holder .color-purple-02 { + color: #ef5fa8 +} + +.normal-article-holder .color-blue-03 { + color: #0176ba +} + +.normal-article-holder .color-lblue-03 { + color: #068f86 +} + +.normal-article-holder .color-green-03 { + color: #1db100 +} + +.normal-article-holder .color-yellow-03 { + color: #f8ba00 +} + +.normal-article-holder .color-pink-03 { + color: #ee230d +} + +.normal-article-holder .color-purple-03 { + color: #cb297a +} + +.normal-article-holder .color-blue-04 { + color: #004e80 +} + +.normal-article-holder .color-lblue-04 { + color: #017c76 +} + +.normal-article-holder .color-green-04 { + color: #017001 +} + +.normal-article-holder .color-yellow-04 { + color: #ff9201 +} + +.normal-article-holder .color-pink-04 { + color: #b41700 +} + +.normal-article-holder .color-purple-04 { + color: #99195e +} + +.normal-article-holder .color-gray-01 { + color: #d6d5d5 +} + +.normal-article-holder .color-gray-02 { + color: #929292 +} + +.normal-article-holder .color-gray-03 { + color: #5f5f5f +} + +.normal-article-holder .color-default { + color: #222 +} diff --git a/bilibili_api/data/article-color/readme.md b/bilibili_api/data/article-color/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..77ab7343f376b368ea7c7c6903dc780e448652fc --- /dev/null +++ b/bilibili_api/data/article-color/readme.md @@ -0,0 +1 @@ +http://s1.hdslb.com/bfs/static/jinkela/article-web/css/article-web.0.38ac85b49eedaa381bbdd12c70e8b71717d205a6.css diff --git a/bilibili_api/data/article_category.json b/bilibili_api/data/article_category.json new file mode 100644 index 0000000000000000000000000000000000000000..46e599d619cfc6e66d2518f2e5815aeed2aa42a8 --- /dev/null +++ b/bilibili_api/data/article_category.json @@ -0,0 +1,218 @@ +[ + { + "id": 2, + "parent_id": 0, + "name": "动画", + "children": [ + { + "id": 4, + "parent_id": 2, + "name": "动漫杂谈" + }, + { + "id": 5, + "parent_id": 2, + "name": "动漫资讯" + }, + { + "id": 31, + "parent_id": 2, + "name": "动画技术" + } + ] + }, + { + "id": 1, + "parent_id": 0, + "name": "游戏", + "children": [ + { + "id": 6, + "parent_id": 1, + "name": "单机游戏" + }, + { + "id": 7, + "parent_id": 1, + "name": "电子竞技" + }, + { + "id": 8, + "parent_id": 1, + "name": "手机游戏" + }, + { + "id": 9, + "parent_id": 1, + "name": "网络游戏" + }, + { + "id": 10, + "parent_id": 1, + "name": "桌游棋牌" + } + ] + }, + { + "id": 28, + "parent_id": 0, + "name": "影视", + "children": [ + { + "id": 12, + "parent_id": 28, + "name": "电影" + }, + { + "id": 35, + "parent_id": 28, + "name": "电视剧" + }, + { + "id": 36, + "parent_id": 28, + "name": "纪录片" + }, + { + "id": 37, + "parent_id": 28, + "name": "综艺" + } + ] + }, + { + "id": 3, + "parent_id": 0, + "name": "生活", + "children": [ + { + "id": 13, + "parent_id": 3, + "name": "美食" + }, + { + "id": 21, + "parent_id": 3, + "name": "萌宠" + }, + { + "id": 14, + "parent_id": 3, + "name": "时尚" + }, + { + "id": 22, + "parent_id": 3, + "name": "运动" + }, + { + "id": 15, + "parent_id": 3, + "name": "日常" + } + ] + }, + { + "id": 29, + "parent_id": 0, + "name": "兴趣", + "children": [ + { + "id": 23, + "parent_id": 29, + "name": "绘画" + }, + { + "id": 24, + "parent_id": 29, + "name": "手工" + }, + { + "id": 38, + "parent_id": 29, + "name": "摄影" + }, + { + "id": 39, + "parent_id": 29, + "name": "音乐舞蹈" + }, + { + "id": 11, + "parent_id": 29, + "name": "模型手办" + } + ] + }, + { + "id": 16, + "parent_id": 0, + "name": "轻小说", + "children": [ + { + "id": 18, + "parent_id": 16, + "name": "原创连载" + }, + { + "id": 19, + "parent_id": 16, + "name": "同人连载" + }, + { + "id": 32, + "parent_id": 16, + "name": "短篇小说" + }, + { + "id": 20, + "parent_id": 16, + "name": "小说杂谈" + } + ] + }, + { + "id": 17, + "parent_id": 0, + "name": "科技", + "children": [ + { + "id": 25, + "parent_id": 17, + "name": "人文历史" + }, + { + "id": 33, + "parent_id": 17, + "name": "自然" + }, + { + "id": 26, + "parent_id": 17, + "name": "数码" + }, + { + "id": 27, + "parent_id": 17, + "name": "汽车" + }, + { + "id": 34, + "parent_id": 17, + "name": "学习" + } + ] + }, + { + "id": 41, + "parent_id": 0, + "name": "笔记", + "children": [ + { + "id": 42, + "parent_id": 41, + "name": "全部笔记" + } + ] + } +] \ No newline at end of file diff --git a/bilibili_api/data/audio_search_tags.json b/bilibili_api/data/audio_search_tags.json new file mode 100644 index 0000000000000000000000000000000000000000..acaa369e2ca55fac2034cff03f7304bdc83785a5 --- /dev/null +++ b/bilibili_api/data/audio_search_tags.json @@ -0,0 +1,146 @@ +{ + "code": 0, + "message": "0", + "ttl": 1, + "data": { + "1": [ + { + "id": 3, + "name": "华语", + "type": 0 + }, + { + "id": 6, + "name": "欧美", + "type": 0 + }, + { + "id": 7, + "name": "日语", + "type": 0 + }, + { + "id": 61, + "name": "韩语", + "type": 0 + }, + { + "id": 1, + "name": "其他", + "type": 0 + } + ], + "2": [ + { + "id": 1, + "name": "流行", + "type": 0 + }, + { + "id": 2, + "name": "摇滚", + "type": 0 + }, + { + "id": 3, + "name": "电子音乐", + "type": 0 + }, + { + "id": 4, + "name": "乡村", + "type": 0 + }, + { + "id": 5, + "name": "民谣", + "type": 0 + }, + { + "id": 6, + "name": "轻音乐", + "type": 0 + }, + { + "id": 7, + "name": "古典", + "type": 0 + }, + { + "id": 8, + "name": "新世纪", + "type": 0 + }, + { + "id": 9, + "name": "雷鬼", + "type": 0 + }, + { + "id": 10, + "name": "布鲁斯", + "type": 0 + }, + { + "id": 12, + "name": "节奏与布鲁斯", + "type": 0 + }, + { + "id": 13, + "name": "原声", + "type": 0 + }, + { + "id": 14, + "name": "世界音乐", + "type": 0 + }, + { + "id": 15, + "name": "儿童音乐", + "type": 0 + }, + { + "id": 16, + "name": "拉丁", + "type": 0 + }, + { + "id": 17, + "name": "朋克", + "type": 0 + }, + { + "id": 18, + "name": "金属", + "type": 0 + }, + { + "id": 19, + "name": "爵士", + "type": 0 + }, + { + "id": 20, + "name": "嘻哈", + "type": 0 + }, + { + "id": 21, + "name": "唱作人", + "type": 0 + }, + { + "id": 22, + "name": "娱乐/舞台", + "type": 0 + }, + { + "id": 23, + "name": "其他", + "type": 0 + } + ] + } +} diff --git a/bilibili_api/data/bangumi_index_params.json b/bilibili_api/data/bangumi_index_params.json new file mode 100644 index 0000000000000000000000000000000000000000..e096febea65765a2f0bb393c1316a26753974b2b --- /dev/null +++ b/bilibili_api/data/bangumi_index_params.json @@ -0,0 +1,1748 @@ +{ + "anime": { + "indexType": "anime", + "ssType": 1, + "spmId": "666.14", + "tid": 13, + "filters": [ + { + "title": "类型", + "key": "season_version", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 1, + "name": "正片" + }, + { + "value": 2, + "name": "电影" + }, + { + "value": 3, + "name": "其他" + } + ] + }, + { + "title": "配音", + "key": "spoken_language_type", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 1, + "name": "原声" + }, + { + "value": 2, + "name": "中文配音" + } + ] + }, + { + "title": "地区", + "key": "area", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 2, + "name": "日本" + }, + { + "value": 3, + "name": "美国" + }, + { + "value": "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", + "name": "其他" + } + ] + }, + { + "title": "状态", + "key": "is_finish", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 1, + "name": "完结" + }, + { + "value": 0, + "name": "连载" + } + ] + }, + { + "title": "版权", + "key": "copyright", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 3, + "name": "独家" + }, + { + "value": "1,2,4", + "name": "其他" + } + ] + }, + { + "title": "付费", + "key": "season_status", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 1, + "name": "免费" + }, + { + "value": "2,6", + "name": "付费" + }, + { + "value": "4,6", + "name": "大会员" + } + ], + "free": true + }, + { + "title": "季度", + "key": "season_month", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 1, + "name": "1月" + }, + { + "value": 4, + "name": "4月" + }, + { + "value": 7, + "name": "7月" + }, + { + "value": 10, + "name": "10月" + } + ] + }, + { + "title": "年份", + "key": "year", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": "[2023,2024)", + "name": "2023" + }, + { + "value": "[2022,2023)", + "name": "2022" + }, + { + "value": "[2021,2022)", + "name": "2021" + }, + { + "value": "[2020,2021)", + "name": "2020" + }, + { + "value": "[2019,2020)", + "name": "2019" + }, + { + "value": "[2018,2019)", + "name": "2018" + }, + { + "value": "[2017,2018)", + "name": "2017" + }, + { + "value": "[2016,2017)", + "name": "2016" + }, + { + "value": "[2015,2016)", + "name": "2015" + }, + { + "value": "[2010,2015)", + "name": "2014-2010" + }, + { + "value": "[2005,2010)", + "name": "2009-2005" + }, + { + "value": "[2000,2005)", + "name": "2004-2000" + }, + { + "value": "[1990,2000)", + "name": "90年代" + }, + { + "value": "[1980,1990)", + "name": "80年代" + }, + { + "value": "[,1980)", + "name": "更早" + } + ], + "free": true + }, + { + "title": "风格", + "key": "style_id", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 10010, + "name": "原创" + }, + { + "value": 10011, + "name": "漫画改" + }, + { + "value": 10012, + "name": "小说改" + }, + { + "value": 10013, + "name": "游戏改" + }, + { + "value": 10102, + "name": "特摄" + }, + { + "value": 10015, + "name": "布袋戏" + }, + { + "value": 10016, + "name": "热血" + }, + { + "value": 10017, + "name": "穿越" + }, + { + "value": 10018, + "name": "奇幻" + }, + { + "value": 10020, + "name": "战斗" + }, + { + "value": 10021, + "name": "搞笑" + }, + { + "value": 10022, + "name": "日常" + }, + { + "value": 10023, + "name": "科幻" + }, + { + "value": 10024, + "name": "萌系" + }, + { + "value": 10025, + "name": "治愈" + }, + { + "value": 10026, + "name": "校园" + }, + { + "value": 10027, + "name": "少儿" + }, + { + "value": 10028, + "name": "泡面" + }, + { + "value": 10029, + "name": "恋爱" + }, + { + "value": 10030, + "name": "少女" + }, + { + "value": 10031, + "name": "魔法" + }, + { + "value": 10032, + "name": "冒险" + }, + { + "value": 10033, + "name": "历史" + }, + { + "value": 10034, + "name": "架空" + }, + { + "value": 10035, + "name": "机战" + }, + { + "value": 10036, + "name": "神魔" + }, + { + "value": 10037, + "name": "声控" + }, + { + "value": 10038, + "name": "运动" + }, + { + "value": 10039, + "name": "励志" + }, + { + "value": 10040, + "name": "音乐" + }, + { + "value": 10041, + "name": "推理" + }, + { + "value": 10042, + "name": "社团" + }, + { + "value": 10043, + "name": "智斗" + }, + { + "value": 10044, + "name": "催泪" + }, + { + "value": 10045, + "name": "美食" + }, + { + "value": 10046, + "name": "偶像" + }, + { + "value": 10047, + "name": "乙女" + }, + { + "value": 10048, + "name": "职场" + } + ], + "line": 10 + } + ], + "orders": [ + { + "title": "追番人数", + "key": "3", + "sort": "0,1" + }, + { + "title": "更新时间", + "key": "0", + "sort": "0,1" + }, + { + "title": "最高评分", + "key": "4", + "sort": "0" + }, + { + "title": "播放数量", + "key": "2", + "sort": "0,1" + }, + { + "title": "开播时间", + "key": "5", + "sort": "0,1" + } + ], + "total": 0, + "pageData": [], + "pages": 0 + }, + "movie": { + "indexType": "movie", + "ssType": 2, + "spmId": "666.23", + "tid": 23, + "filters": [ + { + "title": "地区", + "key": "area", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 1, + "name": "中国大陆" + }, + { + "value": "6,7", + "name": "中国港台" + }, + { + "value": 3, + "name": "美国" + }, + { + "value": 2, + "name": "日本" + }, + { + "value": 8, + "name": "韩国" + }, + { + "value": 9, + "name": "法国" + }, + { + "value": 4, + "name": "英国" + }, + { + "value": 15, + "name": "德国" + }, + { + "value": 10, + "name": "泰国" + }, + { + "value": 35, + "name": "意大利" + }, + { + "value": 13, + "name": "西班牙" + }, + { + "value": "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", + "name": "其他" + } + ], + "free": true + }, + { + "title": "风格", + "key": "style_id", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 10104, + "name": "短片" + }, + { + "value": 10050, + "name": "剧情" + }, + { + "value": 10051, + "name": "喜剧" + }, + { + "value": 10052, + "name": "爱情" + }, + { + "value": 10053, + "name": "动作" + }, + { + "value": 10054, + "name": "恐怖" + }, + { + "value": 10023, + "name": "科幻" + }, + { + "value": 10055, + "name": "犯罪" + }, + { + "value": 10056, + "name": "惊悚" + }, + { + "value": 10057, + "name": "悬疑" + }, + { + "value": 10018, + "name": "奇幻" + }, + { + "value": 10058, + "name": "战争" + }, + { + "value": 10059, + "name": "动画" + }, + { + "value": 10060, + "name": "传记" + }, + { + "value": 10061, + "name": "家庭" + }, + { + "value": 10062, + "name": "歌舞" + }, + { + "value": 10033, + "name": "历史" + }, + { + "value": 10032, + "name": "冒险" + }, + { + "value": 10063, + "name": "纪实" + }, + { + "value": 10064, + "name": "灾难" + }, + { + "value": 10011, + "name": "漫画改" + }, + { + "value": 10012, + "name": "小说改" + } + ], + "line": 10, + "free": true + }, + { + "title": "年份", + "key": "release_date", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": "[2023-01-01 00:00:00,2024-01-01 00:00:00)", + "name": "2023" + }, + { + "value": "[2022-01-01 00:00:00,2023-01-01 00:00:00)", + "name": "2022" + }, + { + "value": "[2021-01-01 00:00:00,2022-01-01 00:00:00)", + "name": "2021" + }, + { + "value": "[2020-01-01 00:00:00,2021-01-01 00:00:00)", + "name": "2020" + }, + { + "value": "[2019-01-01 00:00:00,2020-01-01 00:00:00)", + "name": "2019" + }, + { + "value": "[2018-01-01 00:00:00,2019-01-01 00:00:00)", + "name": "2018" + }, + { + "value": "[2017-01-01 00:00:00,2018-01-01 00:00:00)", + "name": "2017" + }, + { + "value": "[2016-01-01 00:00:00,2017-01-01 00:00:00)", + "name": "2016" + }, + { + "value": "[2010-01-01 00:00:00,2016-01-01 00:00:00)", + "name": "2015-2010" + }, + { + "value": "[2005-01-01 00:00:00,2010-01-01 00:00:00)", + "name": "2009-2005" + }, + { + "value": "[2000-01-01 00:00:00,2005-01-01 00:00:00)", + "name": "2004-2000" + }, + { + "value": "[1990-01-01 00:00:00,2000-01-01 00:00:00)", + "name": "90年代" + }, + { + "value": "[1980-01-01 00:00:00,1990-01-01 00:00:00)", + "name": "80年代" + }, + { + "value": "[,1980-01-01 00:00:00)", + "name": "更早" + } + ] + }, + { + "title": "付费", + "key": "season_status", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 1, + "name": "免费" + }, + { + "value": "2,6", + "name": "付费" + }, + { + "value": "4,6", + "name": "大会员" + } + ], + "free": true + } + ], + "orders": [ + { + "title": "播放数量", + "key": "2", + "sort": "0,1" + }, + { + "title": "更新时间", + "key": "0", + "sort": "0,1" + }, + { + "title": "上映时间", + "key": "6", + "sort": "0,1" + }, + { + "title": "最高评分", + "key": "4", + "sort": "0" + } + ], + "total": 0, + "pageData": [], + "pages": 0 + }, + "tv": { + "indexType": "tv", + "ssType": 5, + "spmId": "666.24", + "tid": 11, + "filters": [ + { + "title": "地区", + "key": "area", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": "1,6,7", + "name": "中国" + }, + { + "value": 2, + "name": "日本" + }, + { + "value": 3, + "name": "美国" + }, + { + "value": 4, + "name": "英国" + }, + { + "value": 10, + "name": "泰国" + }, + { + "value": "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", + "name": "其他" + } + ], + "free": true + }, + { + "title": "风格", + "key": "style_id", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 10021, + "name": "搞笑" + }, + { + "value": 10018, + "name": "奇幻" + }, + { + "value": 10058, + "name": "战争" + }, + { + "value": 10078, + "name": "武侠" + }, + { + "value": 10079, + "name": "青春" + }, + { + "value": 10103, + "name": "短剧" + }, + { + "value": 10080, + "name": "都市" + }, + { + "value": 10081, + "name": "古装" + }, + { + "value": 10082, + "name": "谍战" + }, + { + "value": 10083, + "name": "经典" + }, + { + "value": 10084, + "name": "情感" + }, + { + "value": 10057, + "name": "悬疑" + }, + { + "value": 10039, + "name": "励志" + }, + { + "value": 10085, + "name": "神话" + }, + { + "value": 10017, + "name": "穿越" + }, + { + "value": 10086, + "name": "年代" + }, + { + "value": 10087, + "name": "农村" + }, + { + "value": 10088, + "name": "刑侦" + }, + { + "value": 10050, + "name": "剧情" + }, + { + "value": 10061, + "name": "家庭" + }, + { + "value": 10033, + "name": "历史" + }, + { + "value": 10089, + "name": "军旅" + } + ], + "line": 10, + "free": true + }, + { + "title": "年份", + "key": "release_date", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": "[2023-01-01 00:00:00,2024-01-01 00:00:00)", + "name": "2023" + }, + { + "value": "[2022-01-01 00:00:00,2023-01-01 00:00:00)", + "name": "2022" + }, + { + "value": "[2021-01-01 00:00:00,2022-01-01 00:00:00)", + "name": "2021" + }, + { + "value": "[2020-01-01 00:00:00,2021-01-01 00:00:00)", + "name": "2020" + }, + { + "value": "[2019-01-01 00:00:00,2020-01-01 00:00:00)", + "name": "2019" + }, + { + "value": "[2018-01-01 00:00:00,2019-01-01 00:00:00)", + "name": "2018" + }, + { + "value": "[2017-01-01 00:00:00,2018-01-01 00:00:00)", + "name": "2017" + }, + { + "value": "[2016-01-01 00:00:00,2017-01-01 00:00:00)", + "name": "2016" + }, + { + "value": "[2010-01-01 00:00:00,2016-01-01 00:00:00)", + "name": "2015-2010" + }, + { + "value": "[2005-01-01 00:00:00,2010-01-01 00:00:00)", + "name": "2009-2005" + }, + { + "value": "[2000-01-01 00:00:00,2005-01-01 00:00:00)", + "name": "2004-2000" + }, + { + "value": "[1990-01-01 00:00:00,2000-01-01 00:00:00)", + "name": "90年代" + }, + { + "value": "[1980-01-01 00:00:00,1990-01-01 00:00:00)", + "name": "80年代" + }, + { + "value": "[,1980-01-01 00:00:00)", + "name": "更早" + } + ] + }, + { + "title": "付费", + "key": "season_status", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 1, + "name": "免费" + }, + { + "value": "2,6", + "name": "付费" + }, + { + "value": "4,6", + "name": "大会员" + } + ], + "free": true + } + ], + "orders": [ + { + "title": "播放数量", + "key": "2", + "sort": "0,1" + }, + { + "title": "更新时间", + "key": "0", + "sort": "0,1" + }, + { + "title": "弹幕数量", + "key": "1", + "sort": "0,1" + }, + { + "title": "追剧人数", + "key": "3", + "sort": "0,1" + }, + { + "title": "最高评分", + "key": "4", + "sort": "0" + } + ], + "total": 0, + "pageData": [], + "pages": 0 + }, + "documentary": { + "indexType": "documentary", + "ssType": 3, + "spmId": "666.22", + "tid": 177, + "filters": [ + { + "title": "风格", + "key": "style_id", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 10033, + "name": "历史" + }, + { + "value": 10045, + "name": "美食" + }, + { + "value": 10065, + "name": "人文" + }, + { + "value": 10066, + "name": "科技" + }, + { + "value": 10067, + "name": "探险" + }, + { + "value": 10068, + "name": "宇宙" + }, + { + "value": 10069, + "name": "萌宠" + }, + { + "value": 10070, + "name": "社会" + }, + { + "value": 10071, + "name": "动物" + }, + { + "value": 10072, + "name": "自然" + }, + { + "value": 10073, + "name": "医疗" + }, + { + "value": 10074, + "name": "军事" + }, + { + "value": 10064, + "name": "灾难" + }, + { + "value": 10075, + "name": "罪案" + }, + { + "value": 10076, + "name": "神秘" + }, + { + "value": 10077, + "name": "旅行" + }, + { + "value": 10038, + "name": "运动" + }, + { + "value": -10, + "name": "电影" + } + ], + "line": 10, + "free": true + }, + { + "title": "出品", + "key": "producer_id", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 4, + "name": "央视" + }, + { + "value": 1, + "name": "BBC" + }, + { + "value": 7, + "name": "探索频道" + }, + { + "value": 14, + "name": "国家地理" + }, + { + "value": 2, + "name": "NHK" + }, + { + "value": 6, + "name": "历史频道" + }, + { + "value": 8, + "name": "卫视" + }, + { + "value": 9, + "name": "自制" + }, + { + "value": 5, + "name": "ITV" + }, + { + "value": 3, + "name": "SKY" + }, + { + "value": 10, + "name": "ZDF" + }, + { + "value": 11, + "name": "合作机构" + }, + { + "value": 12, + "name": "国内其他" + }, + { + "value": 13, + "name": "国外其他" + }, + { + "value": 15, + "name": "索尼" + }, + { + "value": 16, + "name": "环球" + }, + { + "value": 17, + "name": "派拉蒙" + }, + { + "value": 18, + "name": "华纳" + }, + { + "value": 19, + "name": "迪士尼" + } + ], + "free": true + }, + { + "title": "年份", + "key": "release_date", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": "[2023-01-01 00:00:00,2024-01-01 00:00:00)", + "name": "2023" + }, + { + "value": "[2022-01-01 00:00:00,2023-01-01 00:00:00)", + "name": "2022" + }, + { + "value": "[2021-01-01 00:00:00,2022-01-01 00:00:00)", + "name": "2021" + }, + { + "value": "[2020-01-01 00:00:00,2021-01-01 00:00:00)", + "name": "2020" + }, + { + "value": "[2019-01-01 00:00:00,2020-01-01 00:00:00)", + "name": "2019" + }, + { + "value": "[2018-01-01 00:00:00,2019-01-01 00:00:00)", + "name": "2018" + }, + { + "value": "[2017-01-01 00:00:00,2018-01-01 00:00:00)", + "name": "2017" + }, + { + "value": "[2016-01-01 00:00:00,2017-01-01 00:00:00)", + "name": "2016" + }, + { + "value": "[2010-01-01 00:00:00,2016-01-01 00:00:00)", + "name": "2015-2010" + }, + { + "value": "[2005-01-01 00:00:00,2010-01-01 00:00:00)", + "name": "2009-2005" + }, + { + "value": "[2000-01-01 00:00:00,2005-01-01 00:00:00)", + "name": "2004-2000" + }, + { + "value": "[1990-01-01 00:00:00,2000-01-01 00:00:00)", + "name": "90年代" + }, + { + "value": "[1980-01-01 00:00:00,1990-01-01 00:00:00)", + "name": "80年代" + }, + { + "value": "[,1980-01-01 00:00:00)", + "name": "更早" + } + ] + }, + { + "title": "付费", + "key": "season_status", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 1, + "name": "免费" + }, + { + "value": "4,6", + "name": "大会员" + } + ], + "free": true + } + ], + "orders": [ + { + "title": "播放数量", + "key": "2", + "sort": "0,1" + }, + { + "title": "最高评分", + "key": "4", + "sort": "0" + }, + { + "title": "更新时间", + "key": "0", + "sort": "0,1" + }, + { + "title": "上映时间", + "key": "6", + "sort": "0,1" + }, + { + "title": "弹幕数量", + "key": "1", + "sort": "0,1" + } + ], + "total": 0, + "pageData": [], + "pages": 0 + }, + "variety": { + "indexType": "variety", + "ssType": 7, + "spmId": "666.10", + "tid": 11, + "filters": [ + { + "title": "付费", + "key": "season_status", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 1, + "name": "免费" + }, + { + "value": "4,6", + "name": "大会员" + } + ], + "free": true + }, + { + "title": "风格", + "key": "style_id", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 10040, + "name": "音乐" + }, + { + "value": 10090, + "name": "访谈" + }, + { + "value": 10091, + "name": "脱口秀" + }, + { + "value": 10092, + "name": "真人秀" + }, + { + "value": 10094, + "name": "选秀" + }, + { + "value": 10045, + "name": "美食" + }, + { + "value": 10095, + "name": "旅游" + }, + { + "value": 10098, + "name": "晚会" + }, + { + "value": 10096, + "name": "演唱会" + }, + { + "value": 10084, + "name": "情感" + }, + { + "value": 10051, + "name": "喜剧" + }, + { + "value": 10097, + "name": "亲子" + }, + { + "value": 10100, + "name": "文化" + }, + { + "value": 10048, + "name": "职场" + }, + { + "value": 10069, + "name": "萌宠" + }, + { + "value": 10099, + "name": "养成" + } + ], + "line": 10, + "free": true + } + ], + "orders": [ + { + "title": "最多播放", + "key": "2", + "sort": "0,1" + }, + { + "title": "最近更新", + "key": "0", + "sort": "0,1" + }, + { + "title": "最新上映", + "key": "6", + "sort": "0,1" + }, + { + "title": "最高评分", + "key": "4", + "sort": "0" + }, + { + "title": "弹幕数量", + "key": "1", + "sort": "0,1" + } + ], + "total": 0, + "pageData": [], + "pages": 0 + }, + "guochuang": { + "indexType": "guochuang", + "ssType": 4, + "spmId": "666.16", + "tid": 167, + "filters": [ + { + "title": "类型", + "key": "season_version", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 1, + "name": "正片" + }, + { + "value": 2, + "name": "电影" + }, + { + "value": 3, + "name": "其他" + } + ] + }, + { + "title": "状态", + "key": "is_finish", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 1, + "name": "完结" + }, + { + "value": 0, + "name": "连载" + } + ] + }, + { + "title": "版权", + "key": "copyright", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 3, + "name": "独家" + }, + { + "value": "1,2,4", + "name": "其他" + } + ] + }, + { + "title": "付费", + "key": "season_status", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 1, + "name": "免费" + }, + { + "value": "2,6", + "name": "付费" + }, + { + "value": "4,6", + "name": "大会员" + } + ], + "free": true + }, + { + "title": "年份", + "key": "year", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": "[2023,2024)", + "name": "2023" + }, + { + "value": "[2022,2023)", + "name": "2022" + }, + { + "value": "[2021,2022)", + "name": "2021" + }, + { + "value": "[2020,2021)", + "name": "2020" + }, + { + "value": "[2019,2020)", + "name": "2019" + }, + { + "value": "[2018,2019)", + "name": "2018" + }, + { + "value": "[2017,2018)", + "name": "2017" + }, + { + "value": "[2016,2017)", + "name": "2016" + }, + { + "value": "[2015,2016)", + "name": "2015" + }, + { + "value": "[2010,2015)", + "name": "2014-2010" + }, + { + "value": "[2005,2010)", + "name": "2009-2005" + }, + { + "value": "[2000,2005)", + "name": "2004-2000" + }, + { + "value": "[1990,2000)", + "name": "90年代" + }, + { + "value": "[1980,1990)", + "name": "80年代" + }, + { + "value": "[,1980)", + "name": "更早" + } + ], + "free": true + }, + { + "title": "风格", + "key": "style_id", + "list": [ + { + "value": -1, + "name": "全部" + }, + { + "value": 10010, + "name": "原创" + }, + { + "value": 10011, + "name": "漫画改" + }, + { + "value": 10012, + "name": "小说改" + }, + { + "value": 10013, + "name": "游戏改" + }, + { + "value": 10014, + "name": "动态漫" + }, + { + "value": 10015, + "name": "布袋戏" + }, + { + "value": 10016, + "name": "热血" + }, + { + "value": 10018, + "name": "奇幻" + }, + { + "value": 10019, + "name": "玄幻" + }, + { + "value": 10020, + "name": "战斗" + }, + { + "value": 10021, + "name": "搞笑" + }, + { + "value": 10078, + "name": "武侠" + }, + { + "value": 10022, + "name": "日常" + }, + { + "value": 10023, + "name": "科幻" + }, + { + "value": 10024, + "name": "萌系" + }, + { + "value": 10025, + "name": "治愈" + }, + { + "value": 10057, + "name": "悬疑" + }, + { + "value": 10026, + "name": "校园" + }, + { + "value": 10027, + "name": "少儿" + }, + { + "value": 10028, + "name": "泡面" + }, + { + "value": 10029, + "name": "恋爱" + }, + { + "value": 10030, + "name": "少女" + }, + { + "value": 10031, + "name": "魔法" + }, + { + "value": 10033, + "name": "历史" + }, + { + "value": 10035, + "name": "机战" + }, + { + "value": 10036, + "name": "神魔" + }, + { + "value": 10037, + "name": "声控" + }, + { + "value": 10038, + "name": "运动" + }, + { + "value": 10039, + "name": "励志" + }, + { + "value": 10040, + "name": "音乐" + }, + { + "value": 10041, + "name": "推理" + }, + { + "value": 10042, + "name": "社团" + }, + { + "value": 10043, + "name": "智斗" + }, + { + "value": 10044, + "name": "催泪" + }, + { + "value": 10045, + "name": "美食" + }, + { + "value": 10046, + "name": "偶像" + }, + { + "value": 10047, + "name": "乙女" + }, + { + "value": 10048, + "name": "职场" + }, + { + "value": 10049, + "name": "古风" + } + ], + "line": 10 + } + ], + "orders": [ + { + "title": "追番人数", + "key": "3", + "sort": "0,1" + }, + { + "title": "更新时间", + "key": "0", + "sort": "0,1" + }, + { + "title": "最高评分", + "key": "4", + "sort": "0" + }, + { + "title": "播放数量", + "key": "2", + "sort": "0,1" + }, + { + "title": "开播时间", + "key": "5", + "sort": "0,1" + } + ], + "total": 0, + "pageData": [], + "pages": 0 + } +} \ No newline at end of file diff --git a/bilibili_api/data/countries_codes.json b/bilibili_api/data/countries_codes.json new file mode 100644 index 0000000000000000000000000000000000000000..3ddda667dcc205faaddd23200569114031475dcc --- /dev/null +++ b/bilibili_api/data/countries_codes.json @@ -0,0 +1,1077 @@ +[ + { + "id": 1, + "cname": "中国大陆", + "country_id": "86" + }, + { + "id": 5, + "cname": "中国香港特别行政区", + "country_id": "852" + }, + { + "id": 2, + "cname": "中国澳门特别行政区", + "country_id": "853" + }, + { + "id": 3, + "cname": "中国台湾", + "country_id": "886" + }, + { + "id": 4, + "cname": "美国", + "country_id": "1" + }, + { + "id": 6, + "cname": "比利时", + "country_id": "32" + }, + { + "id": 7, + "cname": "澳大利亚", + "country_id": "61" + }, + { + "id": 8, + "cname": "法国", + "country_id": "33" + }, + { + "id": 9, + "cname": "加拿大", + "country_id": "1" + }, + { + "id": 10, + "cname": "日本", + "country_id": "81" + }, + { + "id": 11, + "cname": "新加坡", + "country_id": "65" + }, + { + "id": 12, + "cname": "韩国", + "country_id": "82" + }, + { + "id": 13, + "cname": "马来西亚", + "country_id": "60" + }, + { + "id": 14, + "cname": "英国", + "country_id": "44" + }, + { + "id": 15, + "cname": "意大利", + "country_id": "39" + }, + { + "id": 16, + "cname": "德国", + "country_id": "49" + }, + { + "id": 18, + "cname": "俄罗斯", + "country_id": "7" + }, + { + "id": 19, + "cname": "新西兰", + "country_id": "64" + }, + { + "id": 22, + "cname": "阿富汗", + "country_id": "93" + }, + { + "id": 20, + "cname": "阿尔巴尼亚", + "country_id": "355" + }, + { + "id": 21, + "cname": "阿尔及利亚", + "country_id": "213" + }, + { + "id": 31, + "cname": "安道尔", + "country_id": "376" + }, + { + "id": 32, + "cname": "安哥拉", + "country_id": "244" + }, + { + "id": 33, + "cname": "安提瓜岛和巴布达", + "country_id": "1268" + }, + { + "id": 23, + "cname": "阿根廷", + "country_id": "54" + }, + { + "id": 204, + "cname": "亚美尼亚", + "country_id": "374" + }, + { + "id": 183, + "cname": "阿森松岛", + "country_id": "247" + }, + { + "id": 34, + "cname": "奥地利", + "country_id": "43" + }, + { + "id": 26, + "cname": "阿塞拜疆", + "country_id": "994" + }, + { + "id": 37, + "cname": "巴哈马群岛", + "country_id": "1242" + }, + { + "id": 40, + "cname": "巴林", + "country_id": "973" + }, + { + "id": 131, + "cname": "孟加拉国", + "country_id": "880" + }, + { + "id": 35, + "cname": "巴巴多斯", + "country_id": "1246" + }, + { + "id": 43, + "cname": "白俄罗斯", + "country_id": "375" + }, + { + "id": 52, + "cname": "伯利兹", + "country_id": "501" + }, + { + "id": 46, + "cname": "贝宁", + "country_id": "229" + }, + { + "id": 44, + "cname": "百慕大群岛", + "country_id": "1441" + }, + { + "id": 54, + "cname": "不丹", + "country_id": "975" + }, + { + "id": 51, + "cname": "玻利维亚", + "country_id": "591" + }, + { + "id": 49, + "cname": "波黑", + "country_id": "387" + }, + { + "id": 53, + "cname": "博茨瓦纳", + "country_id": "267" + }, + { + "id": 42, + "cname": "巴西", + "country_id": "55" + }, + { + "id": 193, + "cname": "文莱", + "country_id": "673" + }, + { + "id": 45, + "cname": "保加利亚", + "country_id": "359" + }, + { + "id": 55, + "cname": "布基纳法索", + "country_id": "226" + }, + { + "id": 56, + "cname": "布隆迪", + "country_id": "257" + }, + { + "id": 96, + "cname": "柬埔寨", + "country_id": "855" + }, + { + "id": 100, + "cname": "喀麦隆", + "country_id": "237" + }, + { + "id": 72, + "cname": "佛得角", + "country_id": "238" + }, + { + "id": 102, + "cname": "开曼群岛", + "country_id": "1345" + }, + { + "id": 68, + "cname": "非洲中部", + "country_id": "236" + }, + { + "id": 214, + "cname": "乍得", + "country_id": "235" + }, + { + "id": 216, + "cname": "智利", + "country_id": "56" + }, + { + "id": 77, + "cname": "哥伦比亚", + "country_id": "57" + }, + { + "id": 103, + "cname": "科摩罗", + "country_id": "269" + }, + { + "id": 75, + "cname": "刚果", + "country_id": "242" + }, + { + "id": 76, + "cname": "刚果(金)", + "country_id": "243" + }, + { + "id": 107, + "cname": "库克岛", + "country_id": "682" + }, + { + "id": 78, + "cname": "哥斯达黎加", + "country_id": "506" + }, + { + "id": 105, + "cname": "克罗地亚", + "country_id": "385" + }, + { + "id": 81, + "cname": "古巴", + "country_id": "53" + }, + { + "id": 162, + "cname": "塞浦路斯", + "country_id": "357" + }, + { + "id": 97, + "cname": "捷克", + "country_id": "420" + }, + { + "id": 58, + "cname": "丹麦", + "country_id": "45" + }, + { + "id": 59, + "cname": "迪戈加西亚岛", + "country_id": "246" + }, + { + "id": 90, + "cname": "吉布提", + "country_id": "253" + }, + { + "id": 61, + "cname": "多米尼加", + "country_id": "1767" + }, + { + "id": 62, + "cname": "多米尼加代表", + "country_id": "1809" + }, + { + "id": 63, + "cname": "厄瓜多尔", + "country_id": "593" + }, + { + "id": 27, + "cname": "埃及", + "country_id": "20" + }, + { + "id": 157, + "cname": "萨尔瓦多", + "country_id": "503" + }, + { + "id": 57, + "cname": "赤道几内亚", + "country_id": "240" + }, + { + "id": 64, + "cname": "厄立特里亚", + "country_id": "291" + }, + { + "id": 30, + "cname": "爱沙尼亚", + "country_id": "372" + }, + { + "id": 28, + "cname": "埃塞俄比亚", + "country_id": "251" + }, + { + "id": 73, + "cname": "福克兰岛", + "country_id": "500" + }, + { + "id": 65, + "cname": "法罗岛", + "country_id": "298" + }, + { + "id": 70, + "cname": "斐济", + "country_id": "679" + }, + { + "id": 71, + "cname": "芬兰", + "country_id": "358" + }, + { + "id": 67, + "cname": "法属圭亚那", + "country_id": "594" + }, + { + "id": 66, + "cname": "法属波利尼西亚", + "country_id": "689" + }, + { + "id": 95, + "cname": "加蓬", + "country_id": "241" + }, + { + "id": 74, + "cname": "冈比亚", + "country_id": "220" + }, + { + "id": 154, + "cname": "格鲁吉亚", + "country_id": "995" + }, + { + "id": 94, + "cname": "加纳", + "country_id": "233" + }, + { + "id": 215, + "cname": "直布罗陀", + "country_id": "350" + }, + { + "id": 199, + "cname": "希腊", + "country_id": "30" + }, + { + "id": 80, + "cname": "格陵兰岛", + "country_id": "299" + }, + { + "id": 79, + "cname": "格林纳达", + "country_id": "1473" + }, + { + "id": 82, + "cname": "瓜德罗普岛", + "country_id": "590" + }, + { + "id": 83, + "cname": "关岛", + "country_id": "1671" + }, + { + "id": 92, + "cname": "几内亚", + "country_id": "224" + }, + { + "id": 93, + "cname": "几内亚比绍", + "country_id": "245" + }, + { + "id": 84, + "cname": "海地", + "country_id": "509" + }, + { + "id": 87, + "cname": "洪都拉斯", + "country_id": "504" + }, + { + "id": 201, + "cname": "匈牙利", + "country_id": "36" + }, + { + "id": 47, + "cname": "冰岛", + "country_id": "354" + }, + { + "id": 209, + "cname": "印度", + "country_id": "91" + }, + { + "id": 210, + "cname": "印尼", + "country_id": "62" + }, + { + "id": 207, + "cname": "伊朗", + "country_id": "98" + }, + { + "id": 206, + "cname": "伊拉克", + "country_id": "964" + }, + { + "id": 29, + "cname": "爱尔兰", + "country_id": "353" + }, + { + "id": 208, + "cname": "以色列", + "country_id": "972" + }, + { + "id": 200, + "cname": "科特迪瓦", + "country_id": "225" + }, + { + "id": 203, + "cname": "牙买加", + "country_id": "1876" + }, + { + "id": 211, + "cname": "约旦", + "country_id": "962" + }, + { + "id": 106, + "cname": "肯尼亚", + "country_id": "254" + }, + { + "id": 89, + "cname": "基里巴斯", + "country_id": "686" + }, + { + "id": 85, + "cname": "朝鲜", + "country_id": "850" + }, + { + "id": 104, + "cname": "科威特", + "country_id": "965" + }, + { + "id": 91, + "cname": "吉尔吉斯斯坦", + "country_id": "996" + }, + { + "id": 110, + "cname": "老挝", + "country_id": "856" + }, + { + "id": 108, + "cname": "拉脱维亚", + "country_id": "371" + }, + { + "id": 111, + "cname": "黎巴嫩", + "country_id": "961" + }, + { + "id": 109, + "cname": "莱索托", + "country_id": "266" + }, + { + "id": 113, + "cname": "利比里亚", + "country_id": "231" + }, + { + "id": 114, + "cname": "利比亚", + "country_id": "218" + }, + { + "id": 112, + "cname": "立陶宛", + "country_id": "370" + }, + { + "id": 115, + "cname": "卢森堡", + "country_id": "352" + }, + { + "id": 124, + "cname": "马其顿", + "country_id": "389" + }, + { + "id": 118, + "cname": "马达加斯加", + "country_id": "261" + }, + { + "id": 121, + "cname": "马拉维", + "country_id": "265" + }, + { + "id": 119, + "cname": "马尔代夫", + "country_id": "960" + }, + { + "id": 122, + "cname": "马里", + "country_id": "223" + }, + { + "id": 120, + "cname": "马耳他", + "country_id": "356" + }, + { + "id": 123, + "cname": "马里亚纳岛", + "country_id": "1670" + }, + { + "id": 126, + "cname": "马歇尔岛", + "country_id": "692" + }, + { + "id": 125, + "cname": "马提尼克岛", + "country_id": "596" + }, + { + "id": 128, + "cname": "毛里塔尼亚", + "country_id": "222" + }, + { + "id": 127, + "cname": "毛里求斯", + "country_id": "230" + }, + { + "id": 139, + "cname": "墨西哥", + "country_id": "52" + }, + { + "id": 133, + "cname": "密克罗尼西亚", + "country_id": "691" + }, + { + "id": 135, + "cname": "摩尔多瓦", + "country_id": "373" + }, + { + "id": 137, + "cname": "摩纳哥", + "country_id": "377" + }, + { + "id": 129, + "cname": "蒙古", + "country_id": "976" + }, + { + "id": 130, + "cname": "蒙特塞拉特岛", + "country_id": "1664" + }, + { + "id": 136, + "cname": "摩洛哥", + "country_id": "212" + }, + { + "id": 138, + "cname": "莫桑比克", + "country_id": "258" + }, + { + "id": 134, + "cname": "缅甸", + "country_id": "95" + }, + { + "id": 140, + "cname": "纳米比亚", + "country_id": "264" + }, + { + "id": 143, + "cname": "瑙鲁", + "country_id": "674" + }, + { + "id": 144, + "cname": "尼泊尔", + "country_id": "977" + }, + { + "id": 86, + "cname": "荷兰", + "country_id": "31" + }, + { + "id": 145, + "cname": "尼加拉瓜", + "country_id": "505" + }, + { + "id": 146, + "cname": "尼日尔", + "country_id": "227" + }, + { + "id": 147, + "cname": "尼日利亚", + "country_id": "234" + }, + { + "id": 148, + "cname": "纽埃岛", + "country_id": "683" + }, + { + "id": 150, + "cname": "诺福克岛", + "country_id": "672" + }, + { + "id": 149, + "cname": "挪威", + "country_id": "47" + }, + { + "id": 25, + "cname": "阿曼", + "country_id": "968" + }, + { + "id": 38, + "cname": "巴基斯坦", + "country_id": "92" + }, + { + "id": 151, + "cname": "帕劳", + "country_id": "680" + }, + { + "id": 41, + "cname": "巴拿马", + "country_id": "507" + }, + { + "id": 36, + "cname": "巴布亚新几内亚", + "country_id": "675" + }, + { + "id": 39, + "cname": "巴拉圭", + "country_id": "595" + }, + { + "id": 132, + "cname": "秘鲁", + "country_id": "51" + }, + { + "id": 69, + "cname": "菲律宾", + "country_id": "63" + }, + { + "id": 50, + "cname": "波兰", + "country_id": "48" + }, + { + "id": 152, + "cname": "葡萄牙", + "country_id": "351" + }, + { + "id": 48, + "cname": "波多黎各", + "country_id": "1787" + }, + { + "id": 101, + "cname": "卡塔尔", + "country_id": "974" + }, + { + "id": 99, + "cname": "聚会岛", + "country_id": "262" + }, + { + "id": 117, + "cname": "罗马尼亚", + "country_id": "40" + }, + { + "id": 116, + "cname": "卢旺达", + "country_id": "250" + }, + { + "id": 159, + "cname": "萨摩亚,东部", + "country_id": "684" + }, + { + "id": 158, + "cname": "萨摩亚,西部", + "country_id": "685" + }, + { + "id": 168, + "cname": "圣马力诺", + "country_id": "378" + }, + { + "id": 166, + "cname": "圣多美和普林西比", + "country_id": "239" + }, + { + "id": 165, + "cname": "沙特阿拉伯", + "country_id": "966" + }, + { + "id": 161, + "cname": "塞内加尔", + "country_id": "221" + }, + { + "id": 163, + "cname": "塞舌尔共和国", + "country_id": "248" + }, + { + "id": 160, + "cname": "塞拉利昂", + "country_id": "232" + }, + { + "id": 171, + "cname": "斯洛伐克", + "country_id": "421" + }, + { + "id": 172, + "cname": "斯洛文尼亚", + "country_id": "386" + }, + { + "id": 176, + "cname": "所罗门群岛", + "country_id": "677" + }, + { + "id": 177, + "cname": "索马里", + "country_id": "252" + }, + { + "id": 141, + "cname": "南非", + "country_id": "27" + }, + { + "id": 198, + "cname": "西班牙", + "country_id": "34" + }, + { + "id": 170, + "cname": "斯里兰卡", + "country_id": "94" + }, + { + "id": 167, + "cname": "圣卢西亚", + "country_id": "1784" + }, + { + "id": 169, + "cname": "圣皮埃尔和密克隆群岛", + "country_id": "508" + }, + { + "id": 174, + "cname": "苏丹", + "country_id": "249" + }, + { + "id": 175, + "cname": "苏里南", + "country_id": "597" + }, + { + "id": 173, + "cname": "斯威士兰", + "country_id": "268" + }, + { + "id": 155, + "cname": "瑞典", + "country_id": "46" + }, + { + "id": 156, + "cname": "瑞士", + "country_id": "41" + }, + { + "id": 202, + "cname": "叙利亚", + "country_id": "963" + }, + { + "id": 179, + "cname": "坦桑尼亚", + "country_id": "255" + }, + { + "id": 178, + "cname": "泰国", + "country_id": "66" + }, + { + "id": 60, + "cname": "多哥", + "country_id": "228" + }, + { + "id": 188, + "cname": "托克劳岛", + "country_id": "690" + }, + { + "id": 180, + "cname": "汤加", + "country_id": "676" + }, + { + "id": 182, + "cname": "特立尼达和多巴哥", + "country_id": "1868" + }, + { + "id": 184, + "cname": "突尼斯", + "country_id": "216" + }, + { + "id": 186, + "cname": "土耳其", + "country_id": "90" + }, + { + "id": 187, + "cname": "土库曼斯坦", + "country_id": "993" + }, + { + "id": 181, + "cname": "特克斯和凯科斯", + "country_id": "1649" + }, + { + "id": 185, + "cname": "图瓦卢", + "country_id": "688" + }, + { + "id": 194, + "cname": "乌干达", + "country_id": "256" + }, + { + "id": 195, + "cname": "乌克兰", + "country_id": "380" + }, + { + "id": 24, + "cname": "阿联酋", + "country_id": "971" + }, + { + "id": 196, + "cname": "乌拉圭", + "country_id": "598" + }, + { + "id": 197, + "cname": "乌兹别克斯坦", + "country_id": "998" + }, + { + "id": 189, + "cname": "瓦努阿图", + "country_id": "678" + }, + { + "id": 192, + "cname": "委内瑞拉", + "country_id": "58" + }, + { + "id": 212, + "cname": "越南", + "country_id": "84" + }, + { + "id": 191, + "cname": "维珍群岛(英属)", + "country_id": "1284" + }, + { + "id": 190, + "cname": "维珍群岛(美属)", + "country_id": "1340" + }, + { + "id": 88, + "cname": "维克岛", + "country_id": "1808" + }, + { + "id": 153, + "cname": "瓦利斯群岛和富图纳群岛", + "country_id": "1681" + }, + { + "id": 205, + "cname": "也门", + "country_id": "967" + }, + { + "id": 142, + "cname": "塞尔维亚", + "country_id": "381" + }, + { + "id": 213, + "cname": "赞比亚", + "country_id": "260" + }, + { + "id": 164, + "cname": "桑给巴尔岛", + "country_id": "259" + }, + { + "id": 98, + "cname": "津巴布韦", + "country_id": "263" + } +] \ No newline at end of file diff --git a/bilibili_api/data/emote.json b/bilibili_api/data/emote.json new file mode 100644 index 0000000000000000000000000000000000000000..4b618f402b7ae93e41bbeffc2d3b975797508d3b --- /dev/null +++ b/bilibili_api/data/emote.json @@ -0,0 +1,12942 @@ +{ + "13208": "[2023]", + "14476": "[兔年]", + "3301": "[脱单doge]", + "1": "[微笑]", + "1906": "[口罩]", + "26": "[doge]", + "435": "[妙啊]", + "1950": "[OK]", + "1956": "[星星眼]", + "2374": "[辣眼睛]", + "415": "[吃瓜]", + "27": "[滑稽]", + "1902": "[呲牙]", + "510": "[打call]", + "2373": "[歪嘴]", + "1128": "[调皮]", + "5774": "[豹富]", + "3223": "[嗑瓜子]", + "509": "[笑哭]", + "4000": "[藏狐]", + "5406": "[脸红]", + "3302": "[给心心]", + "3303": "[嘟嘟]", + "1940": "[哦呼]", + "3": "[喜欢]", + "1273": "[酸了]", + "2": "[嫌弃]", + "6": "[害羞]", + "5": "[大哭]", + "1938": "[疑惑]", + "791": "[喜极而泣]", + "11": "[奸笑]", + "1958": "[笑]", + "833": "[偷笑]", + "13": "[惊讶]", + "15": "[捂脸]", + "23": "[阴险]", + "14": "[囧]", + "4": "[呆]", + "18": "[抠鼻]", + "12": "[大笑]", + "19": "[惊喜]", + "7": "[无语]", + "25": "[点赞]", + "1964": "[鼓掌]", + "9": "[尴尬]", + "16": "[灵魂出窍]", + "8": "[委屈]", + "20": "[傲娇]", + "21": "[疼]", + "1450": "[冷]", + "7647": "[热]", + "29": "[生病]", + "22": "[吓]", + "17": "[吐]", + "1939": "[捂眼]", + "31": "[嘘声]", + "1941": "[思考]", + "1942": "[再见]", + "1943": "[翻白眼]", + "1951": "[哈欠]", + "1952": "[奋斗]", + "1953": "[墨镜]", + "1954": "[难过]", + "1955": "[撇嘴]", + "1957": "[抓狂]", + "10": "[生气]", + "3827": "[水稻]", + "7180": "[奶茶干杯]", + "5936": "[汤圆]", + "1866": "[锦鲤]", + "1886": "[福到了]", + "1905": "[鸡腿]", + "2979": "[雪花]", + "2126": "[视频卫星]", + "1949": "[干杯]", + "32": "[黑洞]", + "550": "[爱心]", + "2010": "[胜利]", + "1947": "[加油]", + "1904": "[抱拳]", + "30": "[响指]", + "1903": "[保佑]", + "1887": "[福]", + "434": "[支持]", + "1944": "[拥抱]", + "1945": "[跪了]", + "1946": "[怪我咯]", + "1900": "[老鼠]", + "3146": "[牛年]", + "9781": "[三星堆]", + "7256": "[洛天依]", + "4367": "[坎公骑冠剑_吃鸡]", + "4368": "[坎公骑冠剑_钻石]", + "4369": "[坎公骑冠剑_无语]", + "3985": "[来古-沉思]", + "3986": "[来古-呆滞]", + "3987": "[来古-疑问]", + "3988": "[来古-震撼]", + "3989": "[来古-注意]", + "10844": "[初音未来_大笑]", + "4259": "[原神_哇]", + "4260": "[原神_哼]", + "4261": "[原神_嗯]", + "4262": "[原神_欸嘿]", + "4263": "[原神_喝茶]", + "4264": "[原神_生气]", + "4943": "[保卫萝卜_白眼]", + "4944": "[保卫萝卜_笔芯]", + "4945": "[保卫萝卜_哭哭]", + "4946": "[保卫萝卜_哇]", + "4947": "[保卫萝卜_问号]", + "6326": "[无悔华夏_不愧是你]", + "6327": "[无悔华夏_吃瓜]", + "6328": "[无悔华夏_达咩]", + "6329": "[无悔华夏_点赞]", + "6330": "[无悔华夏_好耶]", + "7094": "[奥比岛_搬砖]", + "7095": "[奥比岛_点赞]", + "7096": "[奥比岛_击爪]", + "7097": "[奥比岛_委屈]", + "7098": "[奥比岛_喜欢]", + "15717": "[黎明觉醒_怒了鸦]", + "15718": "[黎明觉醒_石化鸦]", + "15719": "[黎明觉醒_摊手鸦]", + "15720": "[黎明觉醒_比心鸦]", + "15721": "[黎明觉醒_哼白眼鸦]", + "33": "[tv_白眼]", + "34": "[tv_doge]", + "35": "[tv_坏笑]", + "36": "[tv_难过]", + "37": "[tv_生气]", + "38": "[tv_委屈]", + "39": "[tv_斜眼笑]", + "40": "[tv_呆]", + "41": "[tv_发怒]", + "42": "[tv_惊吓]", + "43": "[tv_呕吐]", + "44": "[tv_思考]", + "45": "[tv_微笑]", + "46": "[tv_疑问]", + "47": "[tv_大哭]", + "48": "[tv_鼓掌]", + "49": "[tv_抠鼻]", + "50": "[tv_亲亲]", + "51": "[tv_调皮]", + "52": "[tv_笑哭]", + "53": "[tv_晕]", + "54": "[tv_点赞]", + "55": "[tv_害羞]", + "56": "[tv_睡着]", + "57": "[tv_色]", + "58": "[tv_吐血]", + "59": "[tv_无奈]", + "60": "[tv_再见]", + "61": "[tv_流汗]", + "62": "[tv_偷笑]", + "63": "[tv_抓狂]", + "64": "[tv_黑人问号]", + "65": "[tv_困]", + "66": "[tv_打脸]", + "67": "[tv_闭嘴]", + "68": "[tv_鄙视]", + "69": "[tv_腼腆]", + "70": "[tv_馋]", + "71": "[tv_可爱]", + "72": "[tv_发财]", + "73": "[tv_生病]", + "74": "[tv_流鼻血]", + "75": "[tv_尴尬]", + "76": "[tv_大佬]", + "77": "[tv_流泪]", + "78": "[tv_冷漠]", + "79": "[tv_皱眉]", + "80": "[tv_鬼脸]", + "81": "[tv_调侃]", + "82": "[tv_目瞪口呆]", + "83": "[酷仔]", + "84": "[赞了]", + "85": "[暗中观察]", + "86": "[么么哒]", + "87": "[哭哭]", + "88": "[饿了]", + "89": "[问号]", + "90": "[嘿嘿]", + "91": "[卖萌]", + "92": "[喵]", + "2185": "( ゜- ゜)つロ", + "2201": "_(:з」∠)_", + "93": "(⌒▽⌒)", + "94": "( ̄▽ ̄)", + "95": "(=・ω・=)", + "2180": "(*°▽°*)八(*°▽°*)♪", + "2181": "✿ヽ(°▽°)ノ✿", + "2179": "(¦3【▓▓】", + "2189": "눈_눈", + "2195": "(ಡωಡ)", + "2193": "_(≧∇≦」∠)_", + "2182": "━━━∑(゚□゚*川━", + "96": "(`・ω・´)", + "100": "( ̄3 ̄)", + "2183": "✧(≖ ◡ ≖✿)", + "98": "(・∀・)", + "97": "(〜 ̄△ ̄)〜", + "103": "→_→", + "99": "(°∀°)ノ", + "101": "╮( ̄▽ ̄)╭", + "102": "( ´_ゝ`)", + "104": "←_←", + "105": "(;¬_¬)", + "106": "(゚Д゚≡゚д゚)!?", + "2184": "( ´・・)ノ(._.`)", + "107": "Σ(゚д゚;)", + "108": "Σ(  ̄□ ̄||)<", + "109": "(´;ω;`)", + "110": "(/TДT)/", + "111": "(^・ω・^)", + "112": "(。・ω・。)", + "113": "(● ̄(エ) ̄●)", + "114": "ε=ε=(ノ≧∇≦)ノ", + "115": "(´・_・`)", + "116": "(-_-#)", + "117": "( ̄へ ̄)", + "118": "( ̄ε(# ̄) Σ", + "120": "(╯°口°)╯(┴—┴", + "119": "ヽ(`Д´)ノ", + "2085": "(\"▔□▔)/", + "2186": "(º﹃º )", + "2187": "(๑>؂<๑)", + "2190": "。゚(゚´Д`)゚。", + "2191": "(∂ω∂)", + "2192": "(┯_┯)", + "2194": "(・ω< )★", + "2196": "( ๑ˊ•̥▵•)੭₎₎", + "2197": "¥ㄟ(´・ᴗ・`)ノ¥", + "2198": "Σ_(꒪ཀ꒪」∠)_", + "2199": "٩(๛ ˘ ³˘)۶❤", + "2200": "(๑‾᷅^‾᷅๑)", + "121": "[小电视_笑]", + "122": "[小电视_发愁]", + "123": "[小电视_赞]", + "124": "[小电视_差评]", + "125": "[小电视_嘟嘴]", + "126": "[小电视_汗]", + "127": "[小电视_害羞]", + "128": "[小电视_吃惊]", + "129": "[小电视_哭泣]", + "130": "[小电视_太太喜欢]", + "131": "[小电视_好怒啊]", + "132": "[小电视_困惑]", + "133": "[小电视_我好兴奋]", + "134": "[小电视_思索]", + "135": "[小电视_无语]", + "136": "[2233娘_大笑]", + "137": "[2233娘_吃惊]", + "138": "[2233娘_大哭]", + "139": "[2233娘_耶]", + "140": "[2233娘_卖萌]", + "141": "[2233娘_疑问]", + "142": "[2233娘_汗]", + "143": "[2233娘_困惑]", + "144": "[2233娘_怒]", + "145": "[2233娘_委屈]", + "146": "[2233娘_郁闷]", + "147": "[2233娘_第一]", + "148": "[2233娘_喝水]", + "149": "[2233娘_吐魂]", + "150": "[2233娘_无言]", + "151": "[蛆音娘_卖萌]", + "152": "[蛆音娘_吃瓜群众]", + "153": "[蛆音娘_吃惊]", + "154": "[蛆音娘_害怕]", + "155": "[蛆音娘_扶额]", + "156": "[蛆音娘_滑稽]", + "157": "[蛆音娘_哼]", + "158": "[蛆音娘_机智]", + "159": "[蛆音娘_哭泣]", + "160": "[蛆音娘_睡觉觉]", + "161": "[蛆音娘_生气]", + "162": "[蛆音娘_偷看]", + "163": "[蛆音娘_吐血]", + "164": "[蛆音娘_无语]", + "165": "[蛆音娘_摇头]", + "166": "[蛆音娘_疑问]", + "167": "[蛆音娘_die]", + "168": "[蛆音娘_OK]", + "169": "[蛆音娘_肥皂]", + "170": "[蛆音娘_大笑]", + "171": "[芮小凸小凹_嗯?]", + "172": "[芮小凸小凹_超棒]", + "173": "[芮小凸小凹_哎]", + "174": "[芮小凸小凹_666]", + "175": "[芮小凸小凹_哼]", + "176": "[芮小凸小凹_怀疑]", + "177": "[芮小凸小凹_发现]", + "178": "[芮小凸小凹_绝望]", + "179": "[芮小凸小凹_可爱]", + "180": "[芮小凸小凹_警告]", + "181": "[芮小凸小凹_呵呵]", + "182": "[芮小凸小凹_膜拜]", + "183": "[芮小凸小凹_哭]", + "184": "[芮小凸小凹_无聊]", + "185": "[芮小凸小凹_围观]", + "186": "[芮小凸小凹_失落]", + "187": "[芮小凸小凹_是吗]", + "188": "[芮小凸小凹_心动]", + "189": "[芮小凸小凹_喜欢]", + "190": "[芮小凸小凹_嘻嘻]", + "191": "[芮小凸小凹_震撼]", + "192": "[芮小凸小凹_有见解]", + "193": "[芮小凸小凹_震惊]", + "194": "[芮小凸小凹_心痛]", + "195": "[洛天依_傲娇]", + "196": "[洛天依_吃包群众]", + "197": "[洛天依_吃药]", + "198": "[洛天依_滑稽]", + "199": "[洛天依_哈哈哈]", + "200": "[洛天依_看透一切]", + "201": "[洛天依_打call]", + "202": "[洛天依_前排]", + "203": "[洛天依_去吧]", + "204": "[洛天依_冷漠]", + "205": "[洛天依_可以]", + "206": "[洛天依_掀桌]", + "207": "[洛天依_消灭你]", + "208": "[洛天依_阴阳先生]", + "209": "[洛天依_无言以对]", + "210": "[洛天依_???]", + "211": "[洛天依_爱你哦]", + "212": "[小A和小B_喝茶]", + "213": "[小A和小B_报警]", + "214": "[小A和小B_大哭]", + "215": "[小A和小B_低头]", + "216": "[小A和小B_哈哈哈哈]", + "217": "[小A和小B_司令]", + "218": "[小A和小B_撒花]", + "219": "[小A和小B_摊手]", + "220": "[小A和小B_问号]", + "221": "[小A和小B_躺]", + "222": "[小A和小B_无语]", + "223": "[小A和小B_应援]", + "224": "[小A和小B_NO]", + "225": "[小A和小B_OK]", + "226": "[小A和小B_吃瓜]", + "227": "[小A和小B_捕捉]", + "228": "[请吃红小豆吧!_wow]", + "229": "[请吃红小豆吧!_吃包]", + "230": "[请吃红小豆吧!_倒地]", + "231": "[请吃红小豆吧!_盯]", + "232": "[请吃红小豆吧!_翻白眼]", + "233": "[请吃红小豆吧!_好难]", + "234": "[请吃红小豆吧!_喝水]", + "235": "[请吃红小豆吧!_嘿嘿嘿]", + "236": "[请吃红小豆吧!_开心]", + "237": "[请吃红小豆吧!_哭唧唧]", + "238": "[请吃红小豆吧!_略略略]", + "239": "[请吃红小豆吧!_么么]", + "240": "[请吃红小豆吧!_哦]", + "241": "[请吃红小豆吧!_撒娇]", + "242": "[请吃红小豆吧!_受到惊吓]", + "243": "[请吃红小豆吧!_睡觉]", + "244": "[请吃红小豆吧!_问号脸]", + "245": "[请吃红小豆吧!_耶耶耶]", + "246": "[请吃红小豆吧!_一脸懵逼]", + "247": "[请吃红小豆吧!_眨眼]", + "248": "[正经人_欢呼]", + "249": "[正经人_火]", + "250": "[正经人_挤]", + "251": "[正经人_惊]", + "252": "[正经人_放屁]", + "253": "[正经人_ok]", + "254": "[正经人_趴]", + "255": "[正经人_开始]", + "256": "[正经人_揉脸]", + "257": "[正经人_躺]", + "258": "[正经人_拉]", + "259": "[正经人_心]", + "260": "[正经人_悠闲]", + "261": "[正经人_赞]", + "262": "[正经人_哭]", + "263": "[星梦手记_吃瓜]", + "264": "[星梦手记_薇薇惊吓]", + "265": "[星梦手记_惺梛认真]", + "266": "[星梦手记_石化]", + "267": "[星梦手记_问号]", + "268": "[星梦手记_卖萌]", + "269": "[星梦手记_尴尬]", + "270": "[星梦手记_要抱]", + "271": "[星梦手记_未来认真]", + "272": "[星梦手记_摊手]", + "273": "[星梦手记_委屈]", + "275": "[星梦手记_没钱]", + "276": "[星梦手记_无辜]", + "277": "[星梦手记_弃疗]", + "278": "[星梦手记_雨照惊吓]", + "279": "[冷兔_吃瓜]", + "280": "[冷兔_233]", + "281": "[冷兔_这不科学]", + "282": "[冷兔_???]", + "283": "[冷兔_等更]", + "284": "[冷兔_丢你雷姆]", + "285": "[冷兔_耶]", + "286": "[冷兔_无语]", + "287": "[冷兔_额]", + "288": "[冷兔_呵呵]", + "289": "[冷兔_一无所知]", + "290": "[冷兔_体前屈]", + "291": "[冷兔_喜欢]", + "292": "[冷兔_哭]", + "293": "[冷兔_赞]", + "294": "[冷兔_低头]", + "295": "[冷兔_卖萌]", + "296": "[冷兔_扶额]", + "297": "[冷兔_掀桌]", + "298": "[冷兔_吸欧气]", + "299": "[那兔_合个影]", + "300": "[那兔_囧]", + "301": "[那兔_。。。]", + "302": "[那兔_好滴]", + "303": "[那兔_讲道理]", + "304": "[那兔_懒得理你]", + "305": "[那兔_奈我何]", + "306": "[那兔_你丫试试]", + "307": "[那兔_深思]", + "308": "[那兔_恶代官]", + "309": "[那兔_说什么喵]", + "310": "[那兔_心碎]", + "311": "[那兔_一见钟情]", + "312": "[那兔_找事儿]", + "313": "[那兔_痴呆]", + "314": "[那兔_呃]", + "315": "[那兔_擦]", + "316": "[我家大师兄脑子有坑_比心]", + "317": "[我家大师兄脑子有坑_吃瓜]", + "318": "[我家大师兄脑子有坑_大笑]", + "319": "[我家大师兄脑子有坑_汗]", + "320": "[我家大师兄脑子有坑_机智]", + "321": "[我家大师兄脑子有坑_哭]", + "322": "[我家大师兄脑子有坑_期待]", + "323": "[我家大师兄脑子有坑_撒花]", + "324": "[我家大师兄脑子有坑_吐魂]", + "325": "[我家大师兄脑子有坑_晕]", + "326": "[喂,看见耳朵啦!_懵]", + "327": "[喂,看见耳朵啦!_嗷]", + "328": "[喂,看见耳朵啦!_抱大腿]", + "329": "[喂,看见耳朵啦!_抱走]", + "330": "[喂,看见耳朵啦!_壁咚]", + "331": "[喂,看见耳朵啦!_冷漠]", + "332": "[喂,看见耳朵啦!_嘤嘤嘤]", + "333": "[喂,看见耳朵啦!_吓]", + "334": "[喂,看见耳朵啦!_chu]", + "335": "[喂,看见耳朵啦!_爱你]", + "336": "[喂,看见耳朵啦!_舔]", + "337": "[喂,看见耳朵啦!_睡觉觉]", + "338": "[喂,看见耳朵啦!_举高高]", + "339": "[喂,看见耳朵啦!_说什么都对]", + "340": "[喂,看见耳朵啦!_不可描述]", + "341": "[喂,看见耳朵啦!_摔倒啦]", + "342": "[小绿和小蓝_不想说话]", + "343": "[小绿和小蓝_吵架]", + "344": "[小绿和小蓝_得意脸]", + "345": "[小绿和小蓝_高兴]", + "346": "[小绿和小蓝_哦]", + "347": "[小绿和小蓝_捂脸]", + "348": "[小绿和小蓝_邪恶脸]", + "349": "[小绿和小蓝_要哭了]", + "350": "[小绿和小蓝_疑问]", + "351": "[小绿和小蓝_打滚]", + "352": "[小绿和小蓝_诶]", + "353": "[小绿和小蓝_机智一比]", + "354": "[小绿和小蓝_喵喵喵]", + "355": "[小绿和小蓝_跑]", + "356": "[小绿和小蓝_喂]", + "357": "[小绿和小蓝_已关机]", + "358": "[小绿和小蓝_直接躺平]", + "359": "[小绿和小蓝_呆住]", + "360": "[小绿和小蓝_哈哈]", + "361": "[小绿和小蓝_喝水]", + "362": "[小绿和小蓝_生气]", + "363": "[小绿和小蓝_哇啊啊啊]", + "364": "[小绿和小蓝_一本正经]", + "365": "[小绿和小蓝_惊呆]", + "366": "[小绿和小蓝_开心]", + "367": "[小绿和小蓝_苦恼]", + "368": "[小绿和小蓝_灵光乍现]", + "369": "[小绿和小蓝_思考]", + "370": "[一人之下_机智]", + "371": "[一人之下_刀]", + "372": "[一人之下_埋了]", + "373": "[一人之下_蟑螂]", + "374": "[一人之下_抽打]", + "375": "[一人之下_燃]", + "376": "[一人之下_超凶]", + "377": "[一人之下_干翻苍穹]", + "378": "[一人之下_糟了]", + "379": "[一人之下_孙贼]", + "380": "[一人之下_养生]", + "381": "[一人之下_瘫]", + "382": "[一人之下_打call]", + "383": "[一人之下_比心]", + "384": "[一人之下_摸头]", + "385": "[一人之下_八卦]", + "386": "[一人之下_狐狸]", + "387": "[一人之下_承让]", + "388": "[一人之下_哎?]", + "389": "[一人之下_叹气]", + "390": "[一人之下_雷]", + "391": "[狐妖_不服憋着]", + "392": "[狐妖_吃瓜]", + "393": "[狐妖_不明嚼栗]", + "394": "[狐妖_抽打]", + "395": "[狐妖_吃我安利]", + "396": "[狐妖_伐开心]", + "397": "[狐妖_惊]", + "398": "[狐妖_大笑]", + "399": "[狐妖_震惊]", + "400": "[狐妖_脸红]", + "401": "[狐妖_冷漠]", + "402": "[狐妖_哭]", + "403": "[狐妖_目瞪口呆]", + "404": "[狐妖_前排]", + "405": "[狐妖_沙发]", + "406": "[狐妖_掀桌]", + "407": "[狐妖_心]", + "408": "[狐妖_晕]", + "409": "[狐妖_吃药]", + "410": "[狐妖_mdzz]", + "416": "[灵笼_鄙视]", + "417": "[灵笼_不用了]", + "418": "[灵笼_吃瓜]", + "419": "[灵笼_吃糖]", + "420": "[灵笼_机智]", + "421": "[灵笼_惊吓]", + "422": "[灵笼_生闷气]", + "424": "[灵笼_玩手机]", + "426": "[灵笼_问号]", + "427": "[灵笼_嫌弃]", + "428": "[灵笼_笑哭]", + "429": "[灵笼_兴奋]", + "430": "[灵笼_阴暗]", + "431": "[灵笼_约吗]", + "432": "[灵笼_boom]", + "433": "[灵笼_偷窥]", + "437": "[异常生物见闻录_good]", + "438": "[异常生物见闻录_不信任]", + "456": "[异常生物见闻录_财迷]", + "442": "[异常生物见闻录_嘲讽]", + "450": "[异常生物见闻录_痴呆]", + "447": "[异常生物见闻录_憧憬]", + "457": "[异常生物见闻录_道德标兵]", + "448": "[异常生物见闻录_抵死不从]", + "441": "[异常生物见闻录_发现食物]", + "444": "[异常生物见闻录_奸笑]", + "451": "[异常生物见闻录_绝望]", + "446": "[异常生物见闻录_开心]", + "443": "[异常生物见闻录_困倦]", + "439": "[异常生物见闻录_冷汗]", + "440": "[异常生物见闻录_凉凉]", + "452": "[异常生物见闻录_萌]", + "455": "[异常生物见闻录_请闭嘴]", + "445": "[异常生物见闻录_帅醒]", + "449": "[异常生物见闻录_无语]", + "453": "[异常生物见闻录_装傻]", + "454": "[异常生物见闻录_装文静]", + "458": "[罗小黑_鼓掌]", + "459": "[罗小黑_你好呀]", + "460": "[罗小黑_加油]", + "461": "[罗小黑_可可爱爱]", + "462": "[罗小黑_吃瓜]", + "463": "[罗小黑_嗨]", + "464": "[罗小黑_大麦]", + "465": "[罗小黑_干杯]", + "466": "[罗小黑_找彩蛋]", + "467": "[罗小黑_撒花]", + "468": "[罗小黑_来了]", + "469": "[罗小黑_歪在吗]", + "470": "[罗小黑_求包养]", + "471": "[罗小黑_看电影]", + "472": "[罗小黑_真棒]", + "473": "[罗小黑_自来水]", + "14690": "[热词表情_世萌双冠]", + "14716": "[热词系列_肥肠自信]", + "14717": "[热词系列_我故意的]", + "3217": "[热词系列_新年快乐]", + "5859": "[热词系列_恭喜发财]", + "5858": "[热词系列_大吉大利]", + "5863": "[热词系列_身体健康]", + "5771": "[热词系列_红红火火]", + "5864": "[热词系列_升职加薪]", + "5857": "[热词系列_吃嘛嘛香]", + "5865": "[热词系列_早日脱单]", + "1937": "[热词系列_知识增加]", + "1483": "[热词系列_三连]", + "1493": "[热词系列_妙啊]", + "6533": "[热词系列_优雅]", + "7179": "[热词表情_哎呦不错哦]", + "3218": "[热词系列_好耶]", + "1492": "[热词系列_你币有了]", + "1482": "[热词系列_吹爆]", + "13712": "[念诗之王]", + "4450": "[热词系列_对象]", + "3304": "[热词系列_不孤鸟]", + "7257": "[热词系列_洛水天依]", + "1494": "[热词系列_秀]", + "1966": "[热词系列_标准结局]", + "5584": "[热词系列_B站有房]", + "5275": "[热词系列_破防了]", + "5037": "[热词系列_多谢款待]", + "4842": "[热词系列_燃起来了]", + "4043": "[热词系列_仙人指路]", + "3727": "[热词系列_饮茶先啦]", + "3219": "[热词系列_再来亿遍]", + "3220": "[热词系列_热乎]", + "3221": "[热词系列_好活]", + "2651": "[热词系列_好家伙]", + "3222": "[热词系列_排面]", + "2545": "[热词系列_爷青回]", + "2494": "[热词系列_芜湖起飞]", + "3444": "[热词系列_夺笋呐]", + "3696": "[热词系列_两面包夹芝士]", + "2212": "[热词系列_梦幻联动]", + "2083": "[热词系列_泪目]", + "2082": "[热词系列_保护]", + "1967": "[热词系列_爱了爱了]", + "1487": "[热词系列_可以]", + "2147": "[热词系列_希望没事]", + "1485": "[热词系列_打卡]", + "2321": "[热词系列_DNA]", + "1963": "[热词系列_这次一定]", + "1884": "[热词系列_AWSL]", + "2022": "[热词系列_递话筒]", + "1959": "[热词系列_你细品]", + "2009": "[热词系列_咕咕]", + "1998": "[热词系列_张三]", + "1965": "[热词系列_害]", + "1969": "[热词系列_我裂开了]", + "1960": "[热词系列_有内味了]", + "1961": "[热词系列_猛男必看]", + "1885": "[热词系列_奥力给]", + "1869": "[热词系列_神仙UP]", + "2021": "[热词系列_问号]", + "1962": "[热词系列_我哭了]", + "1484": "[热词系列_高产]", + "1867": "[热词系列_不愧是你]", + "1491": "[热词系列_真香]", + "1490": "[热词系列_我全都要]", + "1495": "[热词系列_爷关更]", + "6951": "[热词系列_福建舰]", + "1488": "[热词系列_锤]", + "1486": "[热词系列_我酸了]", + "1496": "[热词系列_有生之年]", + "1497": "[热词系列_镇站之宝]", + "1870": "[热词系列_我太南了]", + "1489": "[热词系列_完结撒花]", + "1868": "[热词系列_大师球]", + "1871": "[热词系列_知识盲区]", + "1873": "[热词系列_“狼火”]", + "2125": "[热词系列_你可真星]", + "1499": "[沈剑心_滑稽]", + "1500": "[沈剑心_抱大腿]", + "1501": "[沈剑心_不屑]", + "1502": "[沈剑心_苍天呐]", + "1503": "[沈剑心_我秃了]", + "1504": "[沈剑心_得意]", + "1505": "[沈剑心_方了]", + "1506": "[沈剑心_开心]", + "1507": "[沈剑心_呕吐]", + "1508": "[沈剑心_咆哮]", + "1509": "[沈剑心_悄悄话]", + "1510": "[沈剑心_问号]", + "1511": "[沈剑心_无语]", + "1525": "[沈剑心_郁闷]", + "1526": "[沈剑心_吓到]", + "3172": "[沈剑心_摸鱼]", + "3173": "[沈剑心_让我看看]", + "3174": "[沈剑心_傻眼]", + "3175": "[沈剑心_思考]", + "3176": "[沈剑心_赌气]", + "1548": "[咕咕_吃饱了]", + "1549": "[咕咕_出门了]", + "1550": "[咕咕_沉默]", + "1551": "[咕咕_剪视频]", + "1552": "[咕咕_惊吓]", + "1553": "[咕咕_冷静一下]", + "1554": "[咕咕_嚯奶茶]", + "1555": "[咕咕_命运扼脖]", + "1556": "[咕咕_头秃]", + "1557": "[咕咕_灵魂出窍]", + "1558": "[咕咕_流泪]", + "1559": "[咕咕_睡了]", + "1560": "[咕咕_自闭了]", + "1561": "[咕咕_转笔]", + "1562": "[咕咕_宅]", + "1578": "[炎炎消防队_点点点]", + "1579": "[炎炎消防队_独眼巨猩]", + "1582": "[炎炎消防队_恶魔]", + "1583": "[炎炎消防队_拉托姆]", + "1584": "[炎炎消防队_咧嘴惊恐]", + "1585": "[炎炎消防队_灵光一闪]", + "1586": "[炎炎消防队_凝视]", + "1587": "[炎炎消防队_怨气]", + "1588": "[炎炎消防队_讨厌]", + "1589": "[炎炎消防队_少女心]", + "1590": "[炎炎消防队_肯定]", + "1591": "[炎炎消防队_愉快]", + "1592": "[炎炎消防队_噗嗤噗嗤]", + "1593": "[炎炎消防队_灭火]", + "1594": "[炎炎消防队_禁止入内]", + "1595": "[伍六七_观察]", + "1596": "[伍六七_无语]", + "1597": "[伍六七_哭]", + "1598": "[伍六七_右吃惊]", + "1599": "[伍六七_左吃惊]", + "1600": "[伍六七_wink]", + "1601": "[伍六七_超凶]", + "1602": "[伍六七_搓手]", + "1605": "[伍六七_冷漠]", + "1603": "[伍六七_帅]", + "1604": "[伍六七_懵]", + "1606": "[伍六七_嘻嘻]", + "1617": "[一群喵_害羞]", + "1618": "[一群喵_汗]", + "1619": "[一群喵_惊吓]", + "1620": "[一群喵_哭]", + "1621": "[一群喵_困惑]", + "1622": "[一群喵_生气]", + "1623": "[一群喵_喜欢]", + "1624": "[一群喵_笑]", + "1625": "[一群喵_兴奋]", + "1626": "[一群喵_阴险]", + "1627": "[个性灵笼_暗中观察]", + "1628": "[个性灵笼_开心]", + "1629": "[个性灵笼_哭]", + "1630": "[个性灵笼_冷]", + "1631": "[个性灵笼_难受]", + "1632": "[个性灵笼_窃喜]", + "1633": "[个性灵笼_石化]", + "1634": "[个性灵笼_喜欢]", + "1635": "[个性灵笼_凶]", + "1636": "[个性灵笼_耶]", + "1970": "[少前小剧场_期待]", + "1971": "[少前小剧场_心虚]", + "1972": "[少前小剧场_叶]", + "1637": "[少前小剧场_比心]", + "1638": "[少前小剧场_吃瓜]", + "1639": "[少前小剧场_打call]", + "1640": "[少前小剧场_冲鸭]", + "1641": "[少前小剧场_呆]", + "1642": "[少前小剧场_得意]", + "1643": "[少前小剧场_干杯]", + "1644": "[少前小剧场_害羞]", + "1645": "[少前小剧场_黑化]", + "1646": "[少前小剧场_精神抖擞]", + "1647": "[少前小剧场_哭]", + "1648": "[少前小剧场_卖萌]", + "1649": "[少前小剧场_钱钱]", + "1650": "[少前小剧场_认真]", + "1651": "[少前小剧场_色]", + "1652": "[少前小剧场_睡觉]", + "1653": "[少前小剧场_晚安]", + "1654": "[少前小剧场_无语]", + "1655": "[少前小剧场_嫌弃]", + "1656": "[少前小剧场_小公主]", + "1657": "[少前小剧场_耶]", + "1658": "[少前小剧场_疑惑]", + "1659": "[少前小剧场_震惊]", + "1660": "[少前小剧场_醉]", + "1661": "[天选之子_讨厌啦]", + "1662": "[天选之子_心虚]", + "1663": "[天选之子_吃瓜]", + "1664": "[天选之子_什么鬼]", + "1665": "[天选之子_生气气]", + "1666": "[天选之子_原来如此]", + "1667": "[天选之子_我去]", + "1668": "[天选之子_打人了]", + "1669": "[天选之子_辛酸泪]", + "1670": "[天选之子_拒绝]", + "1681": "[战双帕弥什_吃瓜]", + "1682": "[战双帕弥什_哼]", + "1683": "[战双帕弥什_抱抱]", + "1684": "[战双帕弥什_画圈圈]", + "1685": "[战双帕弥什_痴汉]", + "1686": "[战双帕弥什_挠头]", + "1687": "[战双帕弥什_肥宅]", + "1688": "[战双帕弥什_柠檬茶]", + "1689": "[战双帕弥什_诶嘿]", + "1690": "[战双帕弥什_拍桌]", + "1691": "[战双帕弥什_哭哭]", + "1692": "[战双帕弥什_吐血]", + "1693": "[战双帕弥什_凉了]", + "1694": "[战双帕弥什_托腮]", + "1695": "[战双帕弥什_喷了]", + "1696": "[战双帕弥什_星星眼]", + "1697": "[战双帕弥什_我即天命]", + "1698": "[战双帕弥什_作战预备]", + "1846": "[平安物语_变欧喷雾]", + "1847": "[平安物语_生气]", + "1848": "[平安物语_你币有了]", + "1849": "[平安物语_观察]", + "1850": "[平安物语_吃惊]", + "1851": "[平安物语_呆住]", + "1852": "[平安物语_喜欢]", + "1853": "[平安物语_我太难了]", + "1854": "[平安物语_沉默]", + "1856": "[2020拜年祭_哈气]", + "1857": "[2020拜年祭_期待]", + "1858": "[2020拜年祭_吸欧气]", + "1859": "[2020拜年祭_领红包]", + "1860": "[2020拜年祭_谢谢老板]", + "1861": "[2020拜年祭_灯笼]", + "1862": "[2020拜年祭_火锅]", + "1863": "[2020拜年祭_2020]", + "1864": "[2020拜年祭_红包]", + "1865": "[2020拜年祭_新年好]", + "1874": "[镇魂街_给我康康]", + "1875": "[镇魂街_耍酷]", + "1876": "[镇魂街_冷漠]", + "1877": "[镇魂街_脸红]", + "1878": "[镇魂街_消除记忆]", + "1879": "[镇魂街_偷笑]", + "1880": "[镇魂街_捶地]", + "1881": "[镇魂街_阴谋]", + "1882": "[镇魂街_投币呀]", + "1883": "[镇魂街_真香]", + "1888": "[大王不高兴_开心]", + "1889": "[大王不高兴_生气]", + "1890": "[大王不高兴_哭]", + "1891": "[大王不高兴_比心]", + "1892": "[大王不高兴_疑问]", + "1893": "[大王不高兴_我好了]", + "1894": "[大王不高兴_汗]", + "1895": "[大王不高兴_点赞]", + "1896": "[大王不高兴_害怕]", + "1897": "[大王不高兴_滑稽]", + "1898": "[大王不高兴_目瞪口呆]", + "1899": "[大王不高兴_前排]", + "1907": "[崩坏3_点赞]", + "1908": "[崩坏3_入欧]", + "1909": "[崩坏3_脱非]", + "1910": "[崩坏3_快乐]", + "1911": "[崩坏3_注入灵魂]", + "1912": "[崩坏3_危险]", + "1913": "[崩坏3_吃瓜]", + "1914": "[崩坏3_糖葫芦]", + "1915": "[崩坏3_路过]", + "1916": "[崩坏3_魔法少女]", + "1917": "[崩坏3_特效]", + "1918": "[崩坏3_唢呐]", + "1919": "[崩坏3_琵琶]", + "1920": "[崩坏3_二胡]", + "1921": "[崩坏3_笛子]", + "1922": "[崩坏3_镲]", + "1923": "[崩坏3_红包]", + "1924": "[崩坏3_谈话]", + "1925": "[崩坏3_无辜]", + "1926": "[崩坏3_星星眼]", + "1927": "[崩坏3_微笑]", + "1928": "[崩坏3_开心]", + "1929": "[崩坏3_幸福]", + "1930": "[崩坏3_吃]", + "1931": "[崩坏3_口水]", + "1932": "[崩坏3_惊]", + "1933": "[崩坏3_哭哭]", + "1934": "[崩坏3_纠结]", + "1935": "[崩坏3_疑问]", + "1936": "[崩坏3_有主意了]", + "1973": "[大理寺日志_登场]", + "1974": "[大理寺日志_发现]", + "1975": "[大理寺日志_黑线]", + "1976": "[大理寺日志_怀疑]", + "1977": "[大理寺日志_惊讶]", + "1978": "[大理寺日志_开心]", + "1979": "[大理寺日志_哭哭]", + "1980": "[大理寺日志_困困]", + "1981": "[大理寺日志_雷到]", + "1982": "[大理寺日志_猫笑]", + "1983": "[大理寺日志_怒火]", + "1984": "[大理寺日志_闪光]", + "1985": "[大理寺日志_无语]", + "1986": "[大理寺日志_音符]", + "1987": "[大理寺日志_眨眼]", + "1988": "[阎小罗_不简单]", + "1989": "[阎小罗_诶嘿]", + "1990": "[阎小罗_大招]", + "1991": "[阎小罗_尴尬]", + "1992": "[阎小罗_脸红]", + "1993": "[阎小罗_怂]", + "1994": "[阎小罗_瑟瑟发抖]", + "1995": "[阎小罗_想peach]", + "1996": "[阎小罗_吓哭]", + "1997": "[阎小罗_小秘密]", + "1999": "[动物园_来了]", + "2000": "[动物园_傲娇]", + "2001": "[动物园_暴躁]", + "2002": "[动物园_害怕]", + "2003": "[动物园_嘲讽]", + "2004": "[动物园_哦哟?]", + "2005": "[动物园_冷漠]", + "2006": "[动物园_要饭]", + "2007": "[动物园_害羞]", + "2008": "[动物园_咦]", + "2011": "[乐正绫_爱上了]", + "2012": "[乐正绫_和善]", + "2013": "[乐正绫_吃土]", + "2014": "[乐正绫_掏耳朵]", + "2015": "[乐正绫_冲冲冲]", + "2016": "[乐正绫_大师球]", + "2017": "[乐正绫_哼]", + "2018": "[乐正绫_黑恶势力]", + "2019": "[乐正绫_我可以]", + "2020": "[乐正绫_三连呢]", + "2023": "[阴阳师缘结神_你高尚]", + "2024": "[阴阳师缘结神_菜哭]", + "2025": "[阴阳师缘结神_姐妹!]", + "2026": "[阴阳师缘结神_我身体好]", + "2027": "[阴阳师缘结神_我磕到了]", + "2028": "[阴阳师缘结神_爱心发射]", + "2029": "[阴阳师缘结神_快去结婚]", + "2030": "[阴阳师缘结神_亲一个]", + "2031": "[阴阳师缘结神_KWSL]", + "2032": "[阴阳师缘结神_缘结神哒]", + "2033": "[阴阳师缘结神_该结婚了]", + "2034": "[阴阳师缘结神_兄弟情?]", + "2035": "[阴阳师缘结神_别骚了]", + "2036": "[阴阳师缘结神_开心]", + "2037": "[阴阳师缘结神_在一起]", + "2038": "[阴阳师缘结神_期待]", + "2039": "[阴阳师缘结神_多喝热水]", + "2040": "[阴阳师缘结神_有粮不]", + "2041": "[阴阳师缘结神_等粮中]", + "2042": "[阴阳师缘结神_求你产粮]", + "2043": "[阴阳师缘结神_我不活啦]", + "2044": "[百妖谱_药不能停]", + "2045": "[百妖谱_干杯]", + "2046": "[百妖谱_快更新]", + "2047": "[百妖谱_没问题]", + "2048": "[百妖谱_秋梨膏]", + "2049": "[百妖谱_我太难了]", + "2050": "[百妖谱_告辞]", + "2051": "[百妖谱_陷入思考]", + "2052": "[百妖谱_凶]", + "2053": "[百妖谱_一针见效]", + "2071": "[公主连结_不错]", + "2054": "[公主连结_鼓气]", + "2055": "[公主连结_害怕]", + "2056": "[公主连结_惊吓]", + "2057": "[公主连结_来啊]", + "2058": "[公主连结_累死了]", + "2059": "[公主连结_冷漠]", + "2060": "[公主连结_厉害了]", + "2061": "[公主连结_你干嘛]", + "2062": "[公主连结_扭扭捏捏]", + "2063": "[公主连结_祈祷]", + "2064": "[公主连结_思索]", + "2065": "[公主连结_投币]", + "2066": "[公主连结_无奈]", + "2067": "[公主连结_邪恶]", + "2068": "[公主连结_阴谋]", + "2069": "[公主连结_怎么这样]", + "2070": "[公主连结_emmm]", + "2072": "[黄绿合战_带劲]", + "2073": "[黄绿合战_死亡歌姬]", + "2074": "[黄绿合战_哈哈哈]", + "2075": "[黄绿合战_打你哦]", + "2076": "[黄绿合战_真香]", + "2077": "[黄绿合战_蓝蓝路]", + "2078": "[黄绿合战_吹爆]", + "2079": "[黄绿合战_愤怒]", + "2080": "[黄绿合战_威胁]", + "2081": "[黄绿合战_金坷垃]", + "2086": "[泠鸢yousa_awsl]", + "2087": "[泠鸢yousa_打call]", + "2088": "[泠鸢yousa_沉默]", + "2089": "[泠鸢yousa_大哭]", + "2090": "[泠鸢yousa_干杯]", + "2091": "[泠鸢yousa_好人卡]", + "2092": "[泠鸢yousa_加大力度]", + "2093": "[泠鸢yousa_滑稽]", + "2094": "[泠鸢yousa_请吃桃]", + "2095": "[泠鸢yousa_惊讶]", + "2096": "[泠鸢yousa_生气]", + "2097": "[泠鸢yousa_贴贴]", + "2098": "[泠鸢yousa_头晕]", + "2099": "[泠鸢yousa_问号]", + "2100": "[泠鸢yousa_真棒]", + "2101": "[墨清弦2nd_抱抱]", + "2102": "[墨清弦2nd_点赞]", + "2103": "[墨清弦2nd_加载中]", + "2104": "[墨清弦2nd_惊讶]", + "2105": "[墨清弦2nd_撒花]", + "2106": "[墨清弦2nd_生气]", + "2107": "[墨清弦2nd_无语]", + "2108": "[墨清弦2nd_吸猫]", + "2109": "[墨清弦2nd_小心心]", + "2110": "[墨清弦2nd_蹲墙角]", + "2111": "[双生视界_星星眼]", + "2112": "[双生视界_二胡爱豆]", + "2113": "[双生视界_惊讶]", + "2114": "[双生视界_问号]", + "2115": "[双生视界_为友谊干杯]", + "2116": "[双生视界_无所谓]", + "2117": "[双生视界_傲娇脸]", + "2118": "[双生视界_叹气]", + "2119": "[双生视界_暗中观察]", + "2120": "[双生视界_推眼镜]", + "2121": "[双生视界_祈祷]", + "2122": "[双生视界_加油]", + "2123": "[双生视界_摸头]", + "2124": "[双生视界_比心]", + "2127": "[碧蓝航线2020_在干嘛]", + "2128": "[碧蓝航线2020_肚子饿]", + "2129": "[碧蓝航线2020_心动]", + "2130": "[碧蓝航线_hentai]", + "2131": "[碧蓝航线2020_?!]", + "2132": "[碧蓝航线2020_睡着了]", + "2133": "[碧蓝航线2020_对不起]", + "2134": "[碧蓝航线2020_加油]", + "2135": "[碧蓝航线2020_委屈]", + "2136": "[碧蓝航线2020_晕]", + "2137": "[明日方舟_喷喷]", + "2138": "[明日方舟_溜了]", + "2139": "[明日方舟_嘿嘿]", + "2140": "[明日方舟_怎么回事]", + "2141": "[明日方舟_吃瓜]", + "2142": "[明日方舟_不可以哦]", + "2143": "[明日方舟_喧嚣的风]", + "2144": "[明日方舟_哭了]", + "2145": "[明日方舟_不好意思]", + "2146": "[明日方舟_一下就好]", + "2148": "[萌妻食神_awsl]", + "2149": "[萌妻食神_喝奶茶]", + "2150": "[萌妻食神_加鸡腿]", + "2151": "[萌妻食神_撒娇]", + "2152": "[萌妻食神_秀]", + "2153": "[萌妻食神_危]", + "2154": "[萌妻食神_中毒]", + "2155": "[萌妻食神_主角buff]", + "2156": "[萌妻食神_馋]", + "2157": "[萌妻食神_饱了]", + "2158": "[百鬼幼儿园_加油]", + "2159": "[百鬼幼儿园_惊讶]", + "2160": "[百鬼幼儿园_尴尬]", + "2161": "[百鬼幼儿园_噗嗤]", + "2162": "[百鬼幼儿园_气抖冷]", + "2163": "[百鬼幼儿园_点赞]", + "2164": "[百鬼幼儿园_小问号]", + "2165": "[百鬼幼儿园_晕了]", + "2166": "[百鬼幼儿园_哭哭]", + "2167": "[百鬼幼儿园_生气]", + "2169": "[BW2020_欢迎]", + "2170": "[BW2020_请]", + "2171": "[BW2020_你币没了]", + "2172": "[BW2020_你号没了]", + "2173": "[BW2020_棒棒哦]", + "2174": "[BW2020_我全都要]", + "2175": "[BW2020_贫穷]", + "2176": "[BW2020_期待]", + "2177": "[BW2020_我太难了]", + "2178": "[BW2020_震惊]", + "2202": "[洛天依_goodjob]", + "2203": "[洛天依_啊这]", + "2204": "[洛天依_爱你]", + "2205": "[洛天依_嘲笑]", + "2206": "[洛天依_吃瓜]", + "2207": "[洛天依_害羞]", + "2208": "[洛天依_捏捏]", + "2209": "[洛天依_恰柠檬]", + "2210": "[洛天依_耶]", + "2211": "[洛天依_震惊]", + "2213": "[言和7th_不好意思]", + "2214": "[言和7th_乖巧]", + "2215": "[言和7th_来晚了]", + "2216": "[言和7th_静静看你]", + "2217": "[言和7th_一键三连]", + "2218": "[言和7th_捶你胸口]", + "2219": "[言和7th_期待]", + "2220": "[言和7th_小fafa]", + "2221": "[言和7th_不行]", + "2222": "[言和7th_梦里都有]", + "2223": "[BML云live_打call]", + "2224": "[BML云live_22唱歌]", + "2225": "[BML云live_33唱歌]", + "2226": "[BML云live_治疗]", + "2227": "[BML云live_awsl]", + "2228": "[BML云live_点赞]", + "2229": "[BML云live_黑化22]", + "2230": "[BML云live_黑化33]", + "2231": "[BML云live_上buff]", + "2232": "[BML云live_想桃子]", + "2233": "[天宝伏妖录_不满]", + "2234": "[天宝伏妖录_得意]", + "2235": "[天宝伏妖录_害羞]", + "2236": "[天宝伏妖录_噗]", + "2237": "[天宝伏妖录_思考]", + "2238": "[天宝伏妖录_嘿嘿]", + "2239": "[天宝伏妖录_困了]", + "2240": "[天宝伏妖录_比心心]", + "2241": "[天宝伏妖录_吓哭]", + "2242": "[天宝伏妖录_疑惑]", + "2243": "[中国绊爱_吃惊]", + "2244": "[中国绊爱_大叫]", + "2245": "[中国绊爱_点赞]", + "2246": "[中国绊爱_吃饺子]", + "2247": "[中国绊爱_贴贴]", + "2248": "[中国绊爱_无语]", + "2249": "[中国绊爱_笑哭]", + "2250": "[中国绊爱_星星眼]", + "2251": "[中国绊爱_一手好牌]", + "2252": "[中国绊爱_疑惑]", + "2253": "[雾山五行_6到无语]", + "2254": "[雾山五行_冲鸭]", + "2255": "[雾山五行_滑稽]", + "2256": "[雾山五行_3连]", + "2257": "[雾山五行_我可以]", + "2258": "[雾山五行_emmm]", + "2259": "[雾山五行_爷关更]", + "2260": "[雾山五行_真香]", + "2261": "[雾山五行_震惊]", + "2262": "[雾山五行_抓狂]", + "2263": "[hanser_冲鸭]", + "2264": "[hanser_问号]", + "2265": "[hanser_打咩]", + "2266": "[hanser_暗中观察]", + "2267": "[hanser_痛哭]", + "2268": "[hanser_小天使]", + "2269": "[hanser_嚯牛奶]", + "2270": "[hanser_流啤]", + "2271": "[hanser_难以置信]", + "2272": "[hanser_贴贴]", + "2273": "[hanser_okk]", + "2274": "[hanser_好听]", + "2275": "[hanser_晚安]", + "2276": "[hanser_peach]", + "2277": "[hanser_头毛]", + "2278": "[hanser_awsl]", + "2279": "[hanser_滑稽]", + "2280": "[hanser_毛怪惊吓]", + "2281": "[hanser_毛怪哭哭]", + "2282": "[hanser_怂呐]", + "2283": "[hanser_嘤嘤嘤]", + "2284": "[hanser_早]", + "2285": "[hanser_篮球]", + "2286": "[最强蜗牛西能_Hi]", + "2287": "[最强蜗牛西能_呆萌]", + "2288": "[最强蜗牛西能_加油]", + "2289": "[最强蜗牛西能_哼]", + "2290": "[最强蜗牛西能_OK]", + "2291": "[最强蜗牛西能_么么]", + "2292": "[最强蜗牛西能_比心]", + "2293": "[最强蜗牛西能_蟹蟹]", + "2294": "[最强蜗牛西能_哭了]", + "2295": "[最强蜗牛西能_开心]", + "2296": "[最强蜗牛西能_哈哈]", + "2297": "[最强蜗牛西能_喝饮料]", + "2298": "[最强蜗牛西能_哇]", + "2299": "[最强蜗牛西能_勾引]", + "2300": "[最强蜗牛西能_沧桑]", + "2301": "[最强蜗牛西能_嗯嗯]", + "2305": "[凹凸世界_你看看你]", + "2306": "[凹凸世界_hi]", + "2307": "[凹凸世界_无语]", + "2308": "[凹凸世界_思考]", + "2309": "[凹凸世界_好奇]", + "2310": "[凹凸世界_你再见]", + "2311": "[凹凸世界_毁约]", + "2312": "[凹凸世界_约架]", + "2313": "[凹凸世界_拒绝]", + "2314": "[凹凸世界_渣渣]", + "2315": "[凹凸世界_心动]", + "2316": "[凹凸世界_决心]", + "2317": "[凹凸世界_耍帅]", + "2318": "[凹凸世界_算了算了]", + "2319": "[凹凸世界_告辞]", + "2320": "[凹凸世界_强忍怒气]", + "2322": "[Cat_escort]", + "2323": "[Cat_coffeebath]", + "2324": "[Cat_stealth]", + "2325": "[Cat_hightouch]", + "2326": "[Cat_lonely]", + "2327": "[Cat_heart]", + "2328": "[Cat_slip]", + "2329": "[Cat_chips]", + "2330": "[Cat_study]", + "2331": "[Cat_dash]", + "2332": "[Cat_pressure]", + "2333": "[Cat_hibye]", + "2334": "[Cat_confuse]", + "2335": "[Cat_ignore]", + "2336": "[Cat_agree]", + "2337": "[Cat_fight]", + "2338": "[Cat_splash]", + "2339": "[Cat_calledme]", + "2340": "[Cat_sleep]", + "2341": "[Cat_congrats]", + "2342": "[约战_咕咕咕]", + "2343": "[约战_抱抱]", + "2344": "[约战_优秀]", + "2345": "[约战_哈哈哈]", + "2346": "[约战_哼]", + "2347": "[约战_看什么]", + "2348": "[约战_嘲讽]", + "2349": "[约战_爱你]", + "2350": "[约战_OTL]", + "2351": "[约战_谢谢]", + "2352": "[约战_暗中观察]", + "2353": "[约战_怪我咯]", + "2354": "[约战_嗯嗯]", + "2355": "[约战_盯]", + "2356": "[约战_没关系]", + "2357": "[约战_晚安]", + "2358": "[阿库娅_才女才女]", + "2359": "[阿库娅_害羞]", + "2360": "[阿库娅_不关我事]", + "2361": "[阿库娅_惊讶]", + "2362": "[阿库娅_余裕余裕]", + "2363": "[阿库娅_哭了]", + "2364": "[阿库娅_理解理解]", + "2365": "[阿库娅_生气]", + "2366": "[阿库娅_无语]", + "2367": "[阿库娅_Rua!]", + "2368": "[阿库娅_nice]", + "2369": "[阿库娅_喜欢]", + "2370": "[阿库娅_对不起]", + "2371": "[阿库娅_嫌弃]", + "2372": "[阿库娅_洋葱问号]", + "2375": "[神楽Mea_拜托拜托]", + "2376": "[神楽Mea_不可以]", + "2377": "[神楽Mea_给我钱]", + "2378": "[神楽Mea_哈哈哈]", + "2379": "[神楽Mea_哭给你看]", + "2380": "[神楽Mea_理解一下]", + "2381": "[神楽Mea_略略略]", + "2382": "[神楽Mea_啾咪]", + "2383": "[神楽Mea_咩]", + "2384": "[神楽Mea_你很可爱]", + "2385": "[神楽Mea_不满么]", + "2386": "[神楽Mea_我错了]", + "2387": "[神楽Mea_嘲讽]", + "2388": "[神楽Mea_kimo]", + "2389": "[神楽Mea_戳便便]", + "2391": "[新科娘_点赞]", + "2392": "[新科娘_得意]", + "2393": "[新科娘_花手]", + "2394": "[新科娘_懵逼]", + "2395": "[新科娘_妙啊]", + "2396": "[新科娘_过分了]", + "2397": "[新科娘_我装的]", + "2398": "[新科娘_无话可说]", + "2399": "[新科娘_这题不会]", + "2400": "[新科娘_我哭了]", + "2402": "[初音未来_抱抱]", + "2403": "[初音未来_nice]", + "2404": "[初音未来_GKD]", + "2405": "[初音未来_问号]", + "2406": "[初音未来_吃惊]", + "2407": "[初音未来_干杯]", + "2408": "[初音未来_端碗]", + "2409": "[初音未来_害怕]", + "2410": "[初音未来_加油]", + "2411": "[初音未来_哭了]", + "2412": "[初音未来_困了]", + "2413": "[初音未来_鬼脸]", + "2414": "[初音未来_期待]", + "2415": "[初音未来_生气]", + "2416": "[初音未来_睡了]", + "2417": "[初音未来_委屈]", + "2418": "[初音未来_无语]", + "2419": "[初音未来_喜欢]", + "2420": "[初音未来_嫌弃]", + "2421": "[初音未来_再见]", + "2422": "[七海_比心]", + "2423": "[七海_尴尬]", + "2424": "[七海_害怕]", + "2425": "[七海_惊讶]", + "2426": "[七海_哭哭]", + "2427": "[七海_柠檬]", + "2428": "[七海_捏捏]", + "2429": "[七海_生闷气]", + "2430": "[七海_贴贴]", + "2431": "[七海_沉了]", + "2432": "[元龙_嘲讽]", + "2433": "[元龙_偷笑]", + "2434": "[元龙_生气]", + "2435": "[元龙_哭哭]", + "2436": "[元龙_嫌弃]", + "2437": "[元龙_时代变了]", + "2438": "[元龙_好汉饶命]", + "2439": "[元龙_捂脸]", + "2440": "[元龙_我错了]", + "2441": "[元龙_我投降]", + "2442": "[琉绮Ruki_???]", + "2443": "[琉绮Ruki_不是吧]", + "2444": "[琉绮Ruki_吃瓜]", + "2445": "[琉绮Ruki_刺激]", + "2446": "[琉绮Ruki_害羞]", + "2447": "[琉绮Ruki_惊呆惹]", + "2448": "[琉绮Ruki_哭哭]", + "2449": "[琉绮Ruki_气鼓鼓]", + "2450": "[琉绮Ruki_身体被掏空]", + "2451": "[琉绮Ruki_思考中]", + "2452": "[琉绮Ruki_哇哦]", + "2453": "[三周年_问号]", + "2454": "[三周年_诚信互点]", + "2455": "[三周年_这次一定]", + "2456": "[三周年_我全都要]", + "2457": "[三周年_谢谢老板]", + "2458": "[三周年_预定一时爽]", + "2459": "[海国之境_爱你哦]", + "2460": "[海国之境_暗中观察]", + "2461": "[海国之境_宝藏男孩]", + "2462": "[海国之境_补防晒]", + "2463": "[海国之境_不过如此]", + "2464": "[海国之境_不会吧]", + "2465": "[海国之境_心碎等待]", + "2466": "[海国之境_来份鱼丸]", + "2467": "[海国之境_你有吗]", + "2468": "[海国之境_我不听]", + "2469": "[天秤座01_完美]", + "2470": "[天秤座01_奥利给]", + "2471": "[天秤座01_打你]", + "2472": "[天秤座01_还没复习]", + "2473": "[天秤座01_纠结]", + "2474": "[天秤座01_失落]", + "2475": "[天秤座01_叹气]", + "2476": "[天秤座01_贴贴]", + "2477": "[天秤座01_我都可以]", + "2478": "[天秤座01_心动]", + "2479": "[七濑胡桃_吧唧吧唧]", + "2480": "[七濑胡桃_熬夜]", + "2481": "[七濑胡桃_泪目]", + "2482": "[七濑胡桃_乖巧]", + "2483": "[七濑胡桃_好饿]", + "2484": "[七濑胡桃_紧张]", + "2485": "[七濑胡桃_噗]", + "2486": "[七濑胡桃_生气]", + "2487": "[七濑胡桃_委屈]", + "2488": "[七濑胡桃_我超可爱]", + "2489": "[七濑胡桃_我来了!]", + "2490": "[七濑胡桃_有意思]", + "2491": "[七濑胡桃_元气满满]", + "2492": "[七濑胡桃_宅在家]", + "2493": "[七濑胡桃_缜密分析]", + "2495": "[狂三_哒哒哒]", + "2496": "[狂三_气抖冷]", + "2497": "[狂三_害羞]", + "2498": "[狂三_生气了]", + "2499": "[狂三_点赞]", + "2500": "[狂三_求抱抱]", + "2501": "[狂三_委屈]", + "2502": "[狂三_星星眼]", + "2503": "[狂三_疑问]", + "2504": "[狂三_自鲨]", + "2505": "[全职高手_点赞]", + "2506": "[全职高手_大佬]", + "2507": "[全职高手_无语]", + "2508": "[全职高手_怀疑]", + "2509": "[全职高手_痛哭]", + "2510": "[全职高手_快乐]", + "2511": "[全职高手_疯狂输出]", + "2512": "[全职高手_看戏]", + "2513": "[全职高手_摊手]", + "2514": "[全职高手_偷看]", + "2515": "[全职高手_投币]", + "2516": "[全职高手_危险凝视]", + "2517": "[全职高手_盯]", + "2518": "[全职高手_笑咪咪]", + "2519": "[全职高手_自信]", + "2520": "[一起看电竞_别浪]", + "2521": "[一起看电竞_冲冲冲]", + "2522": "[一起看电竞_别奶了]", + "2523": "[一起看电竞_救心丸]", + "2556": "[一起看电竞_起飞]", + "2525": "[一起看电竞_翻盘了]", + "2527": "[一起看电竞_这合理么]", + "2524": "[一起看电竞_工具人]", + "2529": "[一起看电竞_永远的神]", + "2528": "[一起看电竞_MVP]", + "2526": "[一起看电竞_上流]", + "2557": "[一起看电竞_别墅靠海]", + "2558": "[一起看电竞_摇骰子]", + "2559": "[一起看电竞_彳亍]", + "2560": "[一起看电竞_稳住]", + "2546": "[乐正龙牙3rd_打鼓]", + "2547": "[乐正龙牙3rd_呆住]", + "2548": "[乐正龙牙3rd_恶魔]", + "2549": "[乐正龙牙3rd_天使]", + "2550": "[乐正龙牙3rd_记仇]", + "2551": "[乐正龙牙3rd_没问题]", + "2552": "[乐正龙牙3rd_送花]", + "2553": "[乐正龙牙3rd_修仙]", + "2554": "[乐正龙牙3rd_已阅]", + "2555": "[乐正龙牙3rd_将军]", + "2561": "[宇宙机器人_萌]", + "2562": "[宇宙机器人_AWSL]", + "2563": "[宇宙机器人_佛]", + "2564": "[宇宙机器人_衰]", + "2565": "[宇宙机器人_我可以]", + "2566": "[宇宙机器人_凝视]", + "2567": "[宇宙机器人_炸裂]", + "2568": "[宇宙机器人_起飞]", + "2569": "[宇宙机器人_前方高能]", + "2570": "[宇宙机器人_真香]", + "2571": "[宇宙机器人_奥利给]", + "2572": "[宇宙机器人_摸鱼]", + "2573": "[宇宙机器人_我好了]", + "2574": "[宇宙机器人_好会啊]", + "2575": "[宇宙机器人_嘴臭]", + "2576": "[宇宙机器人_气死偶咧]", + "2577": "[宇宙机器人_Hmmm]", + "2578": "[宇宙机器人_爱了爱了]", + "2579": "[宇宙机器人_吹爆]", + "2580": "[宇宙机器人_快跑]", + "2587": "[登乐V计划_心动]", + "2586": "[登乐V计划_贴贴]", + "2585": "[登乐V计划_期待]", + "2584": "[登乐V计划_老箱推了]", + "2583": "[登乐V计划_快上车]", + "2582": "[登乐V计划_工具人]", + "2581": "[登乐V计划_干杯]", + "2588": "[登乐V计划_skr]", + "2589": "[登乐V计划_33唱歌]", + "2590": "[登乐V计划_22唱歌]", + "2591": "[芯觉-暗中观察]", + "2592": "[芯觉-不!]", + "2593": "[芯觉-发呆]", + "2594": "[芯觉-发射]", + "2595": "[芯觉-冷]", + "2596": "[芯觉-你不对劲]", + "2597": "[芯觉-委屈]", + "2598": "[芯觉-无语]", + "2599": "[芯觉-嫌弃]", + "2600": "[芯觉-中枪]", + "2601": "[鹿乃_???]", + "2602": "[鹿乃_awsl]", + "2603": "[鹿乃_DD斩]", + "2604": "[鹿乃_OK]", + "2605": "[鹿乃_比心]", + "2606": "[鹿乃_别在意]", + "2607": "[鹿乃_吃瓜]", + "2608": "[鹿乃_吃桃]", + "2609": "[鹿乃_打call]", + "2610": "[鹿乃_打扰了]", + "2611": "[鹿乃_点赞]", + "2612": "[鹿乃_对不起]", + "2613": "[鹿乃_饿了]", + "2614": "[鹿乃_哈哈]", + "2615": "[鹿乃_好吃]", + "2616": "[鹿乃_哼歌]", + "2617": "[鹿乃_欢迎回来]", + "2618": "[鹿乃_加油]", + "2619": "[鹿乃_惊]", + "2620": "[鹿乃_买买买]", + "2621": "[鹿乃_奇怪的知识]", + "2622": "[鹿乃_生气]", + "2623": "[鹿乃_晚安]", + "2624": "[鹿乃_我回来了]", + "2625": "[鹿乃_喜欢]", + "2626": "[鹿乃_邪魅一笑]", + "2627": "[鹿乃_谢谢]", + "2628": "[鹿乃_再见]", + "2629": "[鹿乃_早安]", + "2630": "[鹿乃_真的]", + "2631": "[天蝎座_爱你]", + "2632": "[天蝎座_暗中观察]", + "2633": "[天蝎座_吃醋]", + "2634": "[天蝎座_画圈圈]", + "2635": "[天蝎座_机智]", + "2636": "[天蝎座_记仇]", + "2637": "[天蝎座_就这?]", + "2638": "[天蝎座_掐你]", + "2639": "[天蝎座_贴贴]", + "2640": "[天蝎座_邪魅一笑]", + "2641": "[阿萨Aza_???]", + "2642": "[阿萨Aza_拜托]", + "2643": "[阿萨Aza_棒]", + "2644": "[阿萨Aza_喝牛奶]", + "2645": "[阿萨Aza_警觉]", + "2646": "[阿萨Aza_哭哭]", + "2647": "[阿萨Aza_生气]", + "2648": "[阿萨Aza_危]", + "2649": "[阿萨Aza_嘻嘻]", + "2650": "[阿萨Aza_咋了咋了]", + "2655": "[天官赐福_害羞]", + "2653": "[天官赐福_彩虹屁]", + "2654": "[天官赐福_钞能力]", + "2659": "[天官赐福_贫穷]", + "2656": "[天官赐福_哭]", + "2657": "[天官赐福_溜了]", + "2652": "[天官赐福_???]", + "2658": "[天官赐福_满脸高兴]", + "2660": "[天官赐福_请慢用]", + "2662": "[天官赐福_喜欢]", + "2661": "[天官赐福_突然出现]", + "2786": "[天官赐福_点赞]", + "2787": "[天官赐福_尴尬]", + "2903": "[天官赐福_机智如我]", + "2904": "[天官赐福_静静看着你]", + "2965": "[天官赐福_看星星]", + "2966": "[天官赐福_我裂开了]", + "2977": "[天官赐福_可怜兮兮]", + "2978": "[天官赐福_起床啦]", + "3095": "[天官赐福_公主抱]", + "2663": "[说唱新世代_蹭吃蹭喝]", + "2664": "[说唱新世代_变身]", + "2665": "[说唱新世代_加油]", + "2666": "[说唱新世代_讲文明]", + "2671": "[说唱新世代_痛苦面具]", + "2667": "[说唱新世代_脚艺人]", + "2668": "[说唱新世代_开门]", + "2676": "[说唱新世代_真漂亮]", + "2669": "[说唱新世代_买买买]", + "2675": "[说唱新世代_贞不戳]", + "2670": "[说唱新世代_算一下]", + "2672": "[说唱新世代_我想画]", + "2673": "[说唱新世代_无语]", + "2674": "[说唱新世代_小太阳]", + "2677": "[说唱新世代_主见]", + "2698": "[说唱新世代_害羞]", + "2699": "[说唱新世代_夸张]", + "2700": "[说唱新世代_明天吧]", + "2701": "[说唱新世代_说唱叫父]", + "2702": "[说唱新世代_舞王]", + "2678": "[女仆醒_摸了]", + "2679": "[女仆醒_吃瓜]", + "2680": "[女仆醒_咕了]", + "2681": "[女仆醒_喝茶]", + "2682": "[女仆醒_假发]", + "2683": "[女仆醒_惊讶]", + "2684": "[女仆醒_彩虹]", + "2685": "[女仆醒_威胁]", + "2686": "[女仆醒_醒醒]", + "2687": "[女仆_流鼻血]", + "2688": "[女仆醒_kimo]", + "2689": "[女仆醒_哎嘿嘿]", + "2690": "[女仆醒_暗中观察]", + "2691": "[女仆醒_吃柠檬]", + "2692": "[女仆醒_画稿人]", + "2693": "[女仆醒_开心]", + "2694": "[女仆醒_贴贴]", + "2695": "[女仆醒_想桃吃]", + "2696": "[女仆醒_心痛]", + "2697": "[女仆醒_???]", + "2703": "[花园Serena_???]", + "2704": "[花园Serena_安详去世]", + "2705": "[花园Serena_mua]", + "2706": "[花园Serena_啊!]", + "2707": "[花园Serena_唉]", + "2708": "[花园Serena_到点了]", + "2709": "[花园Serena_恭喜]", + "2710": "[花园Serena_画好了]", + "2711": "[花园Serena_哭哭]", + "2712": "[花园Serena_猫菜瘾大]", + "2713": "[花园Serena_猫猫祟祟]", + "2714": "[花园Serena_喵]", + "2715": "[花园Serena_神奇按钮]", + "2716": "[花园Serena_睡]", + "2717": "[花园Serena_头脑发热]", + "2718": "[花鸟卷_别拦我]", + "2719": "[花鸟卷_冲]", + "2720": "[花鸟卷_记仇]", + "2721": "[花鸟卷_夸我]", + "2722": "[花鸟卷_美眼看]", + "2723": "[花鸟卷_你行你画]", + "2724": "[花鸟卷_晚安]", + "2725": "[花鸟卷_我保护你]", + "2726": "[花鸟卷_我在]", + "2727": "[花鸟卷_要吗]", + "2728": "[原神_小事一桩]", + "2729": "[原神_唔]", + "2730": "[原神_躺平]", + "2731": "[原神_哦]", + "2732": "[原神_哭哭]", + "2733": "[原神_救救我]", + "2734": "[原神_警觉]", + "2735": "[原神_交给我吧]", + "2736": "[原神_哼哼]", + "2737": "[原神_给我走开]", + "2738": "[原神_鸽子]", + "2739": "[原神_干杯]", + "2740": "[原神_该吃饭了]", + "2741": "[原神_出货吧]", + "2742": "[原神_吃惊]", + "2743": "[原神_不要啊]", + "2744": "[原神_拜托]", + "2745": "[原神_zzZZ]", + "2746": "[原神_good job]", + "2747": "[原神_1摩拉]", + "2760": "[原神_OK]", + "2761": "[原神_暗中观察]", + "2762": "[原神_比心]", + "2763": "[原神_震撼]", + "2764": "[原神_倒]", + "2765": "[原神_点赞]", + "2766": "[原神_鼓掌]", + "2767": "[原神_乖巧]", + "2768": "[原神_得意]", + "2769": "[原神_冷漠]", + "2770": "[原神_期待]", + "2771": "[原神_撒花]", + "2772": "[原神_晚安]", + "2773": "[原神_委屈]", + "2774": "[原神_疑问]", + "2775": "[原神_有主意了]", + "2934": "[Kizuna AI _比心]", + "2935": "[Kizuna AI_哭哭]", + "2937": "[Kizuna AI_嘤嘤嘤]", + "2936": "[Kizuna AI_无语]", + "2938": "[Kizuna AI_震惊]", + "2751": "[Kizuna AI _得意脸]", + "2750": "[Kizuna AI _OK]", + "2752": "[Kizuna AI _嗨多摩]", + "2753": "[Kizuna AI _花q]", + "2754": "[Kizuna AI _怀疑]", + "2755": "[Kizuna AI _惊讶]", + "2756": "[Kizuna AI _拿第一]", + "2757": "[KizunaAI _天才天才]", + "2758": "[KizunaAI _摇晃悠悠]", + "2759": "[KizunaAI _原来如此]", + "2776": "[射手座_好家伙]", + "2777": "[射手座_浪起来!]", + "2778": "[射手座_乐观]", + "2779": "[射手座_撩你]", + "2780": "[射手座_认识一下]", + "2781": "[射手座_随便]", + "2782": "[射手座_贴贴]", + "2783": "[射手座_突然消失]", + "2784": "[射手座_真麻烦]", + "2785": "[射手座_自由万岁!]", + "2788": "[沙茶兔_拜拜]", + "2789": "[沙茶兔_吃瓜]", + "2790": "[沙茶兔_得意]", + "2791": "[沙茶兔_哭了]", + "2792": "[沙茶兔_冷漠]", + "2793": "[沙茶兔_清醒点]", + "2794": "[沙茶兔_请说]", + "2795": "[沙茶兔_问号]", + "2796": "[沙茶兔_嫌弃]", + "2797": "[沙茶兔_在吗]", + "2798": "[奥利猫_6]", + "2799": "[奥利猫_打招呼]", + "2800": "[奥利猫_可爱]", + "2801": "[奥利猫_哭哭]", + "2802": "[奥利猫_灭火右]", + "2803": "[奥利猫_伸手]", + "2804": "[奥利猫_伸爪子]", + "2805": "[奥利猫_生气]", + "2806": "[奥利猫_享受]", + "2807": "[奥利猫_疑问]", + "2808": "[抹茶狗_汗颜]", + "2809": "[抹茶狗_臭美]", + "2810": "[抹茶狗_大笑]", + "2811": "[抹茶狗_愤怒]", + "2812": "[抹茶狗_生病]", + "2813": "[抹茶狗_想哭]", + "2814": "[抹茶狗_有了]", + "2815": "[抹茶狗_棒棒]", + "2816": "[抹茶狗_吃惊]", + "2817": "[抹茶狗_无语]", + "2818": "[小红眼_生气]", + "2819": "[小红眼_鼻血]", + "2820": "[小红眼_嘲笑]", + "2821": "[小红眼_吃瓜]", + "2822": "[小红眼_吃惊]", + "2823": "[小红眼_大哭]", + "2824": "[小红眼_愤怒]", + "2825": "[小红眼_嫌弃]", + "2826": "[小红眼_享受]", + "2827": "[小红眼_点赞]", + "2828": "[欧皇_666]", + "2829": "[欧皇_lucky]", + "2830": "[欧皇_不愧是我]", + "2831": "[欧皇_大佬]", + "2832": "[欧皇_好家伙]", + "2833": "[欧皇_欧泡时间]", + "2834": "[欧皇_欧气喷雾]", + "2835": "[欧皇_脱非入欧]", + "2836": "[欧皇_我可以]", + "2837": "[欧皇_嚣张]", + "2838": "[黑潮之上_心心]", + "2839": "[黑潮之上_不过如此]", + "2840": "[黑潮之上_对不起]", + "2841": "[黑潮之上_晚安]", + "2842": "[黑潮之上_OK]", + "2843": "[黑潮之上_哈哈哈]", + "2844": "[黑潮之上_摸摸头]", + "2845": "[黑潮之上_惊讶]", + "2846": "[黑潮之上_暗中观察]", + "2847": "[黑潮之上_突然出现]", + "2848": "[黑潮之上_精神小火]", + "2849": "[黑潮之上_累了]", + "2850": "[黑潮之上_记小本本]", + "2851": "[黑潮之上_乖巧]", + "2852": "[黑潮之上_无语]", + "2853": "[黑潮之上_什么]", + "2854": "[最强蜗牛_闭嘴]", + "2855": "[最强蜗牛_吃]", + "2856": "[最强蜗牛_喝饮料]", + "2857": "[最强蜗牛_奸笑]", + "2858": "[最强蜗牛_哭笑]", + "2859": "[最强蜗牛_怒骂]", + "2860": "[最强蜗牛_丧]", + "2861": "[最强蜗牛_睡]", + "2862": "[最强蜗牛_调皮]", + "2863": "[最强蜗牛_笑]", + "2864": "[小沪蝶_比心]", + "2865": "[小沪蝶_点赞]", + "2866": "[小沪蝶_好棒]", + "2867": "[小沪蝶_好的]", + "2868": "[小沪蝶_加油]", + "2869": "[小沪蝶_厉害了]", + "2870": "[小沪蝶_嗯嗯]", + "2871": "[小沪蝶_你好]", + "2872": "[小沪蝶_收到]", + "2873": "[小沪蝶_挑战成功]", + "2874": "[小沪蝶_谢谢]", + "2875": "[小沪蝶_辛苦啦]", + "2876": "[小沪蝶_奋斗]", + "2877": "[小沪蝶_学霸]", + "2878": "[小沪蝶_学在上海]", + "2879": "[小沪蝶_一起学习呀]", + "2897": "[偶像梦幻祭2_阿妹胫骨]", + "2896": "[偶像梦幻祭2_冲鸭]", + "2895": "[偶像梦幻祭2_打call]", + "2894": "[偶像梦幻祭2_点点点]", + "2893": "[偶像梦幻祭2_点头]", + "2892": "[偶像梦幻祭2_盯]", + "2891": "[偶像梦幻祭2_躲墙角]", + "2890": "[偶像梦幻祭2_躲闪]", + "2889": "[偶像梦幻祭2_鼓气]", + "2888": "[偶像梦幻祭2_神父之光]", + "2887": "[偶像梦幻祭2_叹气]", + "2886": "[偶像梦幻祭2_危]", + "2885": "[偶像梦幻祭2_问号]", + "2884": "[偶像梦幻祭2_想鱼]", + "2883": "[偶像梦幻祭2_兴奋]", + "2882": "[偶像梦幻祭2_幸福]", + "2881": "[偶像梦幻祭2_震惊]", + "2905": "[新月冰冰_awsl]", + "2906": "[新月冰冰_呆suki]", + "2907": "[新月冰冰_干杯]", + "2908": "[新月冰冰_喝茶]", + "2909": "[新月冰冰_嚯奶茶]", + "2910": "[新月冰冰_忙忙忙]", + "2911": "[新月冰冰_冒冷汗]", + "2912": "[新月冰冰_生气]", + "2913": "[新月冰冰_送牛肉酱]", + "2914": "[新月冰冰_叹气]", + "2915": "[新月冰冰_探头]", + "2916": "[新月冰冰_汪汪碎冰冰]", + "2917": "[新月冰冰_我吃柠檬]", + "2918": "[新月冰冰_疑惑]", + "2919": "[新月冰冰_醉了]", + "2920": "[暗影国度_成功入水]", + "2921": "[暗影国度_小问号]", + "2922": "[暗影国度_冲锋]", + "2923": "[暗影国度_毫无意义]", + "2924": "[暗影国度_黑手远离]", + "2925": "[暗影国度_five]", + "2926": "[暗影国度_lzxx]", + "2927": "[暗影国度_火车王]", + "2928": "[暗影国度_就打德]", + "2929": "[暗影国度_天呐]", + "2930": "[暗影国度_无所畏惧]", + "2931": "[暗影国度_下次我请]", + "2932": "[暗影国度_为了部落]", + "2933": "[暗影国度_为了联盟]", + "2939": "[暗影国度_自寻死路]", + "2940": "[偶像梦幻祭2_lo~ve!]", + "2941": "[偶像梦幻祭2_暗中观察]", + "2942": "[偶像梦幻祭2_盯——]", + "2943": "[偶像梦幻祭2_加油]", + "2944": "[偶像梦幻祭2_开心]", + "2945": "[偶像梦幻祭2_困扰]", + "2946": "[偶像梦幻祭2_泪目]", + "2947": "[偶像梦幻祭2_三连]", + "2948": "[偶像梦幻祭2_投币]", + "2949": "[偶像梦幻祭2_疑惑]", + "2950": "[总之就是非常可爱_拜托你啦]", + "2951": "[总之就是非常可爱_不过如此]", + "2952": "[总之就是非常可爱_不是吧]", + "2953": "[总之就是非常可爱_不听不听]", + "2954": "[总之就是非常可爱_好厉害]", + "2955": "[总之就是非常可爱_嘿嘿]", + "2956": "[总之就是非常可爱_加油]", + "2957": "[总之就是非常可爱_可爱]", + "2958": "[总之就是非常可爱_快截图]", + "2959": "[总之就是非常可爱_流鼻血]", + "2960": "[总之就是非常可爱_生气]", + "2961": "[总之就是非常可爱_哇]", + "2962": "[总之就是非常可爱_谢谢]", + "2963": "[总之就是非常可爱_修正你]", + "2964": "[总之就是非常可爱_震惊]", + "3141": "[总之就是非常可爱_NASA]", + "3142": "[总之就是非常可爱_变身]", + "3143": "[总之就是非常可爱_恶的制裁]", + "3144": "[总之就是非常可爱_我好可怜]", + "3145": "[总之就是非常可爱_心理打击]", + "2967": "[徵羽摩柯_催更]", + "2968": "[徵羽摩柯_灵光一闪]", + "2969": "[徵羽摩柯_乱哭]", + "2970": "[徵羽摩柯_拍照]", + "2971": "[徵羽摩柯_知识增加]", + "2972": "[徵羽摩柯_一点点]", + "2973": "[徵羽摩柯_思考]", + "2974": "[徵羽摩柯_召唤好运]", + "2975": "[徵羽摩柯_中二病]", + "2976": "[徵羽摩柯_装傻]", + "2980": "[拾忆长安_仔细阅读]", + "2981": "[拾忆长安_大哥]", + "2982": "[拾忆长安_大唐颜艺王]", + "2983": "[拾忆长安_绞面]", + "2984": "[拾忆长安_九嫂威武]", + "2985": "[拾忆长安_女单书]", + "2986": "[拾忆长安_三箭齐发]", + "2987": "[拾忆长安_圣人]", + "2988": "[拾忆长安_不要啊]", + "2989": "[拾忆长安_夕阳下奔跑]", + "2990": "[绯赤艾莉欧_prpr]", + "2991": "[绯赤艾莉欧_搬大米]", + "2992": "[绯赤艾莉欧_不要停]", + "2993": "[绯赤艾莉欧_好寂寞]", + "2994": "[绯赤艾莉欧_好耶]", + "2995": "[绯赤艾莉欧_结婚]", + "2996": "[绯赤艾莉欧_困惑]", + "2997": "[绯赤艾莉欧_老婆]", + "2998": "[绯赤艾莉欧_眠眠]", + "2999": "[绯赤艾莉欧_亲亲]", + "3000": "[绯赤艾莉欧_生气]", + "3001": "[绯赤艾莉欧_喜欢]", + "3002": "[绯赤艾莉欧_想中奖]", + "3003": "[绯赤艾莉欧_星星眼]", + "3004": "[绯赤艾莉欧_应援]", + "3008": "[雪未来_太棒了]", + "3009": "[雪未来_摸头]", + "3011": "[雪未来_击掌]", + "3005": "[雪未来_等一等]", + "3018": "[雪未来_忽然出现]", + "3020": "[雪未来_喝奶茶]", + "3010": "[雪未来_冲呀]", + "3021": "[雪未来_下雪了]", + "3022": "[雪未来_堆雪人]", + "3023": "[雪未来_赖床]", + "3007": "[雪未来_忍住不笑]", + "3012": "[雪未来_打气]", + "3013": "[雪未来_叹气]", + "3024": "[雪未来_给你惊喜]", + "3014": "[雪未来_生气]", + "3015": "[雪未来_早安]", + "3006": "[雪未来_挠头]", + "3017": "[雪未来_嘿嘿]", + "3016": "[雪未来_唱歌]", + "3019": "[雪未来_泪目]", + "3025": "[摩羯座_?]", + "3026": "[摩羯座_爱你]", + "3027": "[摩羯座_戳一下]", + "3028": "[摩羯座_没办法]", + "3029": "[摩羯座_生气]", + "3030": "[摩羯座_叹气]", + "3031": "[摩羯座_贴贴]", + "3032": "[摩羯座_我爱工作]", + "3033": "[摩羯座_呜哇]", + "3034": "[摩羯座_无语]", + "3049": "[战双_哭哭]", + "3050": "[战双_贴贴]", + "3051": "[战双_闪亮]", + "3052": "[战双_柠檬精]", + "3053": "[战双_升天]", + "3054": "[战双_干杯]", + "3055": "[战双_饿饿]", + "3058": "[战双_点赞]", + "3056": "[战双_打call]", + "3057": "[战双_吃瓜]", + "3060": "[2233塔罗牌_???]", + "3061": "[2233塔罗牌_666]", + "3062": "[2233塔罗牌_AWSL]", + "3063": "[2233塔罗牌_奥利给]", + "3064": "[2233塔罗牌_比心心]", + "3065": "[2233塔罗牌_不愧是我]", + "3066": "[2233塔罗牌_不约]", + "3067": "[2233塔罗牌_撒花]", + "3068": "[2233塔罗牌_我觉得星]", + "3069": "[2233塔罗牌_下次一定]", + "3070": "[洛天依·夜航星_Lucky]", + "3071": "[洛天依·夜航星_比心]", + "3072": "[洛天依·夜航星_冲鸭]", + "3073": "[洛天依·夜航星_好康]", + "3074": "[洛天依·夜航星_锦鲤]", + "3075": "[洛天依·夜航星_溜了溜了]", + "3076": "[洛天依·夜航星_萌妹初醒]", + "3077": "[洛天依·夜航星_摸摸]", + "3078": "[洛天依·夜航星_起飞]", + "3079": "[洛天依·夜航星_爷青回]", + "3080": "[洛天依·夜航星_转运]", + "3081": "[洛天依·夜航星_崇拜]", + "3082": "[洛天依·夜航星_饿了]", + "3083": "[洛天依·夜航星_欢迎]", + "3084": "[洛天依·夜航星_新年快乐]", + "3085": "[2233电子喵_awsl]", + "3086": "[2233电子喵_gkd]", + "3087": "[2233电子喵_爱了爱了]", + "3088": "[2233电子喵_吹爆老婆]", + "3089": "[2233电子喵_从不中奖]", + "3090": "[2233电子喵_恭喜这个]", + "3091": "[2233电子喵_没钱了]", + "3092": "[2233电子喵_就这]", + "3093": "[2233电子喵_恰柠檬]", + "3094": "[2233电子喵_想桃子]", + "3096": "[神乐七奈_dd斩首]", + "3097": "[神乐七奈_变猫猫]", + "3098": "[神乐七奈_好耶]", + "3099": "[神乐七奈_卖柠檬]", + "3100": "[神乐七奈_你好]", + "3101": "[神乐七奈_七哩哩]", + "3102": "[神乐七奈_七哩哩增加]", + "3103": "[神乐七奈_随便画画]", + "3104": "[神乐七奈_辛苦辣]", + "3105": "[神乐七奈_疑惑]", + "3106": "[神乐七奈_擦玻璃]", + "3107": "[神乐七奈_剪刀手]", + "3108": "[神乐七奈_卖桃子]", + "3109": "[神乐七奈_忘崽牛奶]", + "3110": "[神乐七奈_震惊]", + "3115": "[小沪蝶2_不客气]", + "3116": "[小沪蝶2_来了来了]", + "3117": "[小沪蝶2_害羞]", + "3118": "[小沪蝶2_抱抱]", + "3119": "[小沪蝶2_加班快乐]", + "3120": "[小沪蝶2_下班啦]", + "3121": "[小沪蝶2_开心]", + "3122": "[小沪蝶2_哈哈哈哈哈]", + "3123": "[小沪蝶2_让我想想]", + "3124": "[小沪蝶2_晚安]", + "3125": "[小沪蝶2_早安]", + "3126": "[小沪蝶2_哇哦]", + "3127": "[小沪蝶2_不好意思]", + "3128": "[小沪蝶2_周末愉快]", + "3129": "[小沪蝶2_请多指教]", + "3130": "[小沪蝶2_学习强国]", + "3131": "[水瓶座_捕捉]", + "3132": "[水瓶座_灌水]", + "3133": "[水瓶座_好冷]", + "3134": "[水瓶座_喝热水]", + "3135": "[水瓶座_哭哭]", + "3136": "[水瓶座_困了]", + "3137": "[水瓶座_水平真高]", + "3138": "[水瓶座_贴贴]", + "3139": "[水瓶座_糟了!!]", + "3140": "[水瓶座_住口]", + "3147": "[2021拜年纪_2021]", + "3148": "[2021拜年纪_AWSL]", + "3149": "[2021拜年纪_棒]", + "3150": "[2021拜年纪_笑]", + "3151": "[2021拜年纪_嘲笑]", + "3152": "[2021拜年纪_福]", + "3153": "[2021拜年纪_投币]", + "3154": "[2021拜年纪_哇]", + "3155": "[2021拜年纪_新年快乐]", + "3156": "[2021拜年纪_一般]", + "3157": "[菜菜子_哈哈哈]", + "3158": "[菜菜子_啥玩意]", + "3159": "[菜菜子_比心心]", + "3160": "[菜菜子_赞]", + "3161": "[菜菜子_兴奋]", + "3162": "[菜菜子_开心]", + "3163": "[菜菜子_…]", + "3164": "[菜菜子_hi]", + "3165": "[菜菜子_吃包子]", + "3166": "[菜菜子_斗地主战神]", + "3167": "[菜菜子_哭唧唧]", + "3168": "[菜菜子_罗圈腿]", + "3169": "[菜菜子_咸鱼]", + "3170": "[菜菜子_疑惑]", + "3171": "[菜菜子_正道的光]", + "3177": "[多多poi_吃樱桃]", + "3178": "[多多poi_冲冲冲]", + "3179": "[多多poi_大哭]", + "3180": "[多多poi_害羞]", + "3181": "[多多poi_花痴]", + "3182": "[多多poi_开心]", + "3183": "[多多poi_教我做事]", + "3184": "[多多poi_生气]", + "3185": "[多多poi_危]", + "3186": "[多多poi_委屈]", + "3187": "[多多poi_喜欢]", + "3188": "[多多poi_嫌弃]", + "3189": "[多多poi_疑问]", + "3190": "[多多poi_震惊]", + "3191": "[多多poi_升天]", + "13398": "[多多poi_win]", + "13399": "[多多poi_啊——]", + "13400": "[多多poi_啊对对对]", + "13401": "[多多poi_地铁多多]", + "13402": "[多多poi_流汗]", + "13403": "[多多poi_略略略]", + "13404": "[多多poi_梦里都有]", + "13405": "[多多poi_上号]", + "13406": "[多多poi_思考]", + "13407": "[多多poi_贴贴]", + "3192": "[白之女王_傲慢]", + "3193": "[白之女王_生气]", + "3194": "[白之女王_哼]", + "3195": "[白之女王_吐血]", + "3196": "[白之女王_害羞]", + "3197": "[白之女王_害怕]", + "3198": "[白之女王_点赞]", + "3199": "[白之女王_无语]", + "3200": "[白之女王_崇拜]", + "3201": "[白之女王_疑惑]", + "3202": "[YUKIri_非洲鱼鱼萌]", + "3203": "[YUKIri_好耶]", + "3204": "[YUKIri_很强]", + "3205": "[YUKIri_咧嘴笑]", + "3206": "[YUKIri_裂开了]", + "3207": "[YUKIri_摸鱼]", + "3208": "[YUKIri_鱼鱼大笑]", + "3209": "[YUKIri_鱼鱼萌]", + "3210": "[YUKIri_鱼鱼萌吃惊]", + "3211": "[YUKIri_起飞]", + "3212": "[YUKIri_生气]", + "3213": "[YUKIri_委屈]", + "3214": "[YUKIri_无奈]", + "3215": "[YUKIri_疑问]", + "3216": "[YUKIri_哭哭]", + "3224": "[双鱼座_Zzz]", + "3225": "[双鱼座_发射爱心]", + "3226": "[双鱼座_摸头]", + "3227": "[双鱼座_祈愿流星]", + "3228": "[双鱼座_叹气]", + "3229": "[双鱼座_贴贴]", + "3230": "[双鱼座_哇塞]", + "3231": "[双鱼座_哭的小声]", + "3232": "[双鱼座_有事吗]", + "3233": "[双鱼座_做白日梦]", + "3234": "[崩坏3_GKD]", + "3235": "[崩坏3_拜托了]", + "3236": "[崩坏3_吃饭团]", + "3237": "[崩坏3_吃惊]", + "3238": "[崩坏3_赞]", + "3239": "[崩坏3_饿了]", + "3240": "[崩坏3_干杯]", + "3241": "[崩坏3_看书]", + "3242": "[崩坏3_生气]", + "3243": "[崩坏3_思考]", + "3244": "[崩坏3_叹气]", + "3245": "[崩坏3_疑惑]", + "3246": "[进击的冰糖_ok]", + "3247": "[进击的冰糖_XXBT]", + "3248": "[进击的冰糖_害怕]", + "3249": "[进击的冰糖_补充糖分]", + "3250": "[进击的冰糖_不愧是你]", + "3251": "[进击的冰糖_车门焊死]", + "3252": "[进击的冰糖_吃草]", + "3253": "[进击的冰糖_打call]", + "3254": "[进击的冰糖_懂得都懂]", + "3255": "[进击的冰糖_烤肉中]", + "3256": "[进击的冰糖_溜了溜了]", + "3257": "[进击的冰糖_每日一D]", + "3258": "[进击的冰糖_你邪了门]", + "3259": "[进击的冰糖_清楚清楚]", + "3260": "[进击的冰糖_锐利的剑]", + "3261": "[进击的冰糖_上流]", + "3262": "[进击的冰糖_探头]", + "3263": "[进击的冰糖_贴贴]", + "3264": "[进击的冰糖_挺好的]", + "3265": "[进击的冰糖_晚安]", + "3276": "[不问天_大哭]", + "3277": "[不问天_飞吻]", + "3278": "[不问天_好耶]", + "3279": "[不问天_大笑]", + "3280": "[不问天_prpr]", + "3281": "[不问天_和我结婚]", + "3282": "[不问天_啊这]", + "3283": "[不问天_吃瓜]", + "3284": "[不问天_干杯]", + "3285": "[不问天_喜喜]", + "3286": "[不问天_害羞]", + "3287": "[不问天_问号]", + "3288": "[不问天_感叹号]", + "3289": "[不问天_赞]", + "3290": "[不问天_来了!]", + "3291": "[伍六七_暗中观察]", + "3292": "[伍六七_比心]", + "3293": "[伍六七_给红包]", + "3294": "[伍六七_恭喜发财]", + "3295": "[伍六七_妙啊]", + "3296": "[伍六七_年年有鱼]", + "3297": "[伍六七_牛气冲天]", + "3298": "[伍六七_情比金坚]", + "3299": "[伍六七_送福]", + "3300": "[伍六七_震惊]", + "3305": "[我在皇宫当巨巨_裂开]", + "3306": "[我在皇宫当巨巨_略略略]", + "3307": "[我在皇宫当巨巨_跪求]", + "3308": "[我在皇宫当巨巨_瑟瑟发抖]", + "3309": "[我在皇宫当巨巨_立flag]", + "3310": "[我在皇宫当巨巨_狗粮]", + "3311": "[我在皇宫当巨巨_柠檬]", + "3312": "[我在皇宫当巨巨_心动]", + "3313": "[我在皇宫当巨巨_哼]", + "3314": "[我在皇宫当巨巨_吐血]", + "3315": "[我在皇宫当巨巨_怒了]", + "3316": "[我在皇宫当巨巨_弱小可怜]", + "3317": "[我在皇宫当巨巨_磕死我了]", + "3318": "[我在皇宫当巨巨_苟住]", + "3319": "[我在皇宫当巨巨_紧张]", + "3320": "[我在皇宫当巨巨_摇尾巴]", + "3321": "[网易王三三_爱哭鬼]", + "3322": "[网易王三三_爱了爱了]", + "3323": "[网易王三三_暗中观察]", + "3324": "[网易王三三_搬砖]", + "3325": "[网易王三三_吃瓜]", + "3326": "[网易王三三_打call]", + "3327": "[网易王三三_干饭]", + "3328": "[网易王三三_好家伙]", + "3329": "[网易王三三_惊了]", + "3330": "[网易王三三_累了]", + "3331": "[网易王三三_裂开]", + "3332": "[网易王三三_你不对劲]", + "3333": "[网易王三三_惬意]", + "3334": "[网易王三三_素质三连]", + "3335": "[网易王三三_贴贴]", + "3337": "[第五人格_汗]", + "3338": "[第五人格_送花]", + "3339": "[第五人格_打call]", + "3340": "[第五人格_疑惑]", + "3341": "[第五人格_点赞]", + "3342": "[第五人格_吃瓜]", + "3343": "[第五人格_惊]", + "3344": "[第五人格_机智]", + "3345": "[第五人格_害羞]", + "3346": "[第五人格_酸了]", + "3347": "[第五人格_哭]", + "3348": "[第五人格_偷笑]", + "3349": "[白色情人节_555]", + "3350": "[白色情人节_awsl]", + "3351": "[白色情人节_chou你]", + "3352": "[白色情人节_mua]", + "3353": "[白色情人节_ok]", + "3354": "[白色情人节_out]", + "3355": "[白色情人节_shot]", + "3356": "[白色情人节_yyds]", + "3357": "[白色情人节_爱了爱了]", + "3358": "[白色情人节_好想要]", + "3359": "[白色情人节_好兄弟]", + "3360": "[白色情人节_好耶]", + "3361": "[白色情人节_就这]", + "3362": "[白色情人节_老婆赛高]", + "3363": "[白色情人节_恰柠檬]", + "3364": "[白色情人节_拳头硬了]", + "3365": "[白色情人节_贴贴]", + "3366": "[白色情人节_一键三连]", + "3367": "[白色情人节_在吗]", + "3388": "[白色情人节_牛哦]", + "3368": "[干物妹!小埋_???]", + "3369": "[干物妹!小埋_拜托了哥哥]", + "3370": "[干物妹!小埋_抱抱]", + "3371": "[干物妹!小埋_变身]", + "3372": "[干物妹!小埋_补充体力]", + "3373": "[干物妹!小埋_不想动]", + "3374": "[干物妹!小埋_吨吨吨]", + "3375": "[干物妹!小埋_好吃]", + "3376": "[干物妹!小埋_好累啊]", + "3377": "[干物妹!小埋_假装没事]", + "3378": "[干物妹!小埋_简单的快乐]", + "3379": "[干物妹!小埋_看戏]", + "3380": "[干物妹!小埋_人生赢家]", + "3381": "[干物妹!小埋_瑟瑟发抖]", + "3382": "[干物妹!小埋_石化]", + "3383": "[干物妹!小埋_玩游戏]", + "3384": "[干物妹!小埋_我富了]", + "3385": "[干物妹!小埋_无语]", + "3386": "[干物妹!小埋_羡慕]", + "3387": "[干物妹!小埋_嘤嘤嘤]", + "3389": "[轻视频_呆滞]", + "3390": "[轻视频_点赞]", + "3391": "[轻视频_干杯]", + "3392": "[轻视频_哭哭]", + "3393": "[轻视频_妙啊]", + "3394": "[轻视频_酸了]", + "3395": "[轻视频_躺枪]", + "3396": "[轻视频_无语]", + "3397": "[轻视频_想桃子]", + "3398": "[轻视频_中奖绝缘]", + "3399": "[星座系列:白羊座_爱心表白]", + "3400": "[星座系列:白羊座_蹭蹭]", + "3401": "[星座系列:白羊座_冲鸭]", + "3402": "[星座系列:白羊座_乖巧]", + "3403": "[星座系列:白羊座_懒洋洋]", + "3404": "[星座系列:白羊座_哦是吗]", + "3405": "[星座系列:白羊座_起床小助手]", + "3406": "[星座系列:白羊座_贴贴]", + "3407": "[星座系列:白羊座_我直接回嗨]", + "3408": "[星座系列:白羊座_雨中肖邦]", + "3409": "[催更行动_催更机器]", + "3410": "[催更行动_对酒当鸽]", + "3411": "[催更行动_快更]", + "3412": "[催更行动_下次一定]", + "3413": "[催更行动_想看]", + "3414": "[催更行动_新建文件夹]", + "3415": "[催更行动_悬赏]", + "3416": "[催更行动_一直鸽一直爽]", + "3417": "[催更行动_在做了]", + "3418": "[催更行动_追更不停]", + "3419": "[Hiiro_Hii皮笑脸]", + "3420": "[Hiiro_wink]", + "3421": "[Hiiro_傲娇]", + "3422": "[Hiiro_饿了]", + "3423": "[Hiiro_感谢]", + "3424": "[Hiiro_害羞]", + "3425": "[Hiiro_可爱]", + "3426": "[Hiiro_哭笑不得]", + "3427": "[Hiiro_泪目]", + "3428": "[Hiiro_亲亲]", + "3429": "[Hiiro_烧开水]", + "3430": "[Hiiro_生气]", + "3431": "[Hiiro_王牛奶]", + "3432": "[Hiiro_王酸奶]", + "3433": "[Hiiro_装酷]", + "3434": "[小会员限定_GGMM]", + "3435": "[小会员限定_变身]", + "3436": "[小会员限定_别鸽]", + "3437": "[小会员限定_别说话吻我]", + "3438": "[小会员限定_村头见]", + "3439": "[小会员限定_电你一下]", + "3440": "[小会员限定_请深爱]", + "3441": "[小会员限定_玩心么MM]", + "3442": "[小会员限定_想当年]", + "3443": "[小会员限定_小会员集合]", + "3445": "[C酱_真剑胜负]", + "3446": "[C酱_宅]", + "3447": "[C酱_再来亿把]", + "3448": "[C酱_一般]", + "3449": "[C酱_下饭]", + "3450": "[C酱_问号]", + "3451": "[C酱_警告]", + "3452": "[C酱_惊吓]", + "3453": "[C酱_好诶]", + "3454": "[C酱_AWSL]", + "3455": "[C酱_害怕]", + "3456": "[C酱_哈哈]", + "3457": "[C酱_果咩捏]", + "3458": "[C酱_多喝开水]", + "3459": "[C酱_打游戏]", + "3460": "[C酱_超强]", + "3461": "[C酱_嘻嘻]", + "3462": "[C酱_秃头]", + "3463": "[C酱_不要啊]", + "3464": "[C酱_啊这]", + "3465": "[C酱_KOROSUZO]", + "3466": "[C酱_kira]", + "3467": "[C酱_HSO]", + "3468": "[C酱_GKD]", + "3469": "[C酱_)—(]", + "3498": "[未定事件簿_开心]", + "3499": "[未定事件簿_不开心]", + "3500": "[未定事件簿_点赞]", + "3501": "[未定事件簿_呜呜]", + "3502": "[未定事件簿_打call]", + "3503": "[未定事件簿_燃起来了]", + "3504": "[未定事件簿_看手机]", + "3505": "[未定事件簿_疑问]", + "3506": "[未定事件簿_宕机]", + "3507": "[未定事件簿_偷听]", + "3508": "[未定事件簿_吸氧]", + "3509": "[未定事件簿_你醒啦]", + "3510": "[未定事件簿_躲墙角]", + "3511": "[未定事件簿_扶额]", + "3512": "[未定事件簿_干杯]", + "3513": "[未定事件簿_盯]", + "3514": "[未定事件簿_冷笑]", + "3515": "[未定事件簿_菩萨]", + "3516": "[未定事件簿_怀疑]", + "3517": "[未定事件簿_点赞2]", + "3518": "[未定事件簿_柔弱]", + "3519": "[未定事件簿_可怜]", + "3520": "[未定事件簿_走了]", + "3521": "[未定事件簿_星星眼]", + "3522": "[未定事件簿_大喊]", + "3523": "[未定事件簿_no]", + "3524": "[未定事件簿_震惊]", + "3525": "[未定事件簿_yes]", + "3526": "[不思议之旅_YE!]", + "3527": "[不思议之旅_大笑]", + "3528": "[不思议之旅_惊吓兔]", + "3529": "[不思议之旅_冷漠]", + "3530": "[不思议之旅_卖萌]", + "3531": "[不思议之旅_摸鱼猫]", + "3532": "[不思议之旅_谁也不爱]", + "3533": "[不思议之旅_踢石子]", + "3534": "[不思议之旅_疑问]", + "3535": "[不思议之旅_嘤嘤嘤]", + "3536": "[萌宠派对_拜拜]", + "3537": "[萌宠派对_唱歌]", + "3538": "[萌宠派对_吃瓜]", + "3539": "[萌宠派对_躲雨]", + "3540": "[萌宠派对_干饭]", + "3541": "[萌宠派对_给你花花]", + "3542": "[萌宠派对_好快乐]", + "3543": "[萌宠派对_撒花]", + "3544": "[萌宠派对_我来了]", + "3545": "[萌宠派对_喜欢]", + "3546": "[春游_春眠]", + "3547": "[春游_荡秋千]", + "3548": "[春游_放风筝]", + "3549": "[春游_爬山]", + "3550": "[春游_赏花]", + "3551": "[春游_踏青]", + "3552": "[春游_跳舞]", + "3553": "[春游_野餐]", + "3554": "[春游_植树]", + "3555": "[春游_自拍]", + "3556": "[热词_催更]", + "3557": "[热词_集美]", + "3558": "[热词_就服你]", + "3559": "[热词_磕死我了]", + "3560": "[热词_你开心就好]", + "3561": "[热词_盘它]", + "3562": "[热词_世界的参差]", + "3563": "[热词_心脏骤停]", + "3564": "[热词_这河里吗]", + "3565": "[热词_支棱起来]", + "3566": "[橘猫_打call]", + "3567": "[橘猫_对不起]", + "3568": "[橘猫_干饭人]", + "3569": "[橘猫_咕咕]", + "3570": "[橘猫_记仇]", + "3571": "[橘猫_歇歇]", + "3572": "[橘猫_想你]", + "3573": "[橘猫_小丑]", + "3574": "[橘猫_星星眼]", + "3575": "[橘猫_自动分类]", + "4405": "[橘猫_打咩]", + "4406": "[橘猫_大橘为重]", + "4407": "[橘猫_拿来吧你]", + "4408": "[橘猫_贴贴]", + "4409": "[橘猫_超凶]", + "3576": "[水果派对_春困]", + "3577": "[水果派对_打扮]", + "3578": "[水果派对_打工魂]", + "3579": "[水果派对_干杯]", + "3580": "[水果派对_工具人]", + "3581": "[水果派对_期待]", + "3582": "[水果派对_生气]", + "3583": "[水果派对_素质三连]", + "3584": "[水果派对_偷偷开心]", + "3585": "[水果派对_委屈]", + "3586": "[名侦探柯南_WINK]", + "3587": "[名侦探柯南_暗中观察]", + "3588": "[名侦探柯南_冲啊!]", + "3589": "[名侦探柯南_害怕]", + "3590": "[名侦探柯南_机智]", + "3591": "[名侦探柯南_惊]", + "3592": "[名侦探柯南_开心]", + "3593": "[名侦探柯南_蔑视]", + "3594": "[名侦探柯南_目光如豆]", + "3595": "[名侦探柯南_强颜欢笑]", + "3596": "[名侦探柯南_推理]", + "3597": "[名侦探柯南_玩个球]", + "3598": "[名侦探柯南_心动]", + "3599": "[名侦探柯南_疑问?]", + "3600": "[名侦探柯南_真相只有一个]", + "3601": "[赏樱大会欧皇_拜托了]", + "3602": "[赏樱大会欧皇_大小姐]", + "3603": "[赏樱大会欧皇_凡尔赛]", + "3604": "[赏樱大会欧皇_皇家口气]", + "3605": "[赏樱大会欧皇_马萨卡]", + "3606": "[赏樱大会欧皇_我也要]", + "3607": "[赏樱大会欧皇_吸欧气]", + "3608": "[赏樱大会欧皇_一发入魂]", + "3609": "[赏樱大会欧皇_中了]", + "3610": "[赏樱大会欧皇_抓住希望]", + "3611": "[花丸晴琉_mua]", + "3612": "[花丸晴琉_wink]", + "3613": "[花丸晴琉_啊咧]", + "3614": "[花丸晴琉_大笑]", + "3615": "[花丸晴琉_呆住]", + "3616": "[花丸晴琉_对不起]", + "3617": "[花丸晴琉_好耶]", + "3618": "[花丸晴琉_挥手]", + "3619": "[花丸晴琉_惊慌]", + "3620": "[花丸晴琉_泪目]", + "3621": "[花丸晴琉_丧]", + "3622": "[花丸晴琉_生气]", + "3623": "[花丸晴琉_晚安]", + "3624": "[花丸晴琉_无语]", + "3625": "[花丸晴琉_喜欢]", + "3626": "[花丸晴琉_邪恶]", + "3627": "[花丸晴琉_疑惑]", + "3628": "[花丸晴琉_嘤嘤]", + "3629": "[花丸晴琉_赞]", + "3630": "[花丸晴琉_早安]", + "3631": "[金牛座_按住]", + "3632": "[金牛座_担忧]", + "3633": "[金牛座_富有]", + "3634": "[金牛座_干饭魂]", + "3635": "[金牛座_靠谱]", + "3636": "[金牛座_没钱了]", + "3637": "[金牛座_牛年大吉]", + "3638": "[金牛座_贴贴]", + "3639": "[金牛座_我寻思一下]", + "3640": "[金牛座_爷青回]", + "3641": "[椎名菜羽_nano好]", + "3642": "[椎名菜羽_NO]", + "3643": "[椎名菜羽_OK]", + "3644": "[椎名菜羽_好耶]", + "3645": "[椎名菜羽_愣]", + "3646": "[椎名菜羽_咪啪]", + "3647": "[椎名菜羽_铁咩]", + "3648": "[椎名菜羽_偷瞄]", + "3649": "[椎名菜羽_晚安]", + "3650": "[椎名菜羽_威严]", + "3651": "[椎名菜羽_喜欢]", + "3652": "[椎名菜羽_谢谢]", + "3653": "[椎名菜羽_辛苦了]", + "3654": "[椎名菜羽_雨刷]", + "3655": "[椎名菜羽_震撼]", + "3656": "[出游季_棒棒哒]", + "3657": "[出游季_崩溃]", + "3658": "[出游季_比心]", + "3659": "[出游季_出游]", + "3660": "[出游季_达咩]", + "3661": "[出游季_调皮]", + "3662": "[出游季_感受宇宙]", + "3663": "[出游季_害羞]", + "3664": "[出游季_投币]", + "3665": "[出游季_无欲无求]", + "3666": "[一周年纪念_awsl]", + "3667": "[一周年纪念_抱大腿]", + "3668": "[一周年纪念_吃棒棒糖]", + "3669": "[一周年纪念_大意了]", + "3670": "[一周年纪念_害羞]", + "3671": "[一周年纪念_换装]", + "3672": "[一周年纪念_来迟一步]", + "3673": "[一周年纪念_驴歇菜]", + "3674": "[一周年纪念_买买买]", + "3675": "[一周年纪念_摩多摩多]", + "3676": "[一周年纪念_抢]", + "3677": "[一周年纪念_网卡了]", + "3678": "[一周年纪念_委屈]", + "3679": "[一周年纪念_我真不馋]", + "3680": "[一周年纪念_谢谢惠顾]", + "3681": "[七海演唱会_kira]", + "3682": "[七海演唱会_拜托]", + "3683": "[七海演唱会_唱歌]", + "3684": "[七海演唱会_大笑]", + "3685": "[七海演唱会_呆]", + "3686": "[七海演唱会_盯]", + "3687": "[七海演唱会_哈欠]", + "3688": "[七海演唱会_害羞]", + "3689": "[七海演唱会_好诶]", + "3690": "[七海演唱会_坏笑]", + "3691": "[七海演唱会_迷惑]", + "3692": "[七海演唱会_认真]", + "3693": "[七海演唱会_瘫]", + "3694": "[七海演唱会_委屈]", + "3695": "[七海演唱会_无奈]", + "3697": "[罗伊Roi_Hi~]", + "3698": "[罗伊Roi_suki]", + "3699": "[罗伊Roi_ww]", + "3700": "[罗伊Roi_爱你]", + "3701": "[罗伊Roi_吃桃]", + "3702": "[罗伊Roi_点赞]", + "3703": "[罗伊Roi_干杯]", + "3704": "[罗伊Roi_好耶]", + "3705": "[罗伊Roi_击剑]", + "3706": "[罗伊Roi_惊讶]", + "3707": "[罗伊Roi_哭哭]", + "3708": "[罗伊Roi_希望人没事]", + "3709": "[罗伊Roi_嫌弃]", + "3710": "[罗伊Roi_小丑]", + "3711": "[罗伊Roi_疑惑]", + "3725": "[物述有栖_晚上好]", + "4044": "[物述有栖_bilibili]", + "3718": "[物述有栖_起来]", + "3726": "[物述有栖_香香UP]", + "3712": "[物述有栖_Foo↑Foo↑]", + "3713": "[物述有栖_mu~a]", + "3714": "[物述有栖_好耶]", + "3715": "[物述有栖_结婚]", + "4045": "[物述有栖_晚安]", + "3716": "[物述有栖_看戏]", + "3717": "[物述有栖_肯定]", + "3719": "[物述有栖_上勾拳]", + "3720": "[物述有栖_酸辣]", + "4046": "[物述有栖_JK]", + "4047": "[物述有栖_JS]", + "4048": "[物述有栖_panda]", + "3721": "[物述有栖_天才天才]", + "3722": "[物述有栖_歪比巴卜]", + "3723": "[物述有栖_歪头问号]", + "3724": "[物述有栖_玩手机]", + "3728": "[双子座_爱你]", + "3729": "[双子座_按住]", + "3730": "[双子座_抱抱]", + "3731": "[双子座_饿饿饭饭]", + "3732": "[双子座_复读]", + "3733": "[双子座_干嘛吖]", + "3734": "[双子座_没辙]", + "3735": "[双子座_贴贴]", + "3736": "[双子座_我哭了我装的]", + "3737": "[双子座_糟了]", + "3738": "[登门喜鹊_无言以对]", + "3739": "[登门喜鹊_emm]", + "3740": "[登门喜鹊_啊这]", + "3741": "[登门喜鹊_摆摆摆]", + "3742": "[登门喜鹊_吨吨吨]", + "3743": "[登门喜鹊_好耶]", + "3744": "[登门喜鹊_没羞没臊]", + "3745": "[登门喜鹊_美腿]", + "3746": "[登门喜鹊_求求了]", + "3747": "[登门喜鹊_弱小无助]", + "3748": "[登门喜鹊_生气]", + "3749": "[登门喜鹊_酸了]", + "3750": "[登门喜鹊_我觉得OK]", + "3751": "[登门喜鹊_希望没事]", + "3752": "[登门喜鹊_下次一定]", + "3753": "[登门喜鹊_鸢宝不要]", + "3754": "[登门喜鹊_鸢大头]", + "3755": "[登门喜鹊_鸢飞冲天]", + "3756": "[登门喜鹊_真香]", + "3757": "[登门喜鹊_中二]", + "3758": "[FPX凤仔_nice]", + "3759": "[FPX凤仔_吃瓜]", + "3760": "[FPX凤仔_冲呀]", + "3761": "[FPX凤仔_呆馋]", + "3762": "[FPX凤仔_嗯嗯]", + "3763": "[FPX凤仔_飞吻]", + "3764": "[FPX凤仔_嗨]", + "3765": "[FPX凤仔_坏笑]", + "3766": "[FPX凤仔_开黑]", + "3767": "[FPX凤仔_开心]", + "3768": "[FPX凤仔_冷漠]", + "3769": "[FPX凤仔_理直气壮]", + "3770": "[FPX凤仔_满头问号]", + "3771": "[FPX凤仔_你继续吹]", + "3772": "[FPX凤仔_生气]", + "3773": "[FPX凤仔_石化]", + "3774": "[FPX凤仔_收到]", + "3775": "[FPX凤仔_酸了]", + "3776": "[FPX凤仔_晚安]", + "3777": "[FPX凤仔_委屈巴巴]", + "3778": "[FPX凤仔_无语]", + "3779": "[FPX凤仔_下线]", + "3780": "[FPX凤仔_笑着流泪]", + "3781": "[FPX凤仔_谢谢老板]", + "3782": "[舰长_暗中观察]", + "3783": "[舰长_吃瓜]", + "3786": "[舰长_挥手]", + "3787": "[舰长_好耶]", + "3788": "[舰长_泪目]", + "3789": "[舰长_柠檬酸]", + "3790": "[舰长_气鼓鼓]", + "3791": "[舰长_思考中]", + "3792": "[舰长_哇哦]", + "3793": "[舰长_晚安]", + "3794": "[舰长_喜欢]", + "3795": "[舰长_谢谢]", + "3796": "[舰长_疑惑]", + "3797": "[舰长_赞]", + "3798": "[舰长_震惊]", + "3784": "[提督_棒]", + "3799": "[提督_mua]", + "3800": "[提督_啊咧]", + "3801": "[提督_傲娇]", + "3802": "[提督_抱抱]", + "3803": "[提督_呆住]", + "3804": "[提督_好耶]", + "3805": "[提督_警觉]", + "3806": "[提督_哭笑不得]", + "3807": "[提督_泪目]", + "3808": "[提督_生气]", + "3809": "[提督_耍酷]", + "3810": "[提督_嘻嘻]", + "3811": "[提督_心动]", + "3812": "[提督_召唤好运]", + "3785": "[总督_hi]", + "3813": "[总督_。。。]", + "3814": "[总督_ok]", + "3815": "[总督_wink]", + "3816": "[总督_比心]", + "3817": "[总督_哈哈哈]", + "3818": "[总督_好耶]", + "3819": "[总督_很强]", + "3820": "[总督_裂开]", + "3821": "[总督_灵光一闪]", + "3822": "[总督_起飞]", + "3823": "[总督_傻了]", + "3824": "[总督_咸鱼]", + "3825": "[总督_谢谢]", + "3826": "[总督_召唤好运]", + "3828": "[燃烧的远征_CCQ]", + "3829": "[燃烧的远征_狗杖人噬]", + "3830": "[燃烧的远征_拒绝跳树]", + "3831": "[燃烧的远征_卡拉赞毕业否]", + "3832": "[燃烧的远征_老狗也有几颗牙]", + "3833": "[燃烧的远征_路过]", + "3834": "[燃烧的远征_你们没萨满]", + "3835": "[燃烧的远征_你们没圣骑]", + "3836": "[燃烧的远征_强,学习了]", + "3837": "[燃烧的远征_死吧虫子]", + "3838": "[燃烧的远征_屠龙者咆哮]", + "3839": "[燃烧的远征_亡灵马]", + "3840": "[燃烧的远征_兄弟剑]", + "3841": "[燃烧的远征_赞达拉之魂]", + "3842": "[燃烧的远征_震惊]", + "3843": "[罗小黑战记_棒棒]", + "3844": "[罗小黑战记_比心]", + "3845": "[罗小黑战记_别惹我]", + "3846": "[罗小黑战记_困]", + "3847": "[罗小黑战记_生气]", + "3848": "[罗小黑战记_听不懂]", + "3849": "[罗小黑战记_委屈]", + "3850": "[罗小黑战记_吓]", + "3851": "[罗小黑战记_耶]", + "3852": "[罗小黑战记_疑问]", + "3990": "[罗小黑战记_上号]", + "3991": "[罗小黑战记_馋馋]", + "3992": "[罗小黑战记_开心]", + "3993": "[罗小黑战记_冒泡]", + "3994": "[罗小黑战记_贴贴]", + "3995": "[罗小黑战记_偷看]", + "3996": "[罗小黑战记_哇]", + "3997": "[罗小黑战记_偷笑]", + "3998": "[罗小黑战记_晚安]", + "3999": "[罗小黑战记_无语]", + "3853": "[大航海韩小沐_可爱2]", + "3854": "[大航海韩小沐_发量浓密]", + "3855": "[大航海韩小沐_问号]", + "3856": "[大航海韩小沐_doge]", + "3857": "[大航海韩小沐_打call]", + "3858": "[大航海韩小沐_大笑]", + "3859": "[大航海韩小沐_黑化]", + "3860": "[大航海韩小沐_记仇]", + "3861": "[大航海韩小沐_可爱]", + "3862": "[大航海韩小沐_泪沐]", + "3863": "[大航海韩小沐_裂开]", + "3864": "[大航海韩小沐_牛牛牛]", + "3865": "[大航海韩小沐_上舰]", + "3866": "[大航海韩小沐_酸了]", + "3867": "[大航海韩小沐_震惊]", + "3868": "[大航海嘉然_???]", + "3869": "[大航海嘉然_NO]", + "3870": "[大航海嘉然_YYDS]", + "3871": "[大航海嘉然_饿了]", + "3872": "[大航海嘉然_干饭]", + "3873": "[大航海嘉然_好耶]", + "3874": "[大航海嘉然_柠檬]", + "3875": "[大航海嘉然_生气]", + "3876": "[大航海嘉然_失意]", + "3877": "[大航海嘉然_晚安]", + "3878": "[大航海嘉然_委屈]", + "3879": "[大航海嘉然_喜欢]", + "3880": "[大航海嘉然_想你]", + "3881": "[大航海嘉然_原来如此]", + "3882": "[大航海嘉然_震撼]", + "3967": "[大航海嘉然_草莓]", + "3968": "[大航海嘉然_黄豆流汗]", + "3969": "[大航海嘉然_可爱捏]", + "3883": "[大航海散人_886]", + "3884": "[大航海散人_hello]", + "3885": "[大航海散人_等一个时机]", + "3886": "[大航海散人_干杯]", + "3887": "[大航海散人_高叟]", + "3888": "[大航海散人_勾引一下]", + "3889": "[大航海散人_哈哈哈]", + "3890": "[大航海散人_害怕]", + "3891": "[大航海散人_好的]", + "3892": "[大航海散人_散人干不死]", + "3893": "[大航海散人_逗你玩]", + "3894": "[大航海散人_生气了]", + "3895": "[大航海散人_晚安]", + "3896": "[大航海散人_向往自由]", + "3898": "[大航海散人_一键三连]", + "3899": "[BML2021_i了i了]", + "3900": "[BML2021_YYDS]", + "3901": "[BML2021_船新版本]", + "3902": "[BML2021_打call]", + "3903": "[BML2021_干杯]", + "3904": "[BML2021_高光时刻]", + "3905": "[BML2021_好家伙]", + "3906": "[BML2021_好耶]", + "3907": "[BML2021_开幕雷击]", + "3908": "[BML2021_狂喜]", + "3909": "[BML2021_妙啊]", + "3910": "[BML2021_芜湖起飞]", + "3911": "[BML2021_有内味了]", + "3912": "[BML2021_再来亿遍]", + "3913": "[BML2021_在现场!]", + "3914": "[BML2021_直呼内行]", + "3915": "[咩栗_……]", + "3916": "[咩栗_DD斩首]", + "3917": "[咩栗_nya]", + "3918": "[咩栗_吃草]", + "3919": "[咩栗_吃桃子]", + "3920": "[咩栗_聪明]", + "3921": "[咩栗_打call]", + "3922": "[咩栗_大哭]", + "3923": "[咩栗_害羞]", + "3924": "[咩栗_好耶]", + "3925": "[咩栗_哼!]", + "3926": "[咩栗_惊]", + "3927": "[咩栗_咩?]", + "3928": "[咩栗_咩呀!]", + "3929": "[咩栗_斯米马三]", + "3930": "[咩栗_贴贴]", + "3931": "[咩栗_压咩咯]", + "3932": "[咩栗_羊来!]", + "3933": "[咩栗_羊名立万]", + "3934": "[咩栗_羊羊得意]", + "3935": "[提摩西小队_?]", + "3936": "[提摩西小队_啊]", + "3937": "[提摩西小队_抱住]", + "3938": "[提摩西小队_笔记]", + "3939": "[提摩西小队_币给你]", + "3940": "[提摩西小队_吹楼上]", + "3941": "[提摩西小队_垂钓]", + "3942": "[提摩西小队_浮想联翩]", + "3943": "[提摩西小队_观光]", + "3944": "[提摩西小队_喝水]", + "3945": "[提摩西小队_看看楼下]", + "3946": "[提摩西小队_来了]", + "3947": "[提摩西小队_柠檬]", + "3948": "[提摩西小队_趴]", + "3949": "[提摩西小队_庆祝]", + "3950": "[提摩西小队_我死了]", + "3951": "[提摩西小队_砸屏幕]", + "3952": "[提摩西小队_抓虫]", + "3953": "[提摩西小队_想到了]", + "3954": "[提摩西小队_震惊]", + "3955": "[音律联觉_欢迎]", + "3956": "[音律联觉_赞]", + "3957": "[音律联觉_真的吗]", + "3958": "[音律联觉_期待]", + "3959": "[音律联觉_生气了]", + "3960": "[音律联觉_看手机]", + "3961": "[音律联觉_哭哭]", + "3962": "[音律联觉_吃瓜]", + "3963": "[音律联觉_疑惑]", + "3964": "[音律联觉_摇滚起来]", + "3965": "[音律联觉_和善]", + "3966": "[音律联觉_警告]", + "3970": "[乙女音_5000兆円]", + "3981": "[乙女音_喜欢]", + "3973": "[乙女音_好人]", + "3979": "[乙女音_谢谢]", + "3974": "[乙女音_好耶]", + "3975": "[乙女音_很行]", + "3976": "[乙女音_理解]", + "3977": "[乙女音_柠檬]", + "3972": "[乙女音_对不起]", + "3971": "[乙女音_打咩]", + "3978": "[乙女音_生气]", + "3980": "[乙女音_晚安]", + "3982": "[乙女音_嘻嘻]", + "3983": "[乙女音_原来如此]", + "3984": "[乙女音_震撼]", + "4001": "[来古弥新_放鸽桃]", + "4002": "[来古弥新_思考桃]", + "4003": "[来古弥新_叹气桃]", + "4004": "[来古弥新_贫穷桃]", + "4005": "[来古弥新_看穿桃]", + "4006": "[来古弥新_期待桃]", + "4007": "[来古弥新_端桃大师]", + "4008": "[来古弥新_桃跑]", + "4009": "[来古弥新_吃桃群众]", + "4010": "[来古弥新_爱你桃]", + "4011": "[雫るる_爱]", + "4012": "[雫るる_不想努力]", + "4013": "[雫るる_馋馋]", + "4014": "[雫るる_吃桃]", + "4015": "[雫るる_盯]", + "4016": "[雫るる_好耶]", + "4017": "[雫るる_理解不能]", + "4018": "[雫るる_理解理解]", + "4019": "[雫るる_没救了]", + "4020": "[雫るる_那没事了]", + "4021": "[雫るる_清楚]", + "4022": "[雫るる_生气]", + "4023": "[雫るる_酸]", + "4024": "[雫るる_贴贴]", + "4025": "[雫るる_嘻嘻]", + "4026": "[雫るる_喜欢]", + "4027": "[雫るる_嚣张]", + "4028": "[雫るる_震惊]", + "4029": "[雫るる_指指点点]", + "4030": "[雫るる_忠诚]", + "4031": "[BW2021_KSWL]", + "4032": "[BW2021_冲]", + "4033": "[BW2021_疯狂打call]", + "4034": "[BW2021_买买买]", + "4035": "[BW2021_搞什么飞机]", + "4036": "[BW2021_开始表演]", + "4037": "[BW2021_燃起来了]", + "4038": "[BW2021_三连走你]", + "4039": "[BW2021_呜呜呜]", + "4040": "[BW2021_向大佬低头]", + "4041": "[BW2021_真是绝了]", + "5109": "[贝拉kira_OK]", + "5110": "[贝拉kira_拜托]", + "5111": "[贝拉kira_贝式疑问]", + "5112": "[贝拉kira_笔芯]", + "5113": "[贝拉kira_别爱我]", + "5114": "[贝拉kira_别偷懒]", + "5115": "[贝拉kira_不会吧]", + "5116": "[贝拉kira_吃我贝极拳]", + "5117": "[贝拉kira_给我出去]", + "5118": "[贝拉kira_汗]", + "5119": "[贝拉kira_哼哼]", + "5120": "[贝拉kira_加油]", + "5121": "[贝拉kira_开心到劈叉]", + "5122": "[贝拉kira_看我可爱吗]", + "5123": "[贝拉kira_累趴]", + "5124": "[贝拉kira_送你小花花]", + "5125": "[贝拉kira_王之蔑视]", + "5126": "[贝拉kira_我晕了]", + "5127": "[贝拉kira_在吗在吗]", + "4049": "[贝拉kira_这题我会]", + "5736": "[贝拉kira_sorry]", + "5737": "[贝拉kira_芭蕾]", + "5738": "[贝拉kira_贝极星]", + "5739": "[贝拉kira_拖拉机下山]", + "5740": "[贝拉kira_我们是]", + "4057": "[嘉然_mua]", + "4058": "[嘉然_安详]", + "4059": "[嘉然_暗中观察]", + "4060": "[嘉然_锤头丧气]", + "4061": "[嘉然_点赞]", + "4062": "[嘉然_嘉人们]", + "4063": "[嘉然_嘉速心动]", + "4064": "[嘉然_流口水]", + "4065": "[嘉然_你好]", + "4066": "[嘉然_一米八]", + "4067": "[嘉然_一眼真]", + "4068": "[嘉然_biu]", + "4069": "[嘉然_绷不住了]", + "4070": "[嘉然_笔芯]", + "4071": "[嘉然_番茄炒蛋拳]", + "4072": "[嘉然_嘉油]", + "4073": "[嘉然_剪刀手]", + "4074": "[嘉然_略略略]", + "4075": "[嘉然_敲打]", + "4076": "[嘉然_吓人]", + "5746": "[嘉然_粉色小恶魔]", + "5747": "[嘉然_干饭]", + "5748": "[嘉然_嘉步]", + "5749": "[嘉然_我有嘉心糖]", + "5750": "[嘉然_我们是]", + "4077": "[巨蟹座_好热]", + "4078": "[巨蟹座_难顶]", + "4079": "[巨蟹座_拍拍]", + "4080": "[巨蟹座_体温过高]", + "4081": "[巨蟹座_贴贴]", + "4082": "[巨蟹座_铁锅炖自己]", + "4083": "[巨蟹座_痛啊]", + "4084": "[巨蟹座_蟹蟹]", + "4085": "[巨蟹座_蟹蟹爱你]", + "4086": "[巨蟹座_有八卦]", + "4088": "[夏日狂欢_阿巴阿巴]", + "4089": "[夏日狂欢_吃瓜]", + "4090": "[夏日狂欢_冲浪]", + "4091": "[夏日狂欢_嘎嘎嘎]", + "4092": "[夏日狂欢_好耶]", + "4093": "[夏日狂欢_捏个22]", + "4094": "[夏日狂欢_捏个33]", + "4095": "[夏日狂欢_潜水]", + "4096": "[夏日狂欢_沙瘫]", + "4097": "[夏日狂欢_嚣张]", + "4098": "[夏诺雅_DD斩首]", + "4099": "[夏诺雅_Kimo]", + "4100": "[夏诺雅_nya~]", + "4101": "[夏诺雅_rua猫]", + "4102": "[夏诺雅_爱]", + "4103": "[夏诺雅_才8点]", + "4104": "[夏诺雅_盯]", + "4105": "[夏诺雅_对不起]", + "4106": "[夏诺雅_绝壁]", + "4107": "[夏诺雅_老婆]", + "4108": "[夏诺雅_拍桌]", + "4109": "[夏诺雅_太会了]", + "4110": "[夏诺雅_晚安]", + "4111": "[夏诺雅_震撼]", + "4112": "[夏诺雅_做菜]", + "4113": "[时光代理人_不问将来]", + "4114": "[时光代理人_达成共识]", + "4115": "[时光代理人_听我指挥]", + "4116": "[时光代理人_埃及手]", + "4117": "[时光代理人_嘲讽]", + "4118": "[时光代理人_吃面]", + "4119": "[时光代理人_吃我一拳]", + "4120": "[时光代理人_贴贴]", + "4121": "[时光代理人_加密通话]", + "4122": "[时光代理人_委托完成]", + "4123": "[时光代理人_别刀了]", + "4124": "[时光代理人_退出]", + "4125": "[时光代理人_还债]", + "4126": "[时光代理人_推开]", + "4127": "[时光代理人_来活了]", + "4128": "[=咬人猫=]", + "4129": "[A路人]", + "4130": "[nya酱的一生]", + "4131": "[OELoop]", + "4132": "[zettaranc]", + "4133": "[啊吗粽]", + "4134": "[暴走漫画]", + "4135": "[大祥哥来了]", + "4136": "[倒悬的橘子]", + "4137": "[逗比的雀巢]", + "4138": "[泛式]", + "4139": "[韩小沐]", + "4140": "[鹤吱菌]", + "4141": "[黑猫厨房]", + "4142": "[红色激情]", + "4143": "[花花与三猫Catlive]", + "4144": "[吉原悠一_yui]", + "4145": "[凉风Kaze]", + "4146": "[刘老师说电影]", + "4147": "[绵羊料理]", + "4148": "[墨韵Moyun]", + "4149": "[中国BOY超级大猩猩]", + "4150": "[伊丽莎白鼠]", + "4151": "[伢伢gagako]", + "4152": "[轩邈Elias]", + "4153": "[-欣小萌-]", + "4154": "[小片片说大片]", + "4155": "[神秘店长A]", + "4156": "[三木刃]", + "4157": "[三代鹿人]", + "4158": "[枪弹轨迹]", + "4159": "[某幻君]", + "4160": "[敖厂长-66]", + "4161": "[逍遥散人]", + "4173": "[宝剑嫂]", + "4174": "[还有一天就放假了]", + "4175": "[六道轮回]", + "4176": "[罗翔说刑法]", + "4177": "[木鱼水心]", + "4178": "[排骨教主]", + "4179": "[瓶子君152-25]", + "4180": "[三无Marblue]", + "4181": "[特效小哥studio]", + "4182": "[小熊绅士]", + "4183": "[浑元Rysn]", + "4184": "[机智的党妹]", + "4185": "[星有野]", + "4186": "[硬核的半佛仙人]", + "4187": "[祖娅纳惜-60]", + "4188": "[KBShinya]", + "4162": "[魔法学院_bye]", + "4163": "[魔法学院_giegie]", + "4164": "[魔法学院_变猪]", + "4165": "[魔法学院_冲]", + "4166": "[魔法学院_打咩]", + "4167": "[魔法学院_干饭]", + "4168": "[魔法学院_好厉害]", + "4169": "[魔法学院_好耶]", + "4170": "[魔法学院_那我走]", + "4171": "[魔法学院_瘫]", + "4189": "[两不疑_无语]", + "4190": "[两不疑_得意]", + "4191": "[两不疑_疑惑]", + "4192": "[两不疑_拒绝]", + "4193": "[两不疑_开心]", + "4194": "[两不疑_委屈]", + "4195": "[两不疑_痛啊]", + "4196": "[两不疑_颤抖]", + "4197": "[两不疑_苦恼]", + "4198": "[两不疑_嗑拉了]", + "4199": "[屁屁鼓_???]", + "4200": "[屁屁鼓_YYDS]", + "4201": "[屁屁鼓_冲鸭]", + "4202": "[屁屁鼓_催更]", + "4203": "[屁屁鼓_第一]", + "4204": "[屁屁鼓_哈哈哈]", + "4205": "[屁屁鼓_加油]", + "4206": "[屁屁鼓_哭哭]", + "4207": "[屁屁鼓_你币有了]", + "4208": "[屁屁鼓_求三连]", + "4209": "[屁屁鼓_我裂开]", + "4210": "[屁屁鼓_无语]", + "4211": "[屁屁鼓_下次一定]", + "4212": "[屁屁鼓_自闭]", + "4213": "[屁屁鼓_前方高能]", + "4214": "[龙爸_爱你]", + "4215": "[龙爸_不行]", + "4216": "[龙爸_打call]", + "4217": "[龙爸_害羞]", + "4218": "[龙爸_惊]", + "4219": "[龙爸_惊喜]", + "4220": "[龙爸_开心]", + "4221": "[龙爸_可怜]", + "4222": "[龙爸_哭唧唧]", + "4223": "[龙爸_亲亲]", + "4224": "[龙爸_哇]", + "4225": "[龙爸_问号]", + "4226": "[龙爸_无语]", + "4227": "[龙爸_想想]", + "4228": "[龙爸_一键三连]", + "4239": "[洛天依9th_生日快乐]", + "4240": "[洛天依9th_太好听了]", + "4242": "[洛天依9th_一起唱]", + "4231": "[洛天依9th_好康!]", + "4237": "[洛天依9th_敲碗等]", + "4229": "[洛天依9th_超凶]", + "4238": "[洛天依9th_社会]", + "4230": "[洛天依9th_饿龙咆哮]", + "4232": "[洛天依9th_精神抖擞]", + "4233": "[洛天依9th_略略略]", + "4234": "[洛天依9th_没睡醒]", + "4235": "[洛天依9th_没眼看]", + "4236": "[洛天依9th_气鼓鼓]", + "4241": "[洛天依9th_想peach]", + "4243": "[洛天依9th_资词]", + "4244": "[美波七海_sukiki]", + "4245": "[美波七海_突击]", + "4246": "[美波七海_???]", + "4247": "[美波七海_fake]", + "4248": "[美波七海_GKD]", + "4249": "[美波七海_lei了]", + "4250": "[美波七海_笨笨]", + "4251": "[美波七海_不知道]", + "4252": "[美波七海_果咩]", + "4253": "[美波七海_哈]", + "4254": "[美波七海_好耶]", + "4255": "[美波七海_击剑]", + "4256": "[美波七海_确实]", + "4257": "[美波七海_投降]", + "4258": "[美波七海_我!]", + "4265": "[呜米_得意]", + "4266": "[呜米_你不对劲]", + "4267": "[呜米_一键三连]", + "4268": "[呜米_KiraKira]", + "4269": "[呜米_不高兴了]", + "4270": "[呜米_吃柠檬]", + "4271": "[呜米_打call]", + "4272": "[呜米_呆滞]", + "4273": "[呜米_刀口舔血]", + "4274": "[呜米_害羞]", + "4275": "[呜米_好气]", + "4276": "[呜米_好耶]", + "4277": "[呜米_激爽下班]", + "4278": "[呜米_啾咪]", + "4279": "[呜米_玫瑰]", + "4280": "[呜米_奶死]", + "4281": "[呜米_燃起来了]", + "4282": "[呜米_汪]", + "4283": "[呜米_呜呜]", + "4284": "[呜米_咸鱼]", + "4287": "[狮子座_傲娇女王]", + "4288": "[狮子座_本王准了]", + "4289": "[狮子座_啃一口]", + "4290": "[狮子座_买!]", + "4291": "[狮子座_喵呜]", + "4292": "[狮子座_赛高]", + "4293": "[狮子座_贴贴]", + "4294": "[狮子座_王者]", + "4295": "[狮子座_我不委屈]", + "4296": "[狮子座_要抱抱]", + "4297": "[中国绊爱2nd_AWSL]", + "4298": "[中国绊爱2nd_GKD]", + "4299": "[中国绊爱2nd_YYDS]", + "4300": "[中国绊爱2nd_啊这]", + "4301": "[中国绊爱2nd_不]", + "4302": "[中国绊爱2nd_地铁看手机]", + "4303": "[中国绊爱2nd_夺笋啊]", + "4304": "[中国绊爱2nd_狗头]", + "4305": "[中国绊爱2nd_害怕]", + "4306": "[中国绊爱2nd_嘿嘿嘿]", + "4307": "[中国绊爱2nd_哭了]", + "4308": "[中国绊爱2nd_快逃]", + "4309": "[中国绊爱2nd_噗]", + "4310": "[中国绊爱2nd_生气气]", + "4311": "[中国绊爱2nd_太菜太下饭]", + "4312": "[中国绊爱2nd_我觉得OK]", + "4313": "[中国绊爱2nd_握草]", + "4314": "[中国绊爱2nd_芜湖]", + "4315": "[中国绊爱2nd_有排面]", + "4316": "[中国绊爱2nd_知识增加了]", + "4317": "[古守血遊_?]", + "4318": "[古守血遊_BAN]", + "4319": "[古守血遊_不知道]", + "4320": "[古守血遊_打call]", + "4321": "[古守血遊_打工]", + "4322": "[古守血遊_打游戏]", + "4323": "[古守血遊_得意]", + "4324": "[古守血遊_古守空港]", + "4325": "[古守血遊_好耶]", + "4326": "[古守血遊_寂寞]", + "4327": "[古守血遊_摸鱼]", + "4328": "[古守血遊_睡大觉]", + "4329": "[古守血遊_嗦面]", + "4330": "[古守血遊_通宵]", + "4331": "[古守血遊_早上好]", + "4332": "[小希小桃_???]", + "4333": "[小希小桃_比心]", + "4334": "[小希小桃_变成河豚]", + "4335": "[小希小桃_茶茶]", + "4336": "[小希小桃_对不起]", + "4337": "[小希小桃_给你一斧]", + "4338": "[小希小桃_绝望]", + "4339": "[小希小桃_土土土]", + "4340": "[小希小桃_想peach]", + "4341": "[小希小桃_震惊]", + "4342": "[小希小桃_孜然一身]", + "4343": "[小希小桃_ai有力量]", + "4344": "[小希小桃_mua]", + "4345": "[小希小桃_被气晕]", + "4346": "[小希小桃_呆丝ki]", + "4347": "[小希小桃_得意]", + "4348": "[小希小桃_桃桃我悟了]", + "4349": "[小希小桃_委屈]", + "4350": "[小希小桃_这样那样]", + "4351": "[小希小桃_蚌埠住了]", + "4352": "[PinoQ_OK]", + "4353": "[PinoQ_爱你]", + "4354": "[PinoQ_乖巧]", + "4355": "[PinoQ_哈喽]", + "4356": "[PinoQ_哼]", + "4357": "[PinoQ_惊醒]", + "4358": "[PinoQ_开始吃书]", + "4359": "[PinoQ_哭]", + "4360": "[PinoQ_脸红]", + "4361": "[PinoQ_捏捏]", + "4362": "[PinoQ_什么]", + "4363": "[PinoQ_哇]", + "4364": "[PinoQ_谢谢]", + "4365": "[PinoQ_原地石化]", + "4366": "[PinoQ_在吗]", + "4370": "[夏日冰品_OK!]", + "4371": "[夏日冰品_YYGQ]", + "4372": "[夏日冰品_成了]", + "4373": "[夏日冰品_吃瓜]", + "4374": "[夏日冰品_冲呀!]", + "4375": "[夏日冰品_打CALL]", + "4376": "[夏日冰品_害羞]", + "4377": "[夏日冰品_好耶!]", + "4378": "[夏日冰品_冷漠]", + "4379": "[夏日冰品_溜了溜了]", + "4380": "[夏日冰品_猫猫没心眼]", + "4381": "[夏日冰品_迷惑行为]", + "4382": "[夏日冰品_柠檬好酸]", + "4383": "[夏日冰品_让我康康]", + "4384": "[夏日冰品_热晕了]", + "4385": "[夏日冰品_爽哦]", + "4386": "[夏日冰品_铁咩!]", + "4387": "[夏日冰品_我太难了]", + "4388": "[夏日冰品_我最闪亮]", + "4389": "[夏日冰品_呜呜呜]", + "4390": "[星尘_AWSL]", + "4391": "[星尘_比心]", + "4392": "[星尘_吃瓜]", + "4393": "[星尘_大哭]", + "4394": "[星尘_发呆]", + "4395": "[星尘_搞快点]", + "4396": "[星尘_给你星星]", + "4397": "[星尘_好听]", + "4398": "[星尘_啦啦啦]", + "4399": "[星尘_酸]", + "4400": "[星尘_晚安]", + "4401": "[星尘_无语]", + "4402": "[星尘_永远滴神]", + "4403": "[星尘_真不错]", + "4404": "[星尘_震惊]", + "4410": "[工作细胞_搬砖]", + "4411": "[工作细胞_别担心]", + "4412": "[工作细胞_别怂]", + "4413": "[工作细胞_冲鸭]", + "4414": "[工作细胞_戳戳]", + "4415": "[工作细胞_打扰了]", + "4416": "[工作细胞_核弹开关]", + "4417": "[工作细胞_加油]", + "4418": "[工作细胞_交给我]", + "4419": "[工作细胞_惊吓]", + "4420": "[工作细胞_霉菌再见]", + "4421": "[工作细胞_迷路]", + "4422": "[工作细胞_努力工作]", + "4423": "[工作细胞_前方施工]", + "4424": "[工作细胞_闪亮登场]", + "4425": "[工作细胞_社死]", + "4426": "[工作细胞_团建]", + "4427": "[工作细胞_想不起来]", + "4428": "[工作细胞_辛苦了]", + "4429": "[工作细胞_中暑]", + "4430": "[早稻叽_AWSL]", + "4431": "[早稻叽_no]", + "4432": "[早稻叽_YES!]", + "4433": "[早稻叽_馋]", + "4434": "[早稻叽_嘲笑]", + "4435": "[早稻叽_冲鸭]", + "4436": "[早稻叽_薅呆毛]", + "4437": "[早稻叽_老婆]", + "4438": "[早稻叽_泪目]", + "4439": "[早稻叽_冒头]", + "4440": "[早稻叽_生气]", + "4441": "[早稻叽_瘫]", + "4442": "[早稻叽_打嗝]", + "4443": "[早稻叽_元气满满]", + "4444": "[早稻叽_比心]", + "4445": "[早稻叽_戳]", + "4446": "[早稻叽_牛啊]", + "4447": "[早稻叽_亲亲]", + "4448": "[早稻叽_无语]", + "4449": "[早稻叽_亿点点]", + "4451": "[十一周年_YYDS]", + "4452": "[十一周年_不忍直视]", + "4453": "[十一周年_害怕]", + "4454": "[十一周年_害羞]", + "4455": "[十一周年_好耶]", + "4456": "[十一周年_愣住]", + "4457": "[十一周年_生日快乐]", + "4458": "[十一周年_送礼物]", + "4459": "[十一周年_委屈]", + "4460": "[十一周年_我枯了]", + "4461": "[白神遥_5835]", + "4462": "[白神遥_阿巴阿巴]", + "4463": "[白神遥_豹怒]", + "4464": "[白神遥_豹笑]", + "4465": "[白神遥_kusa]", + "4466": "[白神遥_吃瓜]", + "4467": "[白神遥_吃惊]", + "4468": "[白神遥_吃桃子]", + "4469": "[白神遥_呆呆]", + "4470": "[白神遥_点赞]", + "4471": "[白神遥_好耶]", + "4472": "[白神遥_滑稽]", + "4473": "[白神遥_加油]", + "4474": "[白神遥_骄傲]", + "4475": "[白神遥_哭哭]", + "4476": "[白神遥_柠檬]", + "4477": "[白神遥_让我康康]", + "4478": "[白神遥_跳脸]", + "4479": "[白神遥_晚安]", + "4480": "[白神遥_问号]", + "4481": "[百妖谱2_爱了爱了]", + "4482": "[百妖谱2_拜托拜托]", + "4483": "[百妖谱2_不安好心]", + "4484": "[百妖谱2_放饭了]", + "4485": "[百妖谱2_干饭人]", + "4486": "[百妖谱2_记仇]", + "4487": "[百妖谱2_流泪]", + "4488": "[百妖谱2_贴贴]", + "4489": "[百妖谱2_星星眼]", + "4490": "[百妖谱2_自闭]", + "4491": "[CHE_emmm]", + "4492": "[CHE_respect]", + "4493": "[CHE_salute]", + "4494": "[CHE_吹空调]", + "4495": "[CHE_大为震撼]", + "4496": "[CHE_拿来吧你]", + "4497": "[CHE_强壮]", + "4498": "[CHE_晒化了]", + "4499": "[CHE_学习ing]", + "4500": "[CHE_嘬奶茶]", + "4501": "[处女座_nice]", + "4502": "[处女座_sos]", + "4503": "[处女座_啊呀呀]", + "4504": "[处女座_净化]", + "4505": "[处女座_拿来吧你]", + "4506": "[处女座_让我康康]", + "4507": "[处女座_贴贴]", + "4508": "[处女座_呜呜]", + "4509": "[处女座_优雅]", + "4510": "[处女座_啊?]", + "4511": "[帕里_DD]", + "4512": "[帕里_白目]", + "4513": "[帕里_差评]", + "4514": "[帕里_吃瓜]", + "4515": "[帕里_第一]", + "4516": "[帕里_盯]", + "4517": "[帕里_好人]", + "4518": "[帕里_惊讶]", + "4519": "[帕里_拍桌]", + "4520": "[帕里_笑]", + "4521": "[帕里_疑问]", + "4522": "[帕里_饮茶]", + "4523": "[帕里_赞美]", + "4524": "[帕里_震撼]", + "4525": "[帕里_揍]", + "4526": "[小东人鱼_ok]", + "4527": "[小东人鱼_拜托]", + "4528": "[小东人鱼_病娇]", + "4529": "[小东人鱼_瞠目结舌脸]", + "4530": "[小东人鱼_吃惊]", + "4531": "[小东人鱼_打招呼]", + "4532": "[小东人鱼_大笑]", + "4533": "[小东人鱼_抱歉]", + "4534": "[小东人鱼_得意脸]", + "4535": "[小东人鱼_加油]", + "4536": "[小东人鱼_焦急]", + "4537": "[小东人鱼_哭哭]", + "4538": "[小东人鱼_困]", + "4539": "[小东人鱼_生气]", + "4540": "[小东人鱼_失望]", + "4541": "[小东人鱼_无语]", + "4542": "[小东人鱼_喜欢]", + "4543": "[小东人鱼_笑脸]", + "4544": "[小东人鱼_疑问]", + "4545": "[小东人鱼_自信]", + "4550": "[田中姬铃木雏_awsl]", + "4551": "[田中姬铃木雏_鼻血]", + "4552": "[田中姬铃木雏_好~]", + "4553": "[田中姬铃木雏_好可怕]", + "4554": "[田中姬铃木雏_塔纳t]", + "4555": "[田中姬铃木雏_我懂的]", + "4556": "[田中姬铃木雏_我很冷静]", + "4557": "[田中姬铃木雏_遗忘光线]", + "4558": "[田中姬铃木雏_再见]", + "4559": "[田中姬铃木雏_啊哈]", + "4560": "[田中姬铃木雏_豆芽]", + "4561": "[田中姬铃木雏_胡萝卜应援]", + "4562": "[田中姬铃木雏_科马内奇]", + "4563": "[田中姬铃木雏_铃木爆炸]", + "4564": "[田中姬铃木雏_为什喵!]", + "4565": "[萌宠狗狗_666]", + "4566": "[萌宠狗狗_保持微笑]", + "4567": "[萌宠狗狗_超凶]", + "4568": "[萌宠狗狗_出去玩]", + "4569": "[萌宠狗狗_打扰了]", + "4570": "[萌宠狗狗_乖乖狗]", + "4571": "[萌宠狗狗_开饭啦]", + "4572": "[萌宠狗狗_看戏]", + "4573": "[萌宠狗狗_快去学习]", + "4574": "[萌宠狗狗_秋游]", + "4575": "[萌宠狗狗_思考狗生]", + "4576": "[萌宠狗狗_贴贴]", + "4577": "[萌宠狗狗_吐舌]", + "4578": "[萌宠狗狗_先睡了]", + "4579": "[萌宠狗狗_主人!]", + "4580": "[怪盗基德_BINGO]", + "4581": "[怪盗基德_出警]", + "4582": "[怪盗基德_登场]", + "4583": "[怪盗基德_咕咕]", + "4584": "[怪盗基德_害羞]", + "4585": "[怪盗基德_路过]", + "4586": "[怪盗基德_魔术]", + "4587": "[怪盗基德_无语]", + "4588": "[怪盗基德_献花]", + "4589": "[怪盗基德_嘘]", + "4590": "[怪盗基德_疑惑]", + "4591": "[怪盗基德_预告函]", + "4592": "[怪盗基德_再见]", + "4593": "[怪盗基德_这个嘛]", + "4594": "[怪盗基德_震惊]", + "4595": "[阿梓_安静一下]", + "4596": "[阿梓_别走好吗]", + "4597": "[阿梓_不怕困难]", + "4598": "[阿梓_吹溜溜笛]", + "4599": "[阿梓_大家好呀]", + "4600": "[阿梓_就这?]", + "4601": "[阿梓_可爱捏]", + "4602": "[阿梓_哭哭]", + "4603": "[阿梓_拿个快递]", + "4604": "[阿梓_恰饱饱]", + "4605": "[阿梓_拳头硬了]", + "4606": "[阿梓_忍者修行]", + "4607": "[阿梓_太破费了]", + "4608": "[阿梓_歪歪歪]", + "4609": "[阿梓_我晒吗]", + "4610": "[阿梓_我是楞啊]", + "4611": "[阿梓_谢谢你呀]", + "4612": "[阿梓_咋回事捏]", + "4613": "[阿梓_真接受不了]", + "4614": "[阿梓_真牛蛙]", + "4615": "[FPX凤仔第二弹_武运昌隆]", + "4616": "[FPX凤仔第二弹_嗨起来]", + "4617": "[FPX凤仔第二弹_OK]", + "4618": "[FPX凤仔第二弹_起飞]", + "4619": "[FPX凤仔第二弹_制冷]", + "4620": "[FPX凤仔第二弹_凤一其随]", + "4621": "[FPX凤仔第二弹_干杯]", + "4622": "[FPX凤仔第二弹_big胆]", + "4623": "[FPX凤仔第二弹_打扰了]", + "4624": "[FPX凤仔第二弹_格局小了]", + "4625": "[FPX凤仔第二弹_害怕]", + "4626": "[FPX凤仔第二弹_喝水]", + "4627": "[FPX凤仔第二弹_敬礼]", + "4628": "[FPX凤仔第二弹_鞠躬]", + "4629": "[FPX凤仔第二弹_可怜]", + "4630": "[FPX凤仔第二弹_来了]", + "4631": "[FPX凤仔第二弹_流汗]", + "4632": "[FPX凤仔第二弹_你礼貌么]", + "4633": "[FPX凤仔第二弹_怒]", + "4634": "[FPX凤仔第二弹_破防]", + "4635": "[FPX凤仔第二弹_吸氧]", + "4636": "[FPX凤仔第二弹_思考]", + "4637": "[FPX凤仔第二弹_捂脸]", + "4638": "[FPX凤仔第二弹_震惊]", + "4639": "[青蛇劫起_比心]", + "4640": "[青蛇劫起_好耶]", + "4641": "[青蛇劫起_加油]", + "4642": "[青蛇劫起_哭哭]", + "4643": "[青蛇劫起_女王]", + "4644": "[青蛇劫起_塔塔开]", + "4645": "[青蛇劫起_贴贴]", + "4646": "[青蛇劫起_委屈]", + "4647": "[青蛇劫起_凶]", + "4648": "[青蛇劫起_哟]", + "4650": "[泰蕾莎_suki]", + "4651": "[泰蕾莎_昂]", + "4652": "[泰蕾莎_呆滞]", + "4653": "[泰蕾莎_额]", + "4654": "[泰蕾莎_苦鲁西]", + "4655": "[泰蕾莎_迷惑发言]", + "4656": "[泰蕾莎_拿来吧你]", + "4657": "[泰蕾莎_哦]", + "4658": "[泰蕾莎_气到变形]", + "4659": "[泰蕾莎_强呀]", + "4660": "[泰蕾莎_闪光弹]", + "4661": "[泰蕾莎_爽耶]", + "4662": "[泰蕾莎_听着呢]", + "4663": "[泰蕾莎_完蛋]", + "4664": "[泰蕾莎_做梦]", + "4665": "[花园Serena2_力量]", + "4666": "[花园Serena2_wink]", + "4667": "[花园Serena2_但我拒绝]", + "4668": "[花园Serena2_豆芽]", + "4669": "[花园Serena2_好耶]", + "4670": "[花园Serena2_慌乱]", + "4671": "[花园Serena2_哭]", + "4672": "[花园Serena2_冷漠]", + "4673": "[花园Serena2_猫瞪口呆]", + "4674": "[花园Serena2_挠头]", + "4675": "[花园Serena2_牛啊]", + "4676": "[花园Serena2_奇怪知识]", + "4677": "[花园Serena2_偷笑]", + "4678": "[花园Serena2_委屈]", + "4679": "[花园Serena2_赞赏]", + "4682": "[Overidea_???]", + "4683": "[Overidea_awsl]", + "4684": "[Overidea_DD鸟]", + "4685": "[Overidea_大锤]", + "4686": "[Overidea_点赞]", + "4687": "[Overidea_干杯]", + "4688": "[Overidea_击剑]", + "4689": "[Overidea_京了!]", + "4690": "[Overidea_你是好人]", + "4691": "[Overidea_你斜了门]", + "4692": "[Overidea_上D]", + "4693": "[Overidea_挺好的]", + "4694": "[Overidea_谢谢]", + "4695": "[Overidea_赢了]", + "4696": "[Overidea_你细品]", + "4697": "[Cat2_FallInto!]", + "4698": "[Cat2_FallOut!?]", + "4699": "[Cat2_Watching!]", + "4700": "[Cat2_Nyampage]", + "4701": "[Cat2_Astromeawt!]", + "4702": "[Cat2_Inbox!]", + "4703": "[Cat2_Boom!]", + "4704": "[Cat2_SushiBed!]", + "4705": "[Cat2_EatByDonut]", + "4706": "[Cat2_EatDonut]", + "4707": "[Cat2_SpaceNyan!]", + "4708": "[Cat2_OddFriend]", + "4709": "[Cat2_IsLiquid!]", + "4710": "[Cat2_Hoop!]", + "4711": "[Cat2_DoorKeeper!]", + "4712": "[Cat2_Stargazer!]", + "4713": "[Cat2_JupiterSpot]", + "4714": "[Cat2_Catkitchen]", + "4715": "[Cat2_Launch!]", + "4716": "[Cat2_CatAMail]", + "4717": "[Cat2_Spark!!]", + "4718": "[Cat2_BlackHole!]", + "4719": "[Cat2_Slacking!]", + "4720": "[Cat2_Pinch!!]", + "4721": "[Cat2_Leak]", + "4722": "[Cat2_FatCat!?]", + "4723": "[Cat2_Cross!]", + "4724": "[Cat2_Infinite!]", + "4725": "[Cat2_Escort2!]", + "4726": "[Cat2_Chaos]", + "4727": "[星座特典_惩罚]", + "4728": "[星座特典_秤赞]", + "4729": "[星座特典_宠溺]", + "4730": "[星座特典_调皮]", + "4731": "[星座特典_发射爱心]", + "4732": "[星座特典_害羞]", + "4733": "[星座特典_好运来]", + "4734": "[星座特典_捧在手心]", + "4735": "[星座特典_起飞]", + "4736": "[星座特典_撒花]", + "4737": "[星座特典_晒娃]", + "4738": "[星座特典_贴贴]", + "4739": "[星座特典_投喂]", + "4740": "[星座特典_仙女落泪]", + "4741": "[星座特典_星座史册]", + "4742": "[大会员5周年_不愧是我]", + "4743": "[大会员5周年_打call]", + "4744": "[大会员5周年_大佬来了]", + "4745": "[大会员5周年_放礼花]", + "4746": "[大会员5周年_疯狂点头]", + "4747": "[大会员5周年_喝茶]", + "4748": "[大会员5周年_我觉得行]", + "4749": "[大会员5周年_下次一定]", + "4750": "[大会员5周年_整笑了]", + "4751": "[大会员5周年_装到了]", + "4752": "[小电视看s11_666]", + "4753": "[小电视看s11_MVP]", + "4754": "[小电视看s11_别浪]", + "4755": "[小电视看s11_冲冲冲]", + "4756": "[小电视看s11_打call]", + "4757": "[小电视看s11_逆风翻盘]", + "4758": "[小电视看s11_下次一定]", + "4759": "[小电视看s11_一起看]", + "4760": "[小电视看s11_永远的神]", + "4761": "[小电视看s11_这合理么]", + "4762": "[少年歌行_no]", + "4763": "[少年歌行_yes]", + "4764": "[少年歌行_比心]", + "4765": "[少年歌行_馋]", + "4766": "[少年歌行_冲鸭]", + "4767": "[少年歌行_戳]", + "4768": "[少年歌行_泪目]", + "4769": "[少年歌行_亿点点]", + "4770": "[少年歌行_元气满满]", + "4771": "[少年歌行_在吗]", + "4772": "[向晚_打游戏]", + "4773": "[向晚_弹吉他]", + "4774": "[向晚_得意]", + "4775": "[向晚_顶碗]", + "4776": "[向晚_哈!]", + "4777": "[向晚_嗨起来]", + "4778": "[向晚_害羞]", + "4779": "[向晚_好可怕]", + "4780": "[向晚_嗯?]", + "4781": "[向晚_晚步]", + "4782": "[向晚_捂头]", + "4783": "[向晚_喜欢]", + "4784": "[向晚_下雨了]", + "4785": "[向晚_星星眼]", + "4786": "[向晚_AVA]", + "4787": "[向晚_等待]", + "4788": "[向晚_困]", + "4789": "[向晚_陶醉]", + "4790": "[向晚_头大]", + "4791": "[向晚_微笑]", + "5741": "[向晚_鼻涕泡]", + "5742": "[向晚_大头]", + "5743": "[向晚_给你一拳]", + "5744": "[向晚_水母]", + "5745": "[向晚_我们是]", + "4792": "[RNG_出征]", + "4793": "[RNG_点点]", + "4794": "[RNG_666]", + "4795": "[RNG_牛]", + "4796": "[RNG_哭哭]", + "4797": "[RNG_集合]", + "4798": "[RNG_荣光加冕]", + "4799": "[RNG_庆祝]", + "4800": "[RNG_冲]", + "4801": "[RNG_RNG]", + "4802": "[EDG_666]", + "4803": "[EDG_777]", + "4804": "[EDG_出征]", + "4805": "[EDG_点点]", + "4806": "[EDG_集结]", + "4807": "[EDG_加油]", + "4808": "[EDG_哭哭]", + "4809": "[EDG_喜欢]", + "4810": "[EDG_有了]", + "4811": "[EDG_EDG]", + "5128": "[EDG_3:2]", + "5129": "[EDG_冠军777]", + "5130": "[EDG_我们是冠军]", + "5131": "[EDG_王者归来]", + "5132": "[EDG_奖杯]", + "4812": "[FPX_666]", + "4813": "[FPX_出征]", + "4814": "[FPX_加油]", + "4815": "[FPX_哭哭]", + "4816": "[FPX_凌晨战神]", + "4817": "[FPX_撕破伤口]", + "4818": "[FPX_踏火重鸣]", + "4819": "[FPX_无语]", + "4820": "[FPX_武运昌隆]", + "4821": "[FPX_FPX]", + "4822": "[LNG_666]", + "4823": "[LNG_出征]", + "4824": "[LNG_点点]", + "4825": "[LNG_加油]", + "4826": "[LNG_哭哭]", + "4827": "[LNG_麒麟云中现]", + "4828": "[LNG_要有光]", + "4829": "[LNG_营业]", + "4830": "[LNG_有了]", + "4831": "[LNG_LNG]", + "4832": "[LPL_赞]", + "4833": "[LPL_GG]", + "4834": "[LPL_YYDS]", + "4835": "[LPL_加油]", + "4836": "[LPL_来吧]", + "4837": "[LPL_跑]", + "4838": "[LPL_鲨]", + "4839": "[LPL_冲]", + "4840": "[LPL_五杀]", + "4841": "[LPL_喜欢]", + "4856": "[萌宠小兔_怎能吃兔兔]", + "4843": "[萌宠小兔_HIHI]", + "4844": "[萌宠小兔_奔月]", + "4845": "[萌宠小兔_吃白菜]", + "4846": "[萌宠小兔_乖乖]", + "4847": "[萌宠小兔_好舒服]", + "4848": "[萌宠小兔_惊吓]", + "4849": "[萌宠小兔_泪目]", + "4850": "[萌宠小兔_气鼓鼓]", + "4851": "[萌宠小兔_贴贴]", + "4852": "[萌宠小兔_偷看]", + "4853": "[萌宠小兔_挖洞]", + "4854": "[萌宠小兔_要抱抱]", + "4855": "[萌宠小兔_在吗]", + "4857": "[萌宠小兔_吃萝卜]", + "4858": "[我的音乐你听吗_窒息]", + "4859": "[我的音乐你听吗_不失礼貌]", + "4860": "[我的音乐你听吗_赞]", + "4861": "[我的音乐你听吗_格局]", + "4862": "[我的音乐你听吗_逆袭]", + "4863": "[我的音乐你听吗_勿扰]", + "4864": "[我的音乐你听吗_鹅鹅鹅]", + "4865": "[我的音乐你听吗_OhYeah]", + "4866": "[我的音乐你听吗_很难不火]", + "4867": "[我的音乐你听吗_共悲喜]", + "4868": "[我的音乐你听吗_魅惑]", + "4869": "[我的音乐你听吗_害羞]", + "4870": "[我的音乐你听吗_比心]", + "4871": "[我的音乐你听吗_干哈呢]", + "4872": "[我的音乐你听吗_Fashion]", + "4873": "[猫之茗_盯住]", + "4874": "[猫之茗_害羞]", + "4875": "[猫之茗_好耶]", + "4876": "[猫之茗_竟然]", + "4877": "[猫之茗_开心]", + "4878": "[猫之茗_来了]", + "4879": "[猫之茗_冷汗]", + "4880": "[猫之茗_弱智]", + "4881": "[猫之茗_生气]", + "4882": "[猫之茗_睡觉]", + "4883": "[雪绘_OK]", + "4884": "[雪绘_prprpr]", + "4885": "[雪绘_啊这]", + "4886": "[雪绘_比心]", + "4887": "[雪绘_吃惊]", + "4888": "[雪绘_乖巧]", + "4889": "[雪绘_火冒三丈]", + "4890": "[雪绘_键盘侠]", + "4891": "[雪绘_冒头]", + "4892": "[雪绘_皮]", + "4893": "[雪绘_少来这套]", + "4894": "[雪绘_生气]", + "4895": "[雪绘_酸了]", + "4896": "[雪绘_瘫]", + "4897": "[雪绘_晚安]", + "4898": "[雪绘_委屈]", + "4899": "[雪绘_星星眼]", + "4900": "[雪绘_指指点点]", + "4901": "[雪绘_抓尾巴]", + "4927": "[雪绘_学绘了]", + "4902": "[阿巳小铃铛_不愧是你]", + "4903": "[阿巳小铃铛_吃我一砖]", + "4904": "[阿巳小铃铛_冲鸭]", + "4905": "[阿巳小铃铛_抚摸抚摸]", + "2748": "[阿巳小铃铛_干饭]", + "4906": "[阿巳小铃铛_惊呆]", + "4907": "[阿巳小铃铛_没爱了]", + "4908": "[阿巳小铃铛_你礼貌吗]", + "4909": "[阿巳小铃铛_生气]", + "4910": "[阿巳小铃铛_是心动呀]", + "4912": "[早见咲_OK]", + "4913": "[早见咲_saki招呼]", + "4914": "[早见咲_不愧是我]", + "4915": "[早见咲_喝茶]", + "4916": "[早见咲_好耶]", + "4917": "[早见咲_威亚]", + "4918": "[早见咲_嫌弃]", + "4919": "[早见咲_很不爽]", + "4920": "[早见咲_微笑]", + "4921": "[早见咲_我呸]", + "4922": "[早见咲_晚安]", + "4923": "[早见咲_盯]", + "4924": "[早见咲_谢谢]", + "4925": "[早见咲_铁咩]", + "4926": "[早见咲_震撼]", + "4928": "[坎公骑冠剑_YYDS]", + "4929": "[坎公骑冠剑_唱歌]", + "4930": "[坎公骑冠剑_吃鸡腿]", + "4931": "[坎公骑冠剑_出来玩]", + "4932": "[坎公骑冠剑_干杯]", + "4933": "[坎公骑冠剑_乖巧]", + "4934": "[坎公骑冠剑_惊叹]", + "4935": "[坎公骑冠剑_嗯!]", + "4936": "[坎公骑冠剑_你还好吗]", + "4937": "[坎公骑冠剑_你在干嘛]", + "4938": "[坎公骑冠剑_欧气传输]", + "4939": "[坎公骑冠剑_送花]", + "4940": "[坎公骑冠剑_哇]", + "4941": "[坎公骑冠剑_晚安]", + "4942": "[坎公骑冠剑_妖妖灵]", + "4948": "[仙王_salute]", + "4949": "[仙王_啊这]", + "4950": "[仙王_感动]", + "4951": "[仙王_害怕]", + "4952": "[仙王_害羞]", + "4953": "[仙王_略略略]", + "4954": "[仙王_揉]", + "4955": "[仙王_闪亮]", + "4956": "[仙王_无奈]", + "4957": "[仙王_自信回头]", + "4958": "[恋乃夜舞_kira]", + "4959": "[恋乃夜舞_pero]", + "4960": "[恋乃夜舞_yuyuyu]", + "4961": "[恋乃夜舞_感情]", + "4962": "[恋乃夜舞_技巧]", + "4963": "[恋乃夜舞_干杯]", + "4964": "[恋乃夜舞_害怕]", + "4965": "[恋乃夜舞_好耶]", + "4966": "[恋乃夜舞_哭]", + "4967": "[恋乃夜舞_困]", + "4968": "[恋乃夜舞_骂倒]", + "4969": "[恋乃夜舞_束缚]", + "4970": "[恋乃夜舞_天才]", + "4971": "[恋乃夜舞_笑]", + "4972": "[恋乃夜舞_心]", + "4974": "[MEIKO_Hi]", + "4975": "[MEIKO_YES]", + "4976": "[MEIKO_安静]", + "4977": "[MEIKO_抱抱]", + "4978": "[MEIKO_冲鸭]", + "4979": "[MEIKO_达咩]", + "4980": "[MEIKO_好的]", + "4981": "[MEIKO_坏笑]", + "4982": "[MEIKO_加油]", + "4983": "[MEIKO_就这]", + "4984": "[MEIKO_困扰]", + "4985": "[MEIKO_您请]", + "4986": "[MEIKO_神气]", + "4987": "[MEIKO_无奈叹气]", + "4988": "[MEIKO_震惊]", + "4989": "[爸妈来自二次元_NO]", + "4990": "[爸妈来自二次元_拜托拜托]", + "4991": "[爸妈来自二次元_暴击]", + "4992": "[爸妈来自二次元_大佬]", + "4993": "[爸妈来自二次元_点赞]", + "4994": "[爸妈来自二次元_泪眼汪汪]", + "4995": "[爸妈来自二次元_懵]", + "4996": "[爸妈来自二次元_你好]", + "4997": "[爸妈来自二次元_你好鸭]", + "4998": "[爸妈来自二次元_生气]", + "4999": "[爸妈来自二次元_委屈]", + "5000": "[爸妈来自二次元_问号]", + "5001": "[爸妈来自二次元_无语]", + "5002": "[爸妈来自二次元_在吗]", + "5003": "[爸妈来自二次元_震惊]", + "5005": "[中华字库_丑拒]", + "5006": "[中华字库_催更]", + "5007": "[中华字库_达咩]", + "5008": "[中华字库_好耶]", + "5009": "[中华字库_娇羞]", + "5010": "[中华字库_裂开]", + "5011": "[中华字库_溜了溜了]", + "5012": "[中华字库_前方高能]", + "5013": "[中华字库_闪亮登场]", + "5014": "[中华字库_哇]", + "5015": "[中华字库_呜呜呜]", + "5016": "[中华字库_无语]", + "5017": "[中华字库_下次一定]", + "5018": "[中华字库_一键三连]", + "5019": "[中华字库_疑问]", + "5020": "[铃宫铃_mua]", + "5021": "[铃宫铃_奥里给]", + "5022": "[铃宫铃_病弱]", + "5023": "[铃宫铃_叉出去]", + "5024": "[铃宫铃_臭臭]", + "5025": "[铃宫铃_达咩]", + "5026": "[铃宫铃_好耶]", + "5027": "[铃宫铃_精神抖擞]", + "5028": "[铃宫铃_救救铃宝]", + "5029": "[铃宫铃_摸了]", + "5030": "[铃宫铃_你不单推]", + "5031": "[铃宫铃_让我康康]", + "5032": "[铃宫铃_天才]", + "5033": "[铃宫铃_香香]", + "5034": "[铃宫铃_血压]", + "5038": "[奥特曼初升之光_salute]", + "5039": "[奥特曼初升之光_不愧是我]", + "5040": "[奥特曼初升之光_大声呼喊]", + "5041": "[奥特曼初升之光_给我投币]", + "5042": "[奥特曼初升之光_好耶]", + "5043": "[奥特曼初升之光_夸我]", + "5044": "[奥特曼初升之光_愣住]", + "5045": "[奥特曼初升之光_两万年]", + "5046": "[奥特曼初升之光_哦斯]", + "5047": "[奥特曼初升之光_失忆]", + "5048": "[奥特曼初升之光_为你点赞]", + "5049": "[奥特曼初升之光_我不服]", + "5050": "[奥特曼初升之光_嘻嘻]", + "5051": "[奥特曼初升之光_吓死]", + "5052": "[奥特曼初升之光_指定能行]", + "5053": "[雪狐桑_rua]", + "5054": "[雪狐桑_嗷呜]", + "5055": "[雪狐桑_保护]", + "5056": "[雪狐桑_不可爱]", + "5057": "[雪狐桑_吃桃]", + "5058": "[雪狐桑_大受震撼]", + "5059": "[雪狐桑_给你一拳]", + "5060": "[雪狐桑_好棒]", + "5061": "[雪狐桑_惊]", + "5062": "[雪狐桑_快逃]", + "5063": "[雪狐桑_没钱]", + "5064": "[雪狐桑_摸鱼]", + "5065": "[雪狐桑_生气]", + "5066": "[雪狐桑_躺平]", + "5067": "[雪狐桑_吸欧气]", + "5068": "[雪狐桑_屑]", + "5069": "[雪狐桑_心碎]", + "5070": "[雪狐桑_心虚]", + "5071": "[雪狐桑_学习]", + "5072": "[雪狐桑_疑问]", + "5073": "[萌宠小熊_抱抱]", + "5074": "[萌宠小熊_吃了你]", + "5075": "[萌宠小熊_吃甜食]", + "5076": "[萌宠小熊_冲]", + "5077": "[萌宠小熊_打瞌睡]", + "5078": "[萌宠小熊_脸红]", + "5079": "[萌宠小熊_摔]", + "5080": "[萌宠小熊_跳舞]", + "5081": "[萌宠小熊_贴贴]", + "5082": "[萌宠小熊_委屈屈]", + "5083": "[萌宠小熊_我酸了]", + "5084": "[萌宠小熊_小熊无语]", + "5085": "[萌宠小熊_一熊抗所有]", + "5086": "[萌宠小熊_又胖了]", + "5087": "[萌宠小熊_又瘦了]", + "5089": "[伊万_暴走]", + "5090": "[伊万_不屑]", + "5091": "[伊万_馋]", + "5092": "[伊万_吹笛]", + "5093": "[伊万_打嗝]", + "5094": "[伊万_别急]", + "5095": "[伊万_得意]", + "5096": "[伊万_吨吨吨]", + "5097": "[伊万_感动]", + "5098": "[伊万_贱笑]", + "5099": "[伊万_骄傲]", + "5100": "[伊万_牛蛙]", + "5101": "[伊万_破防]", + "5102": "[伊万_亲亲]", + "5103": "[伊万_无语]", + "5104": "[伊万_希望没事]", + "5105": "[伊万_先辈叫]", + "5106": "[伊万_笑着哭]", + "5107": "[伊万_震惊]", + "5108": "[伊万_做坏事]", + "5133": "[永雏塔菲_NO喵!]", + "5134": "[永雏塔菲_不理你了]", + "5135": "[永雏塔菲_嘲笑]", + "5136": "[永雏塔菲_喵喵拳]", + "5137": "[永雏塔菲_对呀对呀]", + "5138": "[永雏塔菲_尴尬]", + "5139": "[永雏塔菲_哈哈哈]", + "5140": "[永雏塔菲_好热]", + "5141": "[永雏塔菲_呼呼喵]", + "5142": "[永雏塔菲_开派对咯]", + "5143": "[永雏塔菲_累]", + "5144": "[永雏塔菲_令人兴奋]", + "5145": "[永雏塔菲_摸头]", + "5146": "[永雏塔菲_亲嘴]", + "5147": "[永雏塔菲_闪亮登场]", + "5148": "[永雏塔菲_生日快乐]", + "5149": "[永雏塔菲_太好吃了]", + "5150": "[永雏塔菲_我帅吗]", + "5151": "[永雏塔菲_嘻嘻喵]", + "5152": "[永雏塔菲_星星眼]", + "5153": "[永雏塔菲_疑惑]", + "5154": "[永雏塔菲_嘤嘤嘤]", + "5155": "[永雏塔菲_有鬼]", + "5156": "[永雏塔菲_晕了]", + "5157": "[永雏塔菲_震惊]", + "5158": "[冬日旅行_踩点]", + "5159": "[冬日旅行_多穿点]", + "5160": "[冬日旅行_好冷]", + "5161": "[冬日旅行_好漂亮]", + "5162": "[冬日旅行_寂寞]", + "5163": "[冬日旅行_看攻略]", + "5164": "[冬日旅行_暖乎]", + "5165": "[冬日旅行_爬山]", + "5166": "[冬日旅行_芜湖]", + "5167": "[冬日旅行_这边请]", + "5168": "[跨年_2022]", + "5169": "[跨年_大吉大利]", + "5170": "[跨年_发红包啦]", + "5171": "[跨年_放假咯]", + "5172": "[跨年_放烟花]", + "5173": "[跨年_锦鲤附体]", + "5174": "[跨年_欧气满满]", + "5175": "[跨年_柿柿如意]", + "5176": "[跨年_新年暴富]", + "5177": "[跨年_新年冲鸭]", + "5178": "[秋日美食_大闸蟹]", + "5179": "[秋日美食_桂花糕]", + "5180": "[秋日美食_菊花茶]", + "5181": "[秋日美食_烤红薯]", + "5182": "[秋日美食_南瓜]", + "5183": "[秋日美食_烤秋刀鱼]", + "5184": "[秋日美食_秋梨]", + "5185": "[秋日美食_秋笋]", + "5186": "[秋日美食_柿子]", + "5187": "[秋日美食_糖炒栗子]", + "5188": "[圣诞_放礼花]", + "5189": "[圣诞_惊喜]", + "5190": "[圣诞_溜了]", + "5191": "[圣诞_惬意]", + "5192": "[圣诞_圣诞快乐]", + "5193": "[圣诞_探头]", + "5194": "[圣诞_贴贴]", + "5195": "[圣诞_哇]", + "5196": "[圣诞_下雪了]", + "5197": "[圣诞_要礼物]", + "5198": "[中老年人_称赞]", + "5199": "[中老年人_干杯]", + "5200": "[中老年人_宁静致远]", + "5201": "[中老年人_认识你真好]", + "5202": "[中老年人_谢谢]", + "5203": "[中老年人_一生平安]", + "5204": "[中老年人_早上好]", + "5205": "[中老年人_珍惜朋友]", + "5206": "[中老年人_珍惜缘分]", + "5207": "[中老年人_祝福你]", + "5208": "[猫灵相册_阿巴阿巴]", + "5209": "[猫灵相册_爱你]", + "5210": "[猫灵相册_不行]", + "5211": "[猫灵相册_馋]", + "5212": "[猫灵相册_冲]", + "5213": "[猫灵相册_哈喽]", + "5214": "[猫灵相册_好呀]", + "5215": "[猫灵相册_好耶]", + "5216": "[猫灵相册_加油]", + "5217": "[猫灵相册_就是你]", + "5218": "[猫灵相册_猫起来]", + "5219": "[猫灵相册_委屈]", + "5220": "[猫灵相册_再见]", + "5221": "[猫灵相册_赞]", + "5222": "[猫灵相册_真的吗]", + "5223": "[姬拉Kira_awsl]", + "5224": "[姬拉Kira_rua]", + "5225": "[姬拉Kira_啊啊啊]", + "5226": "[姬拉Kira_冲鸭]", + "5227": "[姬拉Kira_点火]", + "5228": "[姬拉Kira_充满力量]", + "5229": "[姬拉Kira_嘟嘴]", + "5230": "[姬拉Kira_对不起]", + "5231": "[姬拉Kira_给我砸]", + "5232": "[姬拉Kira_憨]", + "5233": "[姬拉Kira_好耶]", + "5234": "[姬拉Kira_啾咪]", + "5235": "[姬拉Kira_理直气壮]", + "5236": "[姬拉Kira_那我呢]", + "5237": "[姬拉Kira_哦是吗]", + "5238": "[姬拉Kira_叹气]", + "5239": "[姬拉Kira_晚安]", + "5240": "[姬拉Kira_我歇了]", + "5241": "[姬拉Kira_要抱抱]", + "5242": "[姬拉Kira_疑惑]", + "5263": "[草莓大福_么么]", + "5244": "[草莓大福_亲亲]", + "5260": "[草莓大福_喜欢]", + "5259": "[草莓大福_嘻嘻]", + "5245": "[草莓大福_嘿嘿]", + "5261": "[草莓大福_小心心]", + "5246": "[草莓大福_开心]", + "5247": "[草莓大福_惊讶]", + "5248": "[草莓大福_我也爱你]", + "5249": "[草莓大福_棒棒]", + "5250": "[草莓大福_比心]", + "5251": "[草莓大福_流泪]", + "5252": "[草莓大福_激动]", + "5253": "[草莓大福_爆哭]", + "5254": "[草莓大福_爱你]", + "5255": "[草莓大福_给你花花]", + "5262": "[草莓大福_耶]", + "5256": "[草莓大福_茫然]", + "5257": "[草莓大福_贴贴]", + "5258": "[草莓大福_飞飞]", + "5265": "[银杏鎏金_6]", + "5266": "[银杏鎏金_彩虹屁]", + "5267": "[银杏鎏金_巅峰]", + "5268": "[银杏鎏金_记住了]", + "5269": "[银杏鎏金_可怜巴巴]", + "5270": "[银杏鎏金_乐开花]", + "5271": "[银杏鎏金_期待]", + "5272": "[银杏鎏金_幸运喷雾]", + "5273": "[银杏鎏金_欧皇再现]", + "5274": "[银杏鎏金_欧里欧气]", + "5301": "[萌妻食神2_HI]", + "5302": "[萌妻食神2_不要嘛]", + "5303": "[萌妻食神2_不愿再爱]", + "5304": "[萌妻食神2_加油]", + "5305": "[萌妻食神2_开森]", + "5306": "[萌妻食神2_你说得对]", + "5307": "[萌妻食神2_是心动鸭]", + "5308": "[萌妻食神2_死亡凝视]", + "5309": "[萌妻食神2_真香]", + "5310": "[萌妻食神2_只想休息]", + "5311": "[狼队_......]", + "5312": "[狼队_666]", + "5313": "[狼队_嗷呜]", + "5314": "[狼队_唱歌]", + "5315": "[狼队_刺痛我]", + "5316": "[狼队_害羞]", + "5317": "[狼队_加油]", + "5318": "[狼队_哭]", + "5319": "[狼队_狼队]", + "5320": "[狼队_让我看看]", + "5321": "[狼队_生而无畏]", + "5322": "[狼队_芜湖起飞]", + "5323": "[狼队_想我了莓]", + "5324": "[狼队_友谊干杯]", + "5325": "[狼队_自恋]", + "5326": "[AG超玩会_6]", + "5327": "[AG超玩会_保GP]", + "5328": "[AG超玩会_电竞百灵鸟]", + "5329": "[AG超玩会_啾咪啾咪]", + "5330": "[AG超玩会_哭哭]", + "5331": "[AG超玩会_老父亲]", + "5332": "[AG超玩会_欧皇]", + "5333": "[AG超玩会_唐僧]", + "5334": "[AG超玩会_五杀没喽]", + "5335": "[AG超玩会_向日葵]", + "5336": "[AG超玩会_小明无敌]", + "5337": "[AG超玩会_久诚]", + "5338": "[AG超玩会_易烊千北]", + "5339": "[AG超玩会_有问题吗?]", + "5340": "[AG超玩会_怎么gian的]", + "5341": "[星有野_挨巴掌]", + "5342": "[星有野_暗中观察]", + "5343": "[星有野_吃柠檬]", + "5344": "[星有野_催更]", + "5345": "[星有野_打call]", + "5346": "[星有野_鸽了]", + "5347": "[星有野_狗头]", + "5348": "[星有野_画不完了]", + "5349": "[星有野_毁灭吧]", + "5350": "[星有野_裂开]", + "5351": "[星有野_麻了]", + "5352": "[星有野_目瞪狗呆]", + "5353": "[星有野_你真棒]", + "5354": "[星有野_亲亲]", + "5355": "[星有野_社会人]", + "5356": "[星有野_躺床玩手机]", + "5357": "[星有野_铁咩]", + "5358": "[星有野_投币]", + "5359": "[星有野_优雅喝茶]", + "5360": "[星有野_真诚]", + "5361": "[烈火浇愁_比心]", + "5362": "[烈火浇愁_玩火]", + "5363": "[烈火浇愁_抓狂]", + "5364": "[烈火浇愁_迷惑]", + "5365": "[烈火浇愁_摸头]", + "5366": "[烈火浇愁_偷笑]", + "5367": "[烈火浇愁_达成共识]", + "5368": "[烈火浇愁_围观]", + "5369": "[烈火浇愁_欢迎]", + "5370": "[烈火浇愁_怕怕]", + "5371": "[未来有你_5周年]", + "5372": "[未来有你_Nooo]", + "5373": "[未来有你_SOS]", + "5374": "[未来有你_打call]", + "5375": "[未来有你_登场]", + "5376": "[未来有你_好耶]", + "5377": "[未来有你_热情]", + "5378": "[未来有你_生闷气]", + "5379": "[未来有你_酸了]", + "5380": "[未来有你_头晕晕]", + "5381": "[未来有你_未来有你]", + "5382": "[未来有你_真的么]", + "5383": "[未来有你_走花路]", + "5384": "[未来有你_走了]", + "5385": "[未来有你_委屈]", + "5386": "[奈姬niki_问号]", + "5387": "[奈姬niki_绷不住了]", + "5388": "[奈姬niki_愤怒]", + "5389": "[奈姬niki_节目效果]", + "5390": "[奈姬niki_困了]", + "5391": "[奈姬niki_裂开]", + "5392": "[奈姬niki_流汗]", + "5393": "[奈姬niki_满足]", + "5394": "[奈姬niki_吐舌头]", + "5395": "[奈姬niki_危]", + "5396": "[奈姬niki_微笑]", + "5397": "[奈姬niki_委屈]", + "5398": "[奈姬niki_我超]", + "5399": "[奈姬niki_无语]", + "5400": "[奈姬niki_谢谢]", + "5407": "[花花雪精灵_被击中]", + "5408": "[花花雪精灵_吃柠檬]", + "5409": "[花花雪精灵_地铁手机]", + "5410": "[花花雪精灵_鹅鹅鹅]", + "5411": "[花花雪精灵_好耶]", + "5412": "[花花雪精灵_花门]", + "5413": "[花花雪精灵_看戏]", + "5414": "[花花雪精灵_可恶]", + "5415": "[花花雪精灵_狂喜]", + "5416": "[花花雪精灵_泪目]", + "5417": "[花花雪精灵_愣住]", + "5418": "[花花雪精灵_砸雪球]", + "5419": "[花花雪精灵_摘花]", + "5420": "[花花雪精灵_针不戳]", + "5421": "[花花雪精灵_真香]", + "5422": "[有栖mana_kyunkyun]", + "5423": "[有栖mana_悲伤]", + "5424": "[有栖mana_不许DD]", + "5425": "[有栖mana_不要]", + "5426": "[有栖mana_吃饭]", + "5427": "[有栖mana_打call]", + "5428": "[有栖mana_哈哈哈]", + "5429": "[有栖mana_喝可乐]", + "5430": "[有栖mana_嘿嘿嘿嘿]", + "5431": "[有栖mana_看戏]", + "5432": "[有栖mana_你好]", + "5433": "[有栖mana_去吧狐火]", + "5434": "[有栖mana_什么?]", + "5435": "[有栖mana_太累了]", + "5436": "[有栖mana_晚安]", + "5437": "[有栖mana_我爱你]", + "5438": "[有栖mana_箱中狐狸]", + "5439": "[有栖mana_辛苦了]", + "5440": "[有栖mana_炸毛]", + "5441": "[有栖mana_做什么?]", + "5442": "[乃琳Queen_MUA!]", + "5443": "[乃琳Queen_Wink]", + "5444": "[乃琳Queen_壁咚]", + "5445": "[乃琳Queen_标记]", + "5446": "[乃琳Queen_吃火锅]", + "5447": "[乃琳Queen_鼓掌]", + "5448": "[乃琳Queen_好坏女人]", + "5449": "[乃琳Queen_惊吓小乃琳]", + "5450": "[乃琳Queen_麻了]", + "5451": "[乃琳Queen_摸摸]", + "5452": "[乃琳Queen_乃琳摇]", + "5453": "[乃琳Queen_奶淇琳宝]", + "5454": "[乃琳Queen_女王的肯定]", + "5455": "[乃琳Queen_欧耶]", + "5456": "[乃琳Queen_取证中]", + "5457": "[乃琳Queen_双向奔赴]", + "5458": "[乃琳Queen_太喜欢了]", + "5459": "[乃琳Queen_晚安小乃琳]", + "5460": "[乃琳Queen_我们是]", + "5461": "[乃琳Queen_向日葵]", + "5462": "[乃琳Queen_小狐狸]", + "5463": "[乃琳Queen_笑嘻了]", + "5464": "[乃琳Queen_谢谢大家]", + "5465": "[乃琳Queen_隐藏gamer]", + "5466": "[乃琳Queen_遇到困难]", + "5467": "[小缘_?]", + "5468": "[小缘_啵啵]", + "5469": "[小缘_不行]", + "5470": "[小缘_吃瓜]", + "5471": "[小缘_打call]", + "5472": "[小缘_干杯]", + "5473": "[小缘_给心心]", + "5474": "[小缘_害羞]", + "5475": "[小缘_好耶]", + "5476": "[小缘_哼]", + "5477": "[小缘_滑稽]", + "5478": "[小缘_泪目]", + "5479": "[小缘_累了]", + "5480": "[小缘_愣住]", + "5481": "[小缘_挠头]", + "5482": "[小缘_完全ok]", + "5483": "[小缘_晚安]", + "5484": "[小缘_嫌弃]", + "5485": "[小缘_星星眼]", + "5486": "[小缘_自信]", + "5487": "[蓝色时期_问号]", + "5488": "[蓝色时期_大口吃饭]", + "5489": "[蓝色时期_大叶の肯定]", + "5490": "[蓝色时期_盯]", + "5491": "[蓝色时期_飞吻]", + "5492": "[蓝色时期_赶稿ing]", + "5493": "[蓝色时期_感动]", + "5494": "[蓝色时期_干杯]", + "5495": "[蓝色时期_好耶]", + "5496": "[蓝色时期_慌张]", + "5497": "[蓝色时期_教我]", + "5498": "[蓝色时期_脸红红]", + "5499": "[蓝色时期_呐喊]", + "5500": "[蓝色时期_若有所思]", + "5501": "[蓝色时期_瑟瑟发抖]", + "5502": "[蓝色时期_嫌弃]", + "5503": "[蓝色时期_小天使]", + "5504": "[蓝色时期_星星眼]", + "5505": "[蓝色时期_嘘]", + "5506": "[蓝色时期_拽]", + "5507": "[逍遥散人_比刺刺]", + "5508": "[逍遥散人_勾引一下]", + "5509": "[逍遥散人_好的]", + "5510": "[逍遥散人_金毛乐乐]", + "5511": "[逍遥散人_哭唧唧]", + "5512": "[逍遥散人_嘛玩意]", + "5513": "[逍遥散人_奈斯]", + "5514": "[逍遥散人_撒子]", + "5515": "[逍遥散人_我要谈恋爱]", + "5516": "[逍遥散人_勇敢刺刺]", + "5517": "[镜音铃·连_Len的凝视]", + "5518": "[镜音铃·连_坚定]", + "5519": "[镜音铃·连_震惊]", + "5520": "[镜音铃·连_送花]", + "5521": "[镜音铃·连_捂嘴笑]", + "5522": "[镜音铃·连_嘿哈]", + "5523": "[镜音铃·连_期待]", + "5524": "[镜音铃·连_Rin的凝视]", + "5525": "[镜音铃·连_抱]", + "5526": "[镜音铃·连_比心]", + "5527": "[镜音铃·连_干杯]", + "5528": "[镜音铃·连_挥挥]", + "5529": "[镜音铃·连_开心]", + "5530": "[镜音铃·连_拍拍]", + "5531": "[镜音铃·连_汗]", + "5532": "[蔬菜精灵_em]", + "5533": "[蔬菜精灵_hello]", + "5534": "[蔬菜精灵_啊]", + "5535": "[蔬菜精灵_爱你]", + "5536": "[蔬菜精灵_暗中观察]", + "5537": "[蔬菜精灵_大佬]", + "5538": "[蔬菜精灵_好]", + "5539": "[蔬菜精灵_无语]", + "5540": "[蔬菜精灵_谢谢]", + "5541": "[蔬菜精灵_有内鬼]", + "5542": "[珈乐Carol_Come on]", + "5543": "[珈乐Carol_OK]", + "5544": "[珈乐Carol_Vocal担当]", + "5545": "[珈乐Carol_啵啵]", + "5546": "[珈乐Carol_不懂二次元]", + "5547": "[珈乐Carol_草莓袜子]", + "5548": "[珈乐Carol_大哥]", + "5549": "[珈乐Carol_二甲]", + "5550": "[珈乐Carol_反诈骗]", + "5551": "[珈乐Carol_风情]", + "5552": "[珈乐Carol_富贵儿]", + "5553": "[珈乐Carol_隔岸]", + "5554": "[珈乐Carol_红色高跟鞋]", + "5555": "[珈乐Carol_皇珈骑士]", + "5556": "[珈乐Carol_决明子]", + "5557": "[珈乐Carol_哭哭]", + "5558": "[珈乐Carol_酷盖]", + "5559": "[珈乐Carol_狼牙土豆拳]", + "5560": "[珈乐Carol_睡了睡了]", + "5561": "[珈乐Carol_投降]", + "5562": "[珈乐Carol_团播像坐牢]", + "5563": "[珈乐Carol_我还有牌]", + "5564": "[珈乐Carol_我们是]", + "5565": "[珈乐Carol_小狼堡]", + "5566": "[珈乐Carol_小狼公主]", + "5567": "[洛天依_+1]", + "5568": "[洛天依_……]", + "5569": "[洛天依_GKD]", + "5570": "[洛天依_啊!]", + "5571": "[洛天依_棒]", + "5572": "[洛天依_冲鸭]", + "5573": "[洛天依_干饭]", + "5574": "[洛天依_击掌]", + "5575": "[洛天依_集合]", + "5576": "[洛天依_拉钩]", + "5577": "[洛天依_晚安]", + "5578": "[洛天依_呜呜]", + "5579": "[洛天依_许愿]", + "5580": "[洛天依_糟了]", + "5581": "[洛天依_周年快乐]", + "7258": "[洛天依_超凶]", + "7259": "[洛天依_打call ]", + "7260": "[洛天依_饿龙咆哮]", + "7261": "[洛天依_感动]", + "7262": "[洛天依_礼物]", + "7263": "[洛天依_麻了]", + "7264": "[洛天依_摸鱼]", + "7265": "[洛天依_强颜欢笑]", + "7266": "[洛天依_问号]", + "7267": "[洛天依_最喜欢你了]", + "5586": "[幻塔_啊这]", + "5587": "[幻塔_哎哟]", + "5588": "[幻塔_挨打]", + "5589": "[幻塔_抱抱]", + "5590": "[幻塔_吃瓜]", + "5592": "[幻塔_发呆]", + "5594": "[幻塔_佛系]", + "5595": "[幻塔_害怕]", + "5596": "[幻塔_害羞]", + "5597": "[幻塔_好耶]", + "5598": "[幻塔_啾啾]", + "5599": "[幻塔_就是你]", + "5600": "[幻塔_卖萌]", + "5601": "[幻塔_迷惑]", + "5602": "[幻塔_嘁]", + "5603": "[幻塔_睡眠]", + "5604": "[幻塔_问号]", + "5605": "[幻塔_呜呜]", + "5606": "[幻塔_吓]", + "5607": "[幻塔_血压]", + "5608": "[幻塔_自拍]", + "5609": "[幻塔_摸头]", + "5610": "[幻塔_微笑]", + "5611": "[幻塔_委屈]", + "5612": "[幻塔_悠闲]", + "5614": "[2022拜年纪_2022]", + "5615": "[2022拜年纪_HAHA]", + "5616": "[2022拜年纪_???]", + "5617": "[2022拜年纪_比心]", + "5618": "[2022拜年纪_不愧是我]", + "5619": "[2022拜年纪_不要啊]", + "5620": "[2022拜年纪_馋]", + "5621": "[2022拜年纪_打call]", + "5622": "[2022拜年纪_瞪大双眼]", + "5623": "[2022拜年纪_放空]", + "5624": "[2022拜年纪_放烟花]", + "5625": "[2022拜年纪_干了这杯]", + "5626": "[2022拜年纪_哈?]", + "5627": "[2022拜年纪_擦擦]", + "5628": "[2022拜年纪_红包拿来]", + "5629": "[2022拜年纪_小脑斧]", + "5630": "[2022拜年纪_哭哭]", + "5631": "[2022拜年纪_捂脸]", + "5632": "[2022拜年纪_恰柠檬]", + "5633": "[2022拜年纪_无情吃瓜]", + "5634": "[2022拜年纪_新年快乐]", + "5635": "[2022拜年纪_眼前一黑]", + "5636": "[2022拜年纪_星星眼]", + "5637": "[2022拜年纪_晕]", + "5638": "[2022拜年纪_你真棒]", + "5639": "[月隐空夜_诶嘿]", + "5640": "[月隐空夜_打工人]", + "5641": "[月隐空夜_咕咕咕]", + "5642": "[月隐空夜_害羞]", + "5643": "[月隐空夜_回家了]", + "5644": "[月隐空夜_开心]", + "5645": "[月隐空夜_流泪]", + "5646": "[月隐空夜_你给路哟]", + "5647": "[月隐空夜_你有问题]", + "5648": "[月隐空夜_欧拉]", + "5649": "[月隐空夜_欧气满满]", + "5650": "[月隐空夜_亲亲]", + "5651": "[月隐空夜_生气]", + "5652": "[月隐空夜_帅哥!]", + "5653": "[月隐空夜_天才]", + "5654": "[月隐空夜_完了]", + "5655": "[月隐空夜_捂脸]", + "5656": "[月隐空夜_下饭]", + "5657": "[月隐空夜_小脸一黑]", + "5658": "[月隐空夜_斩!]", + "5663": "[拜年纪2022_摸鱼]", + "6387": "[张AiNi和老E_ICU]", + "6388": "[张AiNi和老E_showshowway]", + "6389": "[张AiNi和老E_暗中观察]", + "6390": "[张AiNi和老E_冲锋]", + "6391": "[张AiNi和老E_崇拜]", + "6392": "[张AiNi和老E_点赞]", + "6393": "[张AiNi和老E_害羞]", + "6394": "[张AiNi和老E_好耶]", + "6395": "[张AiNi和老E_厚礼蟹]", + "6396": "[张AiNi和老E_鸡泽斯]", + "6397": "[张AiNi和老E_惊讶]", + "6398": "[张AiNi和老E_哭哭]", + "6399": "[张AiNi和老E_李大爷]", + "6400": "[张AiNi和老E_上勾拳]", + "6401": "[张AiNi和老E_生气]", + "6402": "[张AiNi和老E_晚安]", + "6403": "[张AiNi和老E_无语]", + "6404": "[张AiNi和老E_疑惑]", + "6405": "[张AiNi和老E_有猪]", + "6406": "[张AiNi和老E_自信]", + "5679": "[异想少女_Sry]", + "5680": "[异想少女_WINK]", + "5681": "[异想少女_不明真相]", + "5682": "[异想少女_不许动]", + "5683": "[异想少女_好耶]", + "5684": "[异想少女_惊吓]", + "5685": "[异想少女_流泪]", + "5686": "[异想少女_摸鱼]", + "5687": "[异想少女_拇指]", + "5688": "[异想少女_脑内风暴]", + "5689": "[异想少女_无奈]", + "5690": "[异想少女_无语]", + "5691": "[异想少女_无欲无求]", + "5692": "[异想少女_悠闲]", + "5693": "[异想少女_宇宙]", + "5694": "[喵铃铛_抱抱]", + "5695": "[喵铃铛_蹭痒痒]", + "5696": "[喵铃铛_哧溜]", + "5697": "[喵铃铛_干饭喵]", + "5698": "[喵铃铛_猫突猛进]", + "5699": "[喵铃铛_陪我玩]", + "5700": "[喵铃铛_伸懒腰]", + "5701": "[喵铃铛_失去我了]", + "5702": "[喵铃铛_摔]", + "5703": "[喵铃铛_招手]", + "5706": "[查理苏_感谢]", + "5707": "[查理苏_干杯]", + "5708": "[查理苏_开心]", + "5709": "[查理苏_买买买]", + "5710": "[查理苏_你币有了]", + "5711": "[查理苏_捧花]", + "5712": "[查理苏_完美]", + "5713": "[查理苏_晚安]", + "5714": "[查理苏_洗澡]", + "5715": "[查理苏_晕]", + "5716": "[查理苏_疑问]", + "5717": "[查理苏_真香]", + "5718": "[查理苏_震惊]", + "5719": "[查理苏_悬赏]", + "5720": "[查理苏_自恋]", + "5721": "[陆沉_盯]", + "5722": "[陆沉_感谢]", + "5723": "[陆沉_干杯]", + "5724": "[陆沉_喝咖啡]", + "5725": "[陆沉_加油]", + "5726": "[陆沉_开心]", + "5727": "[陆沉_看书]", + "5728": "[陆沉_努力工作]", + "5729": "[陆沉_捧花]", + "5730": "[陆沉_晚安]", + "5731": "[陆沉_陷入沉思]", + "5732": "[陆沉_疑问]", + "5733": "[陆沉_晕]", + "5734": "[陆沉_再来亿遍]", + "5735": "[陆沉_震惊]", + "5751": "[博博鹿虎年装扮_i了i了]", + "5752": "[博博鹿虎年装扮_干杯]", + "5753": "[博博鹿虎年装扮_恭喜发财]", + "5754": "[博博鹿虎年装扮_红包拿来]", + "5755": "[博博鹿虎年装扮_哭了]", + "5756": "[博博鹿虎年装扮_生气]", + "5757": "[博博鹿虎年装扮_送你fafa]", + "5758": "[博博鹿虎年装扮_无语了]", + "5759": "[博博鹿虎年装扮_疑问]", + "5760": "[博博鹿虎年装扮_震惊]", + "5761": "[我的小铺_OK]", + "5762": "[我的小铺_啊]", + "5763": "[我的小铺_比心]", + "5764": "[我的小铺_感谢]", + "5765": "[我的小铺_害羞]", + "5766": "[我的小铺_加油]", + "5767": "[我的小铺_开心]", + "5768": "[我的小铺_新年快乐]", + "5769": "[我的小铺_疑惑]", + "5770": "[我的小铺_在吗]", + "5776": "[巡音流歌_啊咧]", + "5777": "[巡音流歌_哎嘿]", + "5778": "[巡音流歌_抱抱]", + "5779": "[巡音流歌_比心]", + "5780": "[巡音流歌_不愧是我]", + "5781": "[巡音流歌_达咩]", + "5782": "[巡音流歌_对手指]", + "5783": "[巡音流歌_歌唱]", + "5784": "[巡音流歌_好耶]", + "5785": "[巡音流歌_泪眼]", + "5786": "[巡音流歌_趴]", + "5787": "[巡音流歌_生气]", + "5788": "[巡音流歌_摊手]", + "5789": "[巡音流歌_托脸]", + "5790": "[巡音流歌_wink]", + "5791": "[枕边童话_???]", + "5792": "[枕边童话_ok]", + "5793": "[枕边童话_rua]", + "5794": "[枕边童话_欸]", + "5795": "[枕边童话_拜拜]", + "5796": "[枕边童话_抱抱]", + "5797": "[枕边童话_比心]", + "5798": "[枕边童话_比心2]", + "5799": "[枕边童话_吃瓜]", + "5800": "[枕边童话_戳]", + "5801": "[枕边童话_戳2]", + "5802": "[枕边童话_果咩捏]", + "5803": "[枕边童话_哈哈哈]", + "5804": "[枕边童话_嘿嘿嘿]", + "5805": "[枕边童话_气气]", + "5806": "[枕边童话_敲打]", + "5807": "[枕边童话_探头]", + "5808": "[枕边童话_贴贴]", + "5809": "[枕边童话_歪脑]", + "5810": "[枕边童话_歪头]", + "5811": "[枕边童话_晚安]", + "5812": "[枕边童话_委屈巴巴]", + "5813": "[枕边童话_呜呜]", + "5814": "[枕边童话_小米星]", + "5815": "[枕边童话_小绵花]", + "5816": "[崩坏3_你好]", + "5817": "[崩坏3_?]", + "5819": "[崩坏3_差不多得了]", + "5821": "[崩坏3_lay了]", + "5826": "[崩坏3_吃咸鱼]", + "5820": "[崩坏3_七]", + "5822": "[崩坏3_十]", + "5823": "[崩坏3_米]", + "5824": "[崩坏3_长]", + "5825": "[崩坏3_刀]", + "5827": "[崩坏3_给你一拳]", + "5828": "[崩坏3_呼呼呼]", + "5829": "[崩坏3_划水]", + "5830": "[崩坏3_乆]", + "5831": "[崩坏3_卡了]", + "5832": "[崩坏3_哭哭哭]", + "5833": "[崩坏3_乱杀]", + "5834": "[崩坏3_怒]", + "5835": "[崩坏3_睿智]", + "5836": "[崩坏3_送你一朵花]", + "5837": "[梦奇_I]", + "5838": "[梦奇_K]", + "5839": "[梦奇_MONKI]", + "5840": "[梦奇_M]", + "5841": "[梦奇_N]", + "5842": "[梦奇_O]", + "5843": "[梦奇_发呆中]", + "5844": "[梦奇_来贴贴]", + "5845": "[梦奇_睡觉觉]", + "5846": "[梦奇_超开心]", + "5847": "[狼行者_暗中观察]", + "5848": "[狼行者_鄙视]", + "5849": "[狼行者_吃瓜]", + "5850": "[狼行者_打咩]", + "5851": "[狼行者_鸽子]", + "5852": "[狼行者_嘿嘿]", + "5853": "[狼行者_失去欲望]", + "5854": "[狼行者_委屈]", + "5855": "[狼行者_我来啦]", + "5856": "[狼行者_下饭]", + "5866": "[夏鸣星_感谢]", + "5867": "[夏鸣星_干杯]", + "5868": "[夏鸣星_开饭啦]", + "5869": "[夏鸣星_开心]", + "5870": "[夏鸣星_来了来了]", + "5871": "[夏鸣星_捧花]", + "5872": "[夏鸣星_生病]", + "5873": "[夏鸣星_完结撒花]", + "5874": "[夏鸣星_晚安]", + "5875": "[夏鸣星_我可以]", + "5876": "[夏鸣星_希望人没事]", + "5877": "[夏鸣星_疑问]", + "5878": "[夏鸣星_晕]", + "5879": "[夏鸣星_震惊]", + "5880": "[夏鸣星_做法]", + "5881": "[齐司礼_www]", + "5882": "[齐司礼_夺笋]", + "5883": "[齐司礼_感谢]", + "5884": "[齐司礼_干杯]", + "5885": "[齐司礼_好活当赏]", + "5886": "[齐司礼_交稿]", + "5887": "[齐司礼_开心]", + "5888": "[齐司礼_看戏]", + "5889": "[齐司礼_冷漠]", + "5890": "[齐司礼_捧花]", + "5891": "[齐司礼_晚安]", + "5892": "[齐司礼_疑问]", + "5893": "[齐司礼_晕]", + "5894": "[齐司礼_知识增加]", + "5895": "[齐司礼_重画]", + "5896": "[萧逸_比心]", + "5897": "[萧逸_感谢]", + "5898": "[萧逸_干杯]", + "5899": "[萧逸_开心]", + "5900": "[萧逸_猫]", + "5901": "[萧逸_捧花]", + "5902": "[萧逸_燃起来了]", + "5903": "[萧逸_生闷气]", + "5904": "[萧逸_耍酷]", + "5905": "[萧逸_晚安]", + "5906": "[萧逸_我们是冠军]", + "5907": "[萧逸_一键三连]", + "5908": "[萧逸_疑问]", + "5909": "[萧逸_晕]", + "5910": "[萧逸_震惊]", + "5911": "[新月冰冰!_奥利给]", + "5912": "[新月冰冰!_达咩]", + "5913": "[新月冰冰!_对手指]", + "5914": "[新月冰冰!_失魂]", + "5915": "[新月冰冰!_问号]", + "5916": "[新月冰冰!_掀桌]", + "5917": "[新月冰冰!_心脏骤停]", + "5918": "[新月冰冰!_加油]", + "5919": "[新月冰冰!_比心]", + "5920": "[新月冰冰!_不可以色色]", + "5921": "[新月冰冰!_大哭]", + "5922": "[新月冰冰!_单推]", + "5923": "[新月冰冰!_诶嘿]", + "5924": "[新月冰冰!_给花花]", + "5925": "[新月冰冰!_滑稽]", + "5926": "[新月冰冰!_举杯浇愁]", + "5927": "[新月冰冰!_开车]", + "5928": "[新月冰冰!_开心]", + "5929": "[新月冰冰!_起床]", + "5930": "[新月冰冰!_穷醒]", + "5931": "[新月冰冰!_生气]", + "5932": "[新月冰冰!_谢谢老板]", + "5933": "[新月冰冰!_优雅]", + "5934": "[新月冰冰!_早安晚安]", + "5935": "[新月冰冰!_kira]", + "5937": "[KAITO_打call]", + "5938": "[KAITO_唱歌]", + "5939": "[KAITO_吃糕群众]", + "5940": "[KAITO_冻住]", + "5941": "[KAITO_鼓掌]", + "5942": "[KAITO_嗨]", + "5943": "[KAITO_害羞]", + "5944": "[KAITO_看看]", + "5945": "[KAITO_聆听]", + "5946": "[KAITO_抹泪]", + "5947": "[KAITO_哦吼]", + "5948": "[KAITO_期待]", + "5949": "[KAITO_叹气]", + "5950": "[KAITO_心心]", + "5951": "[KAITO_抱抱]", + "5952": "[绊爱第二弹_丧]", + "5953": "[绊爱第二弹_啊呜]", + "5954": "[绊爱第二弹_生气气]", + "5955": "[绊爱第二弹_盯]", + "5956": "[绊爱第二弹_忍耐]", + "5957": "[绊爱第二弹_紧张]", + "5958": "[绊爱第二弹_无能狂怒]", + "5959": "[绊爱第二弹_再见]", + "5960": "[绊爱第二弹_哈?]", + "5961": "[绊爱第二弹_好耶]", + "5962": "[绊爱第二弹_充电]", + "5963": "[绊爱第二弹_晕晕]", + "5964": "[绊爱第二弹_开始了]", + "5965": "[绊爱第二弹_撒花]", + "5966": "[绊爱第二弹_哼]", + "5967": "[绊爱第二弹_酷]", + "5968": "[绊爱第二弹_睡觉]", + "5969": "[绊爱第二弹_吃瓜]", + "5970": "[绊爱第二弹_挑衅]", + "5971": "[绊爱第二弹_悟了]", + "5972": "[Hiiro二周年_kimo]", + "5973": "[Hiiro二周年_啊这]", + "5974": "[Hiiro二周年_俺也一样]", + "5975": "[Hiiro二周年_拜托]", + "5976": "[Hiiro二周年_抱抱]", + "5977": "[Hiiro二周年_吃瓜]", + "5978": "[Hiiro二周年_大的要来了]", + "5979": "[Hiiro二周年_干杯]", + "5980": "[Hiiro二周年_给你一拳]", + "5981": "[Hiiro二周年_好耶]", + "5982": "[Hiiro二周年_坏女人]", + "5983": "[Hiiro二周年_击剑]", + "5984": "[Hiiro二周年_寄]", + "5985": "[Hiiro二周年_就这]", + "5986": "[Hiiro二周年_辣眼睛]", + "5987": "[Hiiro二周年_两眼一mua]", + "5988": "[Hiiro二周年_流口水]", + "5989": "[Hiiro二周年_麻了]", + "5990": "[Hiiro二周年_你急了]", + "5991": "[Hiiro二周年_甜菜]", + "5992": "[Hiiro二周年_我打你啊]", + "5993": "[Hiiro二周年_我急了]", + "5994": "[Hiiro二周年_无语]", + "5995": "[Hiiro二周年_吓]", + "5996": "[Hiiro二周年_两眼一黑]", + "5997": "[千从狩_爱的魔力]", + "5998": "[千从狩_戳]", + "5999": "[千从狩_就这]", + "6000": "[千从狩_困]", + "6001": "[千从狩_忙碌]", + "6002": "[千从狩_让我康康]", + "6003": "[千从狩_嘘]", + "6004": "[千从狩_着迷]", + "6005": "[千从狩_自信]", + "6006": "[千从狩_YES!]", + "6007": "[星宫汐_吃桃]", + "6008": "[星宫汐_八成攻]", + "6009": "[星宫汐_不要指望]", + "6010": "[星宫汐_好耶]", + "6011": "[星宫汐_叉出去]", + "6012": "[星宫汐_泪目]", + "6013": "[星宫汐_没什么事]", + "6014": "[星宫汐_摸鱼]", + "6015": "[星宫汐_企鹅]", + "6016": "[星宫汐_企飞]", + "6017": "[星宫汐_塔塔开]", + "6018": "[星宫汐_贴贴]", + "6019": "[星宫汐_完全懂了]", + "6020": "[星宫汐_问号]", + "6021": "[星宫汐_笑死]", + "6022": "[崩坏学园2_震惊]", + "6023": "[崩坏学园2_记仇]", + "6024": "[崩坏学园2_花痴]", + "6025": "[崩坏学园2_绷住]", + "6026": "[崩坏学园2_绷不住了]", + "6027": "[崩坏学园2_白眼]", + "6028": "[崩坏学园2_满头问号]", + "6029": "[崩坏学园2_流汗]", + "6030": "[崩坏学园2_有一说一]", + "6031": "[崩坏学园2_晚安]", + "6032": "[崩坏学园2_心碎]", + "6033": "[崩坏学园2_就这]", + "6034": "[崩坏学园2_害羞]", + "6035": "[崩坏学园2_好耶]", + "6036": "[崩坏学园2_哭哭]", + "6037": "[崩坏学园2_和善]", + "6038": "[崩坏学园2_吃瓜]", + "6039": "[崩坏学园2_偷瞄]", + "6040": "[崩坏学园2_乖巧]", + "6051": "[崩坏学园2_满头叹号]", + "6041": "[小魔头暴露啦_震惊]", + "6042": "[小魔头暴露啦_疑惑]", + "6043": "[小魔头暴露啦_点赞]", + "6044": "[小魔头暴露啦_比心]", + "6045": "[小魔头暴露啦_哒咩]", + "6046": "[小魔头暴露啦_哇]", + "6047": "[小魔头暴露啦_冲鸭]", + "6048": "[小魔头暴露啦_冒头]", + "6049": "[小魔头暴露啦_傲娇]", + "6050": "[小魔头暴露啦_乖巧]", + "6052": "[叶_黑脸捶桌]", + "6053": "[叶_超大声]", + "6054": "[叶_贴贴]", + "6055": "[叶_礼物谢谢]", + "6056": "[叶_熬夜]", + "6057": "[叶_晚安]", + "6058": "[叶_感动]", + "6059": "[叶_害怕]", + "6060": "[叶_威胁]", + "6061": "[叶_失忆]", + "6062": "[叶_天使之吻]", + "6063": "[叶_大佬]", + "6064": "[叶_哭哭]", + "6065": "[叶_叶问]", + "6066": "[叶_别吵架]", + "6067": "[叶_偷看]", + "6068": "[叶_yeah]", + "6069": "[叶_nice]", + "6070": "[叶_meu]", + "6071": "[叶_瞪]", + "6072": "[扶桑大红花_网抑云]", + "6073": "[扶桑大红花_给你一拳]", + "6074": "[扶桑大红花_端水]", + "6075": "[扶桑大红花_疑惑]", + "6076": "[扶桑大红花_略略略]", + "6077": "[扶桑大红花_生气]", + "6078": "[扶桑大红花_查岗]", + "6079": "[扶桑大红花_有猫饼吧]", + "6080": "[扶桑大红花_无语]", + "6081": "[扶桑大红花_摸鱼]", + "6082": "[扶桑大红花_探头]", + "6083": "[扶桑大红花_拿捏]", + "6084": "[扶桑大红花_抱抱]", + "6085": "[扶桑大红花_戳脸]", + "6086": "[扶桑大红花_我超]", + "6087": "[扶桑大红花_寄]", + "6088": "[扶桑大红花_委屈]", + "6089": "[扶桑大红花_好诶]", + "6090": "[扶桑大红花_啪]", + "6091": "[扶桑大红花_啊对对对]", + "6092": "[扶桑大红花_入脑]", + "6093": "[扶桑大红花_傻乐]", + "6094": "[扶桑大红花_不行]", + "6095": "[扶桑大红花_rua]", + "6096": "[扶桑大红花_mua]", + "6097": "[暹罗猫小豆泥_抱大腿]", + "6098": "[暹罗猫小豆泥_不要]", + "6099": "[暹罗猫小豆泥_呆滞]", + "6100": "[暹罗猫小豆泥_单纯]", + "6101": "[暹罗猫小豆泥_好耶]", + "6102": "[暹罗猫小豆泥_惊讶]", + "6103": "[暹罗猫小豆泥_哭]", + "6104": "[暹罗猫小豆泥_来了]", + "6105": "[暹罗猫小豆泥_呸]", + "6106": "[暹罗猫小豆泥_探头]", + "6107": "[暹罗猫小豆泥_舔]", + "6108": "[暹罗猫小豆泥_投币]", + "6109": "[暹罗猫小豆泥_苦鲁西]", + "6110": "[暹罗猫小豆泥_再见]", + "6111": "[暹罗猫小豆泥_震惊]", + "6112": "[非正式会谈_nice]", + "6113": "[非正式会谈_O式嫌弃]", + "6114": "[非正式会谈_yes]", + "6115": "[非正式会谈_沉迷学习]", + "6116": "[非正式会谈_感到气气]", + "6117": "[非正式会谈_干哈呢]", + "6118": "[非正式会谈_贵妇震惊]", + "6119": "[非正式会谈_你明白吗]", + "6121": "[非正式会谈_贴贴]", + "6122": "[非正式会谈_耶]", + "6123": "[国王排名_告辞]", + "6124": "[国王排名_汗]", + "6125": "[国王排名_嘿嘿]", + "6126": "[国王排名_寂寞]", + "6127": "[国王排名_紧张]", + "6128": "[国王排名_惊]", + "6129": "[国王排名_举臂欢呼]", + "6130": "[国王排名_拉勾]", + "6131": "[国王排名_摸摸]", + "6132": "[国王排名_嗯嗯]", + "6133": "[国王排名_睡不着]", + "6134": "[国王排名_挑眉]", + "6135": "[国王排名_无语]", + "6136": "[国王排名_一起玩耍]", + "6137": "[国王排名_准备好了]", + "6138": "[小可学妹_???]", + "6139": "[小可学妹_爱你]", + "6140": "[小可学妹_不可以哦]", + "6141": "[小可学妹_www]", + "6142": "[小可学妹_馋]", + "6143": "[小可学妹_大哭]", + "6144": "[小可学妹_大笑]", + "6145": "[小可学妹_害怕]", + "6146": "[小可学妹_哼]", + "6147": "[小可学妹_击剑]", + "6148": "[小可学妹_加油呀]", + "6149": "[小可学妹_就这?]", + "6150": "[小可学妹_老板大气]", + "6151": "[小可学妹_太牛啦]", + "6152": "[小可学妹_哇]", + "6153": "[小可学妹_我流汗了]", + "6154": "[小可学妹_下饭]", + "6155": "[小可学妹_一起吗]", + "6156": "[小可学妹_抓住你了]", + "6157": "[小可学妹_MUA]", + "6860": "[小可学妹_别急]", + "6861": "[小可学妹_出生]", + "6862": "[小可学妹_好热]", + "6863": "[小可学妹_两眼一黑]", + "6864": "[小可学妹_妈!]", + "6158": "[Akie秋绘_AWSL]", + "6159": "[Akie秋绘_ZZZ]", + "6160": "[Akie秋绘_暗中观察]", + "6161": "[Akie秋绘_傲娇]", + "6162": "[Akie秋绘_比心]", + "6163": "[Akie秋绘_不是浣熊]", + "6164": "[Akie秋绘_唱歌]", + "6165": "[Akie秋绘_打call]", + "6166": "[Akie秋绘_得意]", + "6167": "[Akie秋绘_都可以氪]", + "6168": "[Akie秋绘_憨憨]", + "6169": "[Akie秋绘_绘公传]", + "6170": "[Akie秋绘_绘熊]", + "6171": "[Akie秋绘_可爱]", + "6172": "[Akie秋绘_哭哭]", + "6173": "[Akie秋绘_让我缓缓]", + "6174": "[Akie秋绘_让我康康]", + "6175": "[Akie秋绘_生气]", + "6176": "[Akie秋绘_涮了涮了]", + "6177": "[Akie秋绘_无情嘲笑]", + "6178": "[Akie秋绘_想peach]", + "6179": "[Akie秋绘_谢谢]", + "6180": "[Akie秋绘_疑惑]", + "6181": "[Akie秋绘_震惊]", + "6182": "[Akie秋绘_智能]", + "6183": "[天涯明月刀伙伴_???]", + "6184": "[天涯明月刀伙伴_比心]", + "6185": "[天涯明月刀伙伴_不币]", + "6186": "[天涯明月刀伙伴_馋]", + "6187": "[天涯明月刀伙伴_吃瓜]", + "6188": "[天涯明月刀伙伴_冲鸭]", + "6190": "[天涯明月刀伙伴_滴汗]", + "6191": "[天涯明月刀伙伴_干杯]", + "6192": "[天涯明月刀伙伴_好起来了]", + "6193": "[天涯明月刀伙伴_击掌]", + "6194": "[天涯明月刀伙伴_锦鲤光环]", + "6195": "[天涯明月刀伙伴_氪]", + "6196": "[天涯明月刀伙伴_麻了]", + "6197": "[天涯明月刀伙伴_牛币]", + "6198": "[天涯明月刀伙伴_破防]", + "6200": "[天涯明月刀伙伴_上班]", + "6201": "[天涯明月刀伙伴_贴贴]", + "6202": "[天涯明月刀伙伴_星星眼]", + "6203": "[天涯明月刀伙伴_宇宙]", + "6204": "[天涯明月刀伙伴_emo]", + "6205": "[绘光计划专属_啊对对对]", + "6206": "[绘光计划专属_算了]", + "6207": "[绘光计划专属_不许搞颜色]", + "6208": "[绘光计划专属_打咩]", + "6209": "[绘光计划专属_饭饭]", + "6210": "[绘光计划专属_咕了]", + "6211": "[绘光计划专属_教我画画]", + "6212": "[绘光计划专属_就要搞颜色]", + "6213": "[绘光计划专属_太强了]", + "6214": "[绘光计划专属_我画我画]", + "6215": "[祖娅纳惜生贺_???]", + "6216": "[祖娅纳惜生贺_啊对对对]", + "6217": "[祖娅纳惜生贺_暗中观察]", + "6218": "[祖娅纳惜生贺_饱了]", + "6219": "[祖娅纳惜生贺_馋]", + "6220": "[祖娅纳惜生贺_打call]", + "6221": "[祖娅纳惜生贺_大笑]", + "6222": "[祖娅纳惜生贺_得意]", + "6223": "[祖娅纳惜生贺_夺笋呐]", + "6224": "[祖娅纳惜生贺_好耶]", + "6225": "[祖娅纳惜生贺_哼]", + "6226": "[祖娅纳惜生贺_就这?]", + "6227": "[祖娅纳惜生贺_懒]", + "6228": "[祖娅纳惜生贺_泪奔]", + "6229": "[祖娅纳惜生贺_没眼看]", + "6230": "[祖娅纳惜生贺_你不对劲]", + "6231": "[祖娅纳惜生贺_恰饭]", + "6232": "[祖娅纳惜生贺_生气]", + "6233": "[祖娅纳惜生贺_太牛啦]", + "6234": "[祖娅纳惜生贺_晚安]", + "6235": "[祖娅纳惜生贺_威胁]", + "6236": "[祖娅纳惜生贺_委屈]", + "6237": "[祖娅纳惜生贺_一键三连]", + "6238": "[祖娅纳惜生贺_晕]", + "6239": "[祖娅纳惜生贺_wink]", + "6241": "[时空之隙_不会吧]", + "6242": "[时空之隙_打工人]", + "6243": "[时空之隙_干饭人]", + "6244": "[时空之隙_干啥啥不行]", + "6245": "[时空之隙_工具人]", + "6246": "[时空之隙_豪横]", + "6247": "[时空之隙_就这]", + "6248": "[时空之隙_抗疫防疫]", + "6249": "[时空之隙_我哭了我装的]", + "6250": "[时空之隙_希望人没事]", + "6251": "[时空之隙_???]", + "6252": "[时空之隙_爷青结]", + "6253": "[时空之隙_有内味了]", + "6254": "[时空之隙_直呼内行]", + "6255": "[时空之隙_duck不必]", + "6256": "[测不准的阿波连同学_ok]", + "6257": "[测不准的阿波连同学_拜托了]", + "6258": "[测不准的阿波连同学_颤颤巍巍]", + "6259": "[测不准的阿波连同学_打嗝]", + "6260": "[测不准的阿波连同学_盯]", + "6261": "[测不准的阿波连同学_发怒]", + "6262": "[测不准的阿波连同学_乖乖]", + "6263": "[测不准的阿波连同学_害怕到哭]", + "6264": "[测不准的阿波连同学_害羞]", + "6265": "[测不准的阿波连同学_好吃]", + "6266": "[测不准的阿波连同学_好厉害]", + "6267": "[测不准的阿波连同学_欢喜]", + "6268": "[测不准的阿波连同学_精疲力尽]", + "6269": "[测不准的阿波连同学_开心]", + "6270": "[测不准的阿波连同学_口袋空空]", + "6271": "[测不准的阿波连同学_难道是?!]", + "6272": "[测不准的阿波连同学_上班第一天]", + "6273": "[测不准的阿波连同学_帅气]", + "6274": "[测不准的阿波连同学_晚安]", + "6275": "[测不准的阿波连同学_喂你]", + "6276": "[测不准的阿波连同学_我是大富豪]", + "6277": "[测不准的阿波连同学_我要报警啦]", + "6278": "[测不准的阿波连同学_眼泪汪汪]", + "6279": "[测不准的阿波连同学_晕]", + "6280": "[测不准的阿波连同学_震惊]", + "6281": "[嘉然2.0_我们是]", + "6282": "[嘉然2.0_啊笑死]", + "6283": "[嘉然2.0_变身]", + "6284": "[嘉然2.0_擦汗]", + "6285": "[嘉然2.0_打盹]", + "6286": "[嘉然2.0_打气]", + "6287": "[嘉然2.0_干饭神]", + "6288": "[嘉然2.0_好好吃饭]", + "6289": "[嘉然2.0_好好好]", + "6290": "[嘉然2.0_嘿嘿嘿嘿]", + "6291": "[嘉然2.0_坏东西]", + "6292": "[嘉然2.0_嘉然Queen]", + "6293": "[嘉然2.0_嘉心糖]", + "6294": "[嘉然2.0_可爱捏]", + "6295": "[嘉然2.0_可是蒂娜我]", + "6296": "[嘉然2.0_累了]", + "6297": "[嘉然2.0_撒娇]", + "6298": "[嘉然2.0_生闷气]", + "6299": "[嘉然2.0_甜甜小草莓]", + "6300": "[嘉然2.0_小天使]", + "6301": "[嘉然2.0_小熊]", + "6302": "[嘉然2.0_早上好]", + "6303": "[嘉然2.0_宅舞]", + "6304": "[嘉然2.0_MUA你一下]", + "6305": "[嘉然2.0_RUN了]", + "6306": "[星瞳_mua]", + "6307": "[星瞳_钓鱼]", + "6308": "[星瞳_给你一拳]", + "6309": "[星瞳_见钱眼开]", + "6310": "[星瞳_敬礼]", + "6311": "[星瞳_口蘑]", + "6312": "[星瞳_流汗]", + "6313": "[星瞳_妈!]", + "6314": "[星瞳_发癫]", + "6315": "[星瞳_扭扭]", + "6316": "[星瞳_生气]", + "6317": "[星瞳_跳舞]", + "6318": "[星瞳_晚安]", + "6319": "[星瞳_呜呜]", + "6320": "[星瞳_谢谢]", + "6321": "[星瞳_星想事成]", + "6322": "[星瞳_咬钩]", + "6323": "[星瞳_咬星星]", + "6324": "[星瞳_耶]", + "6325": "[星瞳_疑问]", + "6382": "[星瞳_爱心]", + "6383": "[星瞳_超级加倍]", + "6384": "[星瞳_放过我吧]", + "6385": "[星瞳_眉笔力量]", + "6386": "[星瞳_小企鹅来咯]", + "6331": "[请吃红小豆吧_吃薯片]", + "6332": "[请吃红小豆吧_发呆]", + "6333": "[请吃红小豆吧_告辞]", + "6334": "[请吃红小豆吧_害羞]", + "6335": "[请吃红小豆吧_好耶]", + "6336": "[请吃红小豆吧_嘿嘿]", + "6337": "[请吃红小豆吧_坏笑]", + "6338": "[请吃红小豆吧_泪目]", + "6339": "[请吃红小豆吧_摸摸头]", + "6340": "[请吃红小豆吧_哇]", + "6341": "[请吃红小豆吧_我不理解]", + "6342": "[请吃红小豆吧_我很好]", + "6343": "[请吃红小豆吧_在吗]", + "6344": "[请吃红小豆吧_糟糕]", + "6345": "[请吃红小豆吧_啧]", + "6346": "[时光代理人周年纪念_比心]", + "6347": "[时光代理人周年纪念_emmm]", + "6348": "[时光代理人周年纪念_salute]", + "6349": "[时光代理人周年纪念_不]", + "6350": "[时光代理人周年纪念_钞能力]", + "6351": "[时光代理人周年纪念_盯]", + "6352": "[时光代理人周年纪念_好耶]", + "6353": "[时光代理人周年纪念_酷]", + "6354": "[时光代理人周年纪念_买买买]", + "6355": "[时光代理人周年纪念_你醒啦]", + "6356": "[时光代理人周年纪念_怒]", + "6357": "[时光代理人周年纪念_期待]", + "6358": "[时光代理人周年纪念_无论过去]", + "6359": "[时光代理人周年纪念_心脏骤停]", + "6360": "[时光代理人周年纪念_一周年快乐]", + "6362": "[屁股鸟_mua]", + "6363": "[屁股鸟_no]", + "6364": "[屁股鸟_yes]", + "6365": "[屁股鸟_比心]", + "6366": "[屁股鸟_馋]", + "6367": "[屁股鸟_冲冲葱]", + "6368": "[屁股鸟_戳]", + "6369": "[屁股鸟_达咩]", + "6370": "[屁股鸟_嗝]", + "6371": "[屁股鸟_咕]", + "6372": "[屁股鸟_就这]", + "6373": "[屁股鸟_泪目]", + "6374": "[屁股鸟_拎起来]", + "6375": "[屁股鸟_冒头]", + "6376": "[屁股鸟_丧]", + "6377": "[屁股鸟_生气]", + "6378": "[屁股鸟_贴贴]", + "6379": "[屁股鸟_推倒]", + "6380": "[屁股鸟_亿点点]", + "6381": "[屁股鸟_元气满满]", + "6408": "[最后的召唤师_报警了]", + "6409": "[最后的召唤师_吃瓜]", + "6410": "[最后的召唤师_吃惊]", + "6411": "[最后的召唤师_冲]", + "6412": "[最后的召唤师_打扰了]", + "6413": "[最后的召唤师_呆滞]", + "6414": "[最后的召唤师_点赞]", + "6415": "[最后的召唤师_盯]", + "6416": "[最后的召唤师_朵拉嘲笑]", + "6417": "[最后的召唤师_惊]", + "6418": "[最后的召唤师_来了]", + "6419": "[最后的召唤师_摸摸]", + "6420": "[最后的召唤师_去吧]", + "6421": "[最后的召唤师_忍耐]", + "6422": "[最后的召唤师_生气]", + "6423": "[最后的召唤师_吐]", + "6424": "[最后的召唤师_问号]", + "6425": "[最后的召唤师_歇歇]", + "6426": "[最后的召唤师_摇尾巴]", + "6427": "[最后的召唤师_一顿操作]", + "6428": "[凹凸世界第二弹_GKD]", + "6429": "[凹凸世界第二弹_不可以噢]", + "6430": "[凹凸世界第二弹_差不多得了]", + "6431": "[凹凸世界第二弹_超凶]", + "6432": "[凹凸世界第二弹_吃糖]", + "6433": "[凹凸世界第二弹_冲呀]", + "6434": "[凹凸世界第二弹_催更]", + "6435": "[凹凸世界第二弹_诶嘿嘿]", + "6436": "[凹凸世界第二弹_好耶]", + "6437": "[凹凸世界第二弹_哼]", + "6438": "[凹凸世界第二弹_哭哭]", + "6439": "[凹凸世界第二弹_来了]", + "6440": "[凹凸世界第二弹_拿来吧你]", + "6441": "[凹凸世界第二弹_问号]", + "6442": "[凹凸世界第二弹_我忍]", + "6443": "[hanser动物套装_no!]", + "6444": "[hanser动物套装_哎呀别]", + "6445": "[hanser动物套装_爱你呀]", + "6446": "[hanser动物套装_抱抱]", + "6447": "[hanser动物套装_打call]", + "6448": "[hanser动物套装_打压]", + "6449": "[hanser动物套装_鼓掌]", + "6450": "[hanser动物套装_好!]", + "6451": "[hanser动物套装_呵弱者]", + "6452": "[hanser动物套装_泪流成河]", + "6453": "[hanser动物套装_妈耶]", + "6454": "[hanser动物套装_满头大憨]", + "6455": "[hanser动物套装_毛怪爱了]", + "6456": "[hanser动物套装_毛怪爆哭]", + "6457": "[hanser动物套装_毛怪鼻血]", + "6458": "[hanser动物套装_毛怪衰老]", + "6459": "[hanser动物套装_毛怪疑惑]", + "6460": "[hanser动物套装_你真可怜]", + "6461": "[hanser动物套装_让你皮]", + "6462": "[hanser动物套装_双马尾的疑惑]", + "6463": "[hanser动物套装_思索]", + "6464": "[hanser动物套装_头疼]", + "6465": "[hanser动物套装_哇哦]", + "6466": "[hanser动物套装_委屈]", + "6467": "[hanser动物套装_呜呼]", + "6468": "[切茜娅Chelsea_啊对对对]", + "6469": "[切茜娅Chelsea_懂不]", + "6470": "[切茜娅Chelsea_关你屁事]", + "6471": "[切茜娅Chelsea_关我屁事]", + "6472": "[切茜娅Chelsea_好怪哦]", + "6473": "[切茜娅Chelsea_急了急了]", + "6474": "[切茜娅Chelsea_啾咪]", + "6475": "[切茜娅Chelsea_救命]", + "6476": "[切茜娅Chelsea_来抱抱]", + "6477": "[切茜娅Chelsea_来点来点]", + "6478": "[切茜娅Chelsea_老婆]", + "6479": "[切茜娅Chelsea_没问题的]", + "6480": "[切茜娅Chelsea_你有事吗]", + "6481": "[切茜娅Chelsea_牛头人]", + "6482": "[切茜娅Chelsea_上个舰长吧]", + "6483": "[切茜娅Chelsea_生日快乐]", + "6484": "[切茜娅Chelsea_生无可恋]", + "6485": "[切茜娅Chelsea_偷偷摸摸]", + "6486": "[切茜娅Chelsea_笑一个]", + "6487": "[切茜娅Chelsea_要哭了哦]", + "6488": "[葛叶_IQ200]", + "6489": "[葛叶_pien]", + "6490": "[葛叶_xiexie]", + "6491": "[葛叶_んぶ]", + "6492": "[葛叶_不可以]", + "6493": "[葛叶_大家好]", + "6494": "[葛叶_飞吻]", + "6495": "[葛叶_牢底坐穿术]", + "6496": "[葛叶_老婆!]", + "6497": "[葛叶_你邪了门]", + "6498": "[葛叶_生气]", + "6499": "[葛叶_嘶]", + "6500": "[葛叶_天气卡组]", + "6501": "[葛叶_想要]", + "6502": "[葛叶_有趣的女人]", + "6503": "[蔬菜精灵第二弹_???]", + "6504": "[蔬菜精灵第二弹_100分]", + "6505": "[蔬菜精灵第二弹_HAHA]", + "6506": "[蔬菜精灵第二弹_NO]", + "6507": "[蔬菜精灵第二弹_OK]", + "6508": "[蔬菜精灵第二弹_YES]", + "6509": "[蔬菜精灵第二弹_暗中观察]", + "6510": "[蔬菜精灵第二弹_拜拜]", + "6511": "[蔬菜精灵第二弹_点赞]", + "6512": "[蔬菜精灵第二弹_夺笋鸭]", + "6513": "[蔬菜精灵第二弹_哈哈哈哈]", + "6514": "[蔬菜精灵第二弹_嗨皮]", + "6515": "[蔬菜精灵第二弹_害怕]", + "6516": "[蔬菜精灵第二弹_害羞]", + "6517": "[蔬菜精灵第二弹_好样的]", + "6518": "[蔬菜精灵第二弹_活下去]", + "6519": "[蔬菜精灵第二弹_卷起来了]", + "6520": "[蔬菜精灵第二弹_哭哭]", + "6521": "[蔬菜精灵第二弹_嗯嗯]", + "6522": "[蔬菜精灵第二弹_咆哮]", + "6523": "[蔬菜精灵第二弹_求收养]", + "6524": "[蔬菜精灵第二弹_撒娇]", + "6525": "[蔬菜精灵第二弹_睡觉]", + "6526": "[蔬菜精灵第二弹_太棒了]", + "6527": "[蔬菜精灵第二弹_哇哦]", + "6528": "[蔬菜精灵第二弹_我爱工作]", + "6529": "[蔬菜精灵第二弹_小辣鸡]", + "6530": "[蔬菜精灵第二弹_歇歇]", + "6531": "[蔬菜精灵第二弹_谢谢]", + "6532": "[蔬菜精灵第二弹_辛苦了]", + "6534": "[狼队第五人格_啊对对对]", + "6535": "[狼队第五人格_嗷呜]", + "6536": "[狼队第五人格_比心]", + "6537": "[狼队第五人格_唱歌]", + "6538": "[狼队第五人格_达咩]", + "6539": "[狼队第五人格_打电话]", + "6540": "[狼队第五人格_断网啦]", + "6541": "[狼队第五人格_狗门]", + "6542": "[狼队第五人格_咕咕鸡起飞]", + "6543": "[狼队第五人格_紧张]", + "6544": "[狼队第五人格_辣眼睛]", + "6545": "[狼队第五人格_我不听]", + "6546": "[狼队第五人格_耶]", + "6547": "[狼队第五人格_以狼之名]", + "6548": "[狼队第五人格_在吗]", + "6549": "[天涯明月刀太白_羡慕]", + "6550": "[天涯明月刀太白_比心]", + "6551": "[天涯明月刀太白_吃瓜]", + "6552": "[天涯明月刀太白_冲鸭]", + "6553": "[天涯明月刀太白_得意]", + "6554": "[天涯明月刀太白_滴汗]", + "6555": "[天涯明月刀太白_干杯]", + "6556": "[天涯明月刀太白_告辞剑法]", + "6557": "[天涯明月刀太白_击掌]", + "6558": "[天涯明月刀太白_锦鲤光环]", + "6559": "[天涯明月刀太白_氪]", + "6560": "[天涯明月刀太白_麻了]", + "6561": "[天涯明月刀太白_破防]", + "6562": "[天涯明月刀太白_闪亮登场]", + "6563": "[天涯明月刀太白_上班]", + "6564": "[天涯明月刀太白_生气]", + "6565": "[天涯明月刀太白_酸]", + "6566": "[天涯明月刀太白_天下第一]", + "6567": "[天涯明月刀太白_问号]", + "6568": "[天涯明月刀太白_赞]", + "6569": "[天涯明月刀唐门_比心]", + "6570": "[天涯明月刀唐门_吃瓜]", + "6571": "[天涯明月刀唐门_冲鸭]", + "6572": "[天涯明月刀唐门_得意]", + "6573": "[天涯明月刀唐门_滴汗]", + "6574": "[天涯明月刀唐门_干杯]", + "6575": "[天涯明月刀唐门_击掌]", + "6576": "[天涯明月刀唐门_锦鲤光环]", + "6577": "[天涯明月刀唐门_氪]", + "6578": "[天涯明月刀唐门_麻了]", + "6579": "[天涯明月刀唐门_破防]", + "6580": "[天涯明月刀唐门_闪亮登场]", + "6581": "[天涯明月刀唐门_上班]", + "6582": "[天涯明月刀唐门_生气]", + "6583": "[天涯明月刀唐门_双人成行]", + "6584": "[天涯明月刀唐门_酸]", + "6585": "[天涯明月刀唐门_天下第一]", + "6586": "[天涯明月刀唐门_问号]", + "6587": "[天涯明月刀唐门_羡慕]", + "6588": "[天涯明月刀唐门_赞]", + "6589": "[天涯明月刀真武_比心]", + "6590": "[天涯明月刀真武_吃瓜]", + "6591": "[天涯明月刀真武_冲鸭]", + "6592": "[天涯明月刀真武_淡定]", + "6593": "[天涯明月刀真武_得意]", + "6594": "[天涯明月刀真武_滴汗]", + "6595": "[天涯明月刀真武_干杯]", + "6596": "[天涯明月刀真武_击掌]", + "6597": "[天涯明月刀真武_锦鲤光环]", + "6598": "[天涯明月刀真武_氪]", + "6599": "[天涯明月刀真武_麻了]", + "6600": "[天涯明月刀真武_破防]", + "6601": "[天涯明月刀真武_闪亮登场]", + "6602": "[天涯明月刀真武_上班]", + "6603": "[天涯明月刀真武_生气]", + "6604": "[天涯明月刀真武_酸]", + "6605": "[天涯明月刀真武_天下第一]", + "6606": "[天涯明月刀真武_问号]", + "6607": "[天涯明月刀真武_羡慕]", + "6608": "[天涯明月刀真武_赞]", + "6609": "[天涯明月刀神威_比心]", + "6610": "[天涯明月刀神威_吃瓜]", + "6611": "[天涯明月刀神威_冲鸭]", + "6612": "[天涯明月刀神威_得意]", + "6613": "[天涯明月刀神威_滴汗]", + "6614": "[天涯明月刀神威_飞了]", + "6615": "[天涯明月刀神威_干杯]", + "6616": "[天涯明月刀神威_击掌]", + "6617": "[天涯明月刀神威_锦鲤光环]", + "6618": "[天涯明月刀神威_氪]", + "6619": "[天涯明月刀神威_麻了]", + "6620": "[天涯明月刀神威_破防]", + "6621": "[天涯明月刀神威_闪亮登场]", + "6622": "[天涯明月刀神威_上班]", + "6623": "[天涯明月刀神威_生气]", + "6624": "[天涯明月刀神威_酸]", + "6625": "[天涯明月刀神威_天下第一]", + "6626": "[天涯明月刀神威_问号]", + "6627": "[天涯明月刀神威_羡慕]", + "6628": "[天涯明月刀神威_赞]", + "6629": "[天涯明月刀丐帮_比心]", + "6630": "[天涯明月刀丐帮_吃瓜]", + "6631": "[天涯明月刀丐帮_冲鸭]", + "6632": "[天涯明月刀丐帮_得意]", + "6633": "[天涯明月刀丐帮_干杯]", + "6634": "[天涯明月刀丐帮_击掌]", + "6635": "[天涯明月刀丐帮_锦鲤光环]", + "6636": "[天涯明月刀丐帮_氪]", + "6637": "[天涯明月刀丐帮_流汗]", + "6638": "[天涯明月刀丐帮_麻了]", + "6639": "[天涯明月刀丐帮_破防]", + "6640": "[天涯明月刀丐帮_闪亮登场]", + "6641": "[天涯明月刀丐帮_上班]", + "6642": "[天涯明月刀丐帮_生气]", + "6643": "[天涯明月刀丐帮_酸]", + "6644": "[天涯明月刀丐帮_天下第一]", + "6645": "[天涯明月刀丐帮_问号]", + "6646": "[天涯明月刀丐帮_羡慕]", + "6647": "[天涯明月刀丐帮_赞]", + "6648": "[天涯明月刀丐帮_醉了]", + "6649": "[天涯明月刀天香_比心]", + "6650": "[天涯明月刀天香_吃瓜]", + "6651": "[天涯明月刀天香_冲鸭]", + "6652": "[天涯明月刀天香_得意]", + "6653": "[天涯明月刀天香_滴汗]", + "6654": "[天涯明月刀天香_干杯]", + "6655": "[天涯明月刀天香_击掌]", + "6656": "[天涯明月刀天香_锦鲤光环]", + "6657": "[天涯明月刀天香_警告]", + "6658": "[天涯明月刀天香_氪]", + "6659": "[天涯明月刀天香_麻了]", + "6660": "[天涯明月刀天香_破防]", + "6661": "[天涯明月刀天香_闪亮登场]", + "6662": "[天涯明月刀天香_上班]", + "6663": "[天涯明月刀天香_生气]", + "6664": "[天涯明月刀天香_酸]", + "6665": "[天涯明月刀天香_天下第一]", + "6666": "[天涯明月刀天香_问号]", + "6667": "[天涯明月刀天香_羡慕]", + "6668": "[天涯明月刀天香_赞]", + "6669": "[天涯明月刀移花_比心]", + "6670": "[天涯明月刀移花_吃瓜]", + "6671": "[天涯明月刀移花_冲鸭]", + "6672": "[天涯明月刀移花_得意]", + "6673": "[天涯明月刀移花_滴汗]", + "6674": "[天涯明月刀移花_干杯]", + "6675": "[天涯明月刀移花_击掌]", + "6676": "[天涯明月刀移花_锦鲤光环]", + "6677": "[天涯明月刀移花_氪]", + "6678": "[天涯明月刀移花_麻了]", + "6679": "[天涯明月刀移花_破防]", + "6680": "[天涯明月刀移花_闪亮登场]", + "6681": "[天涯明月刀移花_上班]", + "6682": "[天涯明月刀移花_生气]", + "6683": "[天涯明月刀移花_酸]", + "6684": "[天涯明月刀移花_天下第一]", + "6685": "[天涯明月刀移花_问号]", + "6686": "[天涯明月刀移花_羡慕]", + "6687": "[天涯明月刀移花_优雅]", + "6688": "[天涯明月刀移花_赞]", + "6689": "[天涯明月刀五毒_比心]", + "6690": "[天涯明月刀五毒_吃瓜]", + "6691": "[天涯明月刀五毒_冲鸭]", + "6692": "[天涯明月刀五毒_得意]", + "6693": "[天涯明月刀五毒_干杯]", + "6694": "[天涯明月刀五毒_击掌]", + "6695": "[天涯明月刀五毒_锦鲤光环]", + "6696": "[天涯明月刀五毒_氪]", + "6697": "[天涯明月刀五毒_流汗]", + "6698": "[天涯明月刀五毒_麻了]", + "6699": "[天涯明月刀五毒_破防]", + "6700": "[天涯明月刀五毒_闪亮登场]", + "6701": "[天涯明月刀五毒_上班]", + "6702": "[天涯明月刀五毒_生气]", + "6703": "[天涯明月刀五毒_酸]", + "6704": "[天涯明月刀五毒_天下第一]", + "6705": "[天涯明月刀五毒_舔]", + "6706": "[天涯明月刀五毒_问号]", + "6707": "[天涯明月刀五毒_羡慕]", + "6708": "[天涯明月刀五毒_赞]", + "6709": "[天涯明月刀神刀_比心]", + "6710": "[天涯明月刀神刀_吃瓜]", + "6711": "[天涯明月刀神刀_冲鸭]", + "6712": "[天涯明月刀神刀_大刀]", + "6713": "[天涯明月刀神刀_得意]", + "6714": "[天涯明月刀神刀_干杯]", + "6715": "[天涯明月刀神刀_汗]", + "6716": "[天涯明月刀神刀_击掌]", + "6717": "[天涯明月刀神刀_锦鲤光环]", + "6718": "[天涯明月刀神刀_氪]", + "6719": "[天涯明月刀神刀_麻了]", + "6720": "[天涯明月刀神刀_破防]", + "6721": "[天涯明月刀神刀_闪亮登场]", + "6722": "[天涯明月刀神刀_上班]", + "6723": "[天涯明月刀神刀_生气]", + "6724": "[天涯明月刀神刀_酸]", + "6725": "[天涯明月刀神刀_天下第一]", + "6726": "[天涯明月刀神刀_问号]", + "6727": "[天涯明月刀神刀_羡慕]", + "6728": "[天涯明月刀神刀_赞]", + "6729": "[天涯明月刀从龙_比心]", + "6730": "[天涯明月刀从龙_吃瓜]", + "6731": "[天涯明月刀从龙_冲鸭]", + "6732": "[天涯明月刀从龙_得意]", + "6733": "[天涯明月刀从龙_滴汗]", + "6734": "[天涯明月刀从龙_干杯]", + "6735": "[天涯明月刀从龙_击掌]", + "6736": "[天涯明月刀从龙_锦鲤光环]", + "6737": "[天涯明月刀从龙_氪]", + "6738": "[天涯明月刀从龙_麻了]", + "6739": "[天涯明月刀从龙_破防]", + "6740": "[天涯明月刀从龙_闪亮登场]", + "6741": "[天涯明月刀从龙_上班]", + "6742": "[天涯明月刀从龙_生气]", + "6743": "[天涯明月刀从龙_酸]", + "6744": "[天涯明月刀从龙_天下第一]", + "6745": "[天涯明月刀从龙_为人民服务]", + "6746": "[天涯明月刀从龙_问号]", + "6747": "[天涯明月刀从龙_羡慕]", + "6748": "[天涯明月刀从龙_赞]", + "6749": "[穆小泠_AWSL]", + "6750": "[穆小泠_DD头子]", + "6751": "[穆小泠_比心]", + "6752": "[穆小泠_吹卡祖笛]", + "6753": "[穆小泠_打嗝]", + "6754": "[穆小泠_单身狗]", + "6755": "[穆小泠_点赞]", + "6756": "[穆小泠_耳翅飞镖]", + "6757": "[穆小泠_咕咕咕]", + "6758": "[穆小泠_哈哈哈]", + "6759": "[穆小泠_哈利路亚]", + "6760": "[穆小泠_憨憨]", + "6761": "[穆小泠_好耶]", + "6762": "[穆小泠_滑稽]", + "6763": "[穆小泠_加班狗]", + "6764": "[穆小泠_口胡]", + "6765": "[穆小泠_牛哇]", + "6766": "[穆小泠_亲亲]", + "6767": "[穆小泠_撒娇]", + "6768": "[穆小泠_贴贴]", + "6769": "[穆小泠_晚安]", + "6770": "[穆小泠_我的鸡]", + "6771": "[穆小泠_辛苦啦]", + "6772": "[穆小泠_疑惑]", + "6773": "[穆小泠_早安]", + "6774": "[君有云_安慰]", + "6775": "[君有云_傲娇]", + "6776": "[君有云_吃瓜]", + "6777": "[君有云_沮丧]", + "6778": "[君有云_看书]", + "6779": "[君有云_让我康康]", + "6780": "[君有云_生气]", + "6781": "[君有云_师姐救我]", + "6782": "[君有云_捅回去]", + "6783": "[君有云_捅你]", + "6784": "[暂停!让我查攻略_阿巴阿巴]", + "6785": "[暂停!让我查攻略_安详]", + "6786": "[暂停!让我查攻略_吃瓜]", + "6787": "[暂停!让我查攻略_大佬]", + "6788": "[暂停!让我查攻略_呆]", + "6789": "[暂停!让我查攻略_就这]", + "6790": "[暂停!让我查攻略_什么]", + "6791": "[暂停!让我查攻略_生气]", + "6792": "[暂停!让我查攻略_贴贴]", + "6793": "[暂停!让我查攻略_呜呜]", + "6794": "[眞白花音_菜]", + "6795": "[眞白花音_打call]", + "6796": "[眞白花音_大头]", + "6797": "[眞白花音_对不起]", + "6798": "[眞白花音_对对对]", + "6799": "[眞白花音_干杯]", + "6800": "[眞白花音_恭喜]", + "6801": "[眞白花音_好吃]", + "6802": "[眞白花音_好耶]", + "6803": "[眞白花音_加油]", + "6804": "[眞白花音_举拳攻击]", + "6805": "[眞白花音_哭哭]", + "6806": "[眞白花音_拿着白菜]", + "6807": "[眞白花音_你好]", + "6808": "[眞白花音_亲]", + "6809": "[眞白花音_认真学习中]", + "6810": "[眞白花音_生气]", + "6811": "[眞白花音_晚安]", + "6812": "[眞白花音_我是天才吧]", + "6813": "[眞白花音_下次还敢]", + "6814": "[眞白花音_现在是什么感觉啊]", + "6815": "[眞白花音_笑脸]", + "6816": "[眞白花音_谢谢]", + "6817": "[眞白花音_星星眼]", + "6818": "[眞白花音_疑问]", + "6820": "[姆明_吹口琴]", + "6821": "[姆明_吃饭了]", + "6822": "[姆明_抱抱]", + "6823": "[姆明_钓鱼]", + "6824": "[姆明_钓鱼2]", + "6825": "[姆明_放风筝]", + "6826": "[姆明_工作快乐]", + "6827": "[姆明_害怕]", + "6828": "[姆明_开心]", + "6829": "[姆明_看报纸]", + "6830": "[姆明_可靠]", + "6831": "[姆明_礼物]", + "6832": "[姆明_溜了]", + "6833": "[姆明_去度假]", + "6834": "[姆明_烧开水]", + "6835": "[姆明_刷墙]", + "6836": "[姆明_思考]", + "6837": "[姆明_躺]", + "6838": "[姆明_晚安]", + "6839": "[姆明_委屈巴巴]", + "6840": "[姆明_我来了]", + "6841": "[姆明_我美吗]", + "6842": "[姆明_下雨]", + "6843": "[姆明_修理]", + "6844": "[姆明_织毛衣]", + "6845": "[喵铃铛轻松一刻_爱你]", + "6846": "[喵铃铛轻松一刻_按摩]", + "6847": "[喵铃铛轻松一刻_拜拜]", + "6848": "[喵铃铛轻松一刻_拜托]", + "6849": "[喵铃铛轻松一刻_点赞]", + "6850": "[喵铃铛轻松一刻_呵呵人类]", + "6851": "[喵铃铛轻松一刻_加油]", + "6852": "[喵铃铛轻松一刻_摸鱼]", + "6853": "[喵铃铛轻松一刻_趴着打游戏]", + "6854": "[喵铃铛轻松一刻_趴着睡觉]", + "6855": "[喵铃铛轻松一刻_无语]", + "6856": "[喵铃铛轻松一刻_吸奶茶]", + "6857": "[喵铃铛轻松一刻_摇屁股]", + "6858": "[喵铃铛轻松一刻_在吗]", + "6859": "[喵铃铛轻松一刻_歇了]", + "6865": "[天官赐福花怜生日快乐_拜托]", + "6866": "[天官赐福花怜生日快乐_吃馒头]", + "6867": "[天官赐福花怜生日快乐_大佬坐姿]", + "6868": "[天官赐福花怜生日快乐_盯]", + "6869": "[天官赐福花怜生日快乐_狐狐生威]", + "6870": "[天官赐福花怜生日快乐_挠头]", + "6871": "[天官赐福花怜生日快乐_勤劳小花]", + "6872": "[天官赐福花怜生日快乐_若鼬所思]", + "6873": "[天官赐福花怜生日快乐_水逆退散]", + "6874": "[天官赐福花怜生日快乐_睡觉了]", + "6875": "[天官赐福花怜生日快乐_送你花花]", + "6876": "[天官赐福花怜生日快乐_探头]", + "6877": "[天官赐福花怜生日快乐_托您的福]", + "6878": "[天官赐福花怜生日快乐_心满意足]", + "6879": "[天官赐福花怜生日快乐_嘘]", + "6880": "[天官赐福花怜生日快乐_在吗在吗]", + "6881": "[天官赐福花怜生日快乐_职业假笑]", + "6882": "[天官赐福花怜生日快乐_追星成功]", + "6883": "[天官赐福花怜生日快乐_自豪]", + "6884": "[天官赐福花怜生日快乐_最靓的仔]", + "7318": "[天官赐福花怜生日快乐_花城生日]", + "7319": "[天官赐福花怜生日快乐_桃花]", + "7320": "[天官赐福花怜生日快乐_贴贴]", + "7321": "[天官赐福花怜生日快乐_谢怜生日]", + "7322": "[天官赐福花怜生日快乐_银蝶]", + "6885": "[茉吱Mojuko_???]", + "6886": "[茉吱Mojuko_啊对对对]", + "6887": "[茉吱Mojuko_抱抱]", + "6888": "[茉吱Mojuko_暴风哭泣 ]", + "6889": "[茉吱Mojuko_比心]", + "6890": "[茉吱Mojuko_啵一口]", + "6891": "[茉吱Mojuko_不准跑]", + "6892": "[茉吱Mojuko_嘚瑟]", + "6893": "[茉吱Mojuko_摸头]", + "6894": "[茉吱Mojuko_求求你]", + "6895": "[茉吱Mojuko_我好了]", + "6896": "[茉吱Mojuko_辛苦了]", + "6897": "[茉吱Mojuko_虚无]", + "6898": "[茉吱Mojuko_糟了]", + "6899": "[茉吱Mojuko_揍你哦]", + "7134": "[茉吱Mojuko_吃草]", + "7135": "[茉吱Mojuko_叉出去]", + "7136": "[茉吱Mojuko_吃瓜]", + "7137": "[茉吱Mojuko_害羞]", + "7138": "[茉吱Mojuko_汗]", + "7139": "[茉吱Mojuko_加班]", + "7140": "[茉吱Mojuko_加油]", + "7141": "[茉吱Mojuko_歇了]", + "7142": "[茉吱Mojuko_开锅]", + "7143": "[茉吱Mojuko_嫌弃]", + "6900": "[约战4_i了i了]", + "6901": "[约战4_不解]", + "6902": "[约战4_干杯]", + "6903": "[约战4_暗中观察 ]", + "6904": "[约战4_烦恼]", + "6905": "[约战4_乖巧]", + "6906": "[约战4_柠檬]", + "6907": "[约战4_全体起立]", + "6908": "[约战4_撒花]", + "6909": "[约战4_贴贴]", + "6910": "[约战4_危]", + "6911": "[约战4_无语]", + "6912": "[约战4_想桃]", + "6913": "[约战4_疑惑]", + "6914": "[约战4_再来亿遍]", + "6915": "[Gon的旱獭_好听]", + "6916": "[Gon的旱獭_?!]", + "6917": "[Gon的旱獭_???]", + "6918": "[Gon的旱獭_好耶]", + "6919": "[Gon的旱獭_喝可乐]", + "6920": "[Gon的旱獭_加油]", + "6921": "[Gon的旱獭_精彩]", + "6922": "[Gon的旱獭_开心开心]", + "6923": "[Gon的旱獭_来了来了]", + "6924": "[Gon的旱獭_梦幻联动]", + "6925": "[Gon的旱獭_三连了]", + "6926": "[Gon的旱獭_上车]", + "6927": "[Gon的旱獭_晚安]", + "6928": "[Gon的旱獭_无语]", + "6929": "[Gon的旱獭_歇了]", + "6930": "[蕾尔娜Leona_崩!]", + "6931": "[蕾尔娜Leona_编!]", + "6932": "[蕾尔娜Leona_才没有]", + "6933": "[蕾尔娜Leona_吹口哨]", + "6934": "[蕾尔娜Leona_恶人]", + "6935": "[蕾尔娜Leona_饿了]", + "6936": "[蕾尔娜Leona_反弹]", + "6937": "[蕾尔娜Leona_好不出来]", + "6938": "[蕾尔娜Leona_好嘛]", + "6939": "[蕾尔娜Leona_好耶]", + "6940": "[蕾尔娜Leona_盒盒]", + "6941": "[蕾尔娜Leona_可爱]", + "6942": "[蕾尔娜Leona_蒙圈]", + "6943": "[蕾尔娜Leona_咆哮]", + "6944": "[蕾尔娜Leona_全都要!]", + "6945": "[蕾尔娜Leona_顺毛]", + "6946": "[蕾尔娜Leona_虚弱]", + "6947": "[蕾尔娜Leona_咬人]", + "6948": "[蕾尔娜Leona_真的会谢]", + "6949": "[蕾尔娜Leona_震惊]", + "6952": "[凡人修仙传_叉出去]", + "6953": "[凡人修仙传_馋了]", + "6954": "[凡人修仙传_遁出红尘]", + "6955": "[凡人修仙传_含泪舔包]", + "6956": "[凡人修仙传_寂寞]", + "6957": "[凡人修仙传_谨慎小心]", + "6958": "[凡人修仙传_就这]", + "6959": "[凡人修仙传_眉头一皱]", + "6960": "[凡人修仙传_念头通达]", + "6961": "[凡人修仙传_请进]", + "6962": "[凡人修仙传_三连]", + "6963": "[凡人修仙传_忘忧丹]", + "6964": "[凡人修仙传_相貌平平]", + "6965": "[凡人修仙传_疑惑]", + "6966": "[凡人修仙传_在下历飞雨]", + "6968": "[奶龙_熬夜]", + "6969": "[奶龙_呆滞]", + "6970": "[奶龙_酸了]", + "6971": "[奶龙_我是小丑]", + "6972": "[奶龙_我知道了]", + "6973": "[奶龙_疑惑]", + "6974": "[奶龙_与你无关]", + "6975": "[奶龙_这是中国]", + "6976": "[奶龙_真香]", + "6977": "[奶龙_歇了]", + "6978": "[向晚2.0_MUA]", + "6979": "[向晚2.0_Spring]", + "6980": "[向晚2.0_比心]", + "6981": "[向晚2.0_变猪喷雾]", + "6982": "[向晚2.0_才女小向晚]", + "6983": "[向晚2.0_迪斯科]", + "6984": "[向晚2.0_点赞]", + "6985": "[向晚2.0_顶碗人]", + "6986": "[向晚2.0_翻跟头]", + "6987": "[向晚2.0_果咩纳塞]", + "6988": "[向晚2.0_凉拌海蜇拳]", + "6989": "[向晚2.0_两眼一黑]", + "6990": "[向晚2.0_美少女的事]", + "6991": "[向晚2.0_木头小向晚]", + "6992": "[向晚2.0_双眼wink]", + "6993": "[向晚2.0_索嗨嗨]", + "6994": "[向晚2.0_通过通过]", + "6995": "[向晚2.0_投降]", + "6996": "[向晚2.0_我们是]", + "6997": "[向晚2.0_我要闹了]", + "6998": "[向晚2.0_兄弟们]", + "6999": "[向晚2.0_在吗]", + "7000": "[向晚2.0_震惊小向晚]", + "7001": "[向晚2.0_装酷]", + "7002": "[向晚2.0_钻头]", + "7003": "[最终幻想14_GKD]", + "7004": "[最终幻想14_salute]", + "7005": "[最终幻想14_吃萝卜]", + "7006": "[最终幻想14_大哭]", + "7007": "[最终幻想14_好奇]", + "7008": "[最终幻想14_好耶]", + "7009": "[最终幻想14_哼歌]", + "7010": "[最终幻想14_加油]", + "7011": "[最终幻想14_惊讶]", + "7012": "[最终幻想14_看攻略]", + "7013": "[最终幻想14_哭笑]", + "7014": "[最终幻想14_冷漠]", + "7015": "[最终幻想14_灵光一现]", + "7016": "[最终幻想14_摸鱼]", + "7017": "[最终幻想14_挠头]", + "7018": "[最终幻想14_让我康康]", + "7019": "[最终幻想14_认同]", + "7020": "[最终幻想14_撒娇]", + "7021": "[最终幻想14_我晕]", + "7022": "[最终幻想14_疑问]", + "7023": "[沉默寡言白河愁_吃瓜]", + "7024": "[沉默寡言白河愁_呆住]", + "7025": "[沉默寡言白河愁_干杯]", + "7026": "[沉默寡言白河愁_哈哈哈哈]", + "7027": "[沉默寡言白河愁_好耶]", + "7028": "[沉默寡言白河愁_喝口茶]", + "7029": "[沉默寡言白河愁_加油]", + "7030": "[沉默寡言白河愁_骄傲]", + "7031": "[沉默寡言白河愁_哭哭]", + "7032": "[沉默寡言白河愁_酷]", + "7033": "[沉默寡言白河愁_迷惑]", + "7034": "[沉默寡言白河愁_恼]", + "7035": "[沉默寡言白河愁_晚安]", + "7036": "[沉默寡言白河愁_威胁]", + "7037": "[沉默寡言白河愁_委屈]", + "7038": "[沉默寡言白河愁_呜呜呜]", + "7039": "[沉默寡言白河愁_希望没事]", + "7040": "[沉默寡言白河愁_疑惑]", + "7041": "[沉默寡言白河愁_阴险]", + "7042": "[沉默寡言白河愁_真棒]", + "7044": "[高田熊和高田狗_Good]", + "7045": "[高田熊和高田狗_Hi]", + "7046": "[高田熊和高田狗_oh no]", + "7047": "[高田熊和高田狗_爱心]", + "7048": "[高田熊和高田狗_饱了]", + "7049": "[高田熊和高田狗_冲呀]", + "7050": "[高田熊和高田狗_达成共识]", + "7051": "[高田熊和高田狗_诶诶]", + "7052": "[高田熊和高田狗_诶嘿]", + "7053": "[高田熊和高田狗_哈欠]", + "7054": "[高田熊和高田狗_嗨嗨]", + "7055": "[高田熊和高田狗_害羞]", + "7056": "[高田熊和高田狗_汗]", + "7057": "[高田熊和高田狗_欢呼]", + "7058": "[高田熊和高田狗_静静看]", + "7059": "[高田熊和高田狗_开心]", + "7060": "[高田熊和高田狗_了解]", + "7061": "[高田熊和高田狗_灵魂出窍]", + "7062": "[高田熊和高田狗_毛巾狗]", + "7063": "[高田熊和高田狗_毛巾熊]", + "7064": "[高田熊和高田狗_起床]", + "7065": "[高田熊和高田狗_气]", + "7066": "[高田熊和高田狗_沙发瘫]", + "7067": "[高田熊和高田狗_上网]", + "7068": "[高田熊和高田狗_双手耶]", + "7069": "[高田熊和高田狗_哇啊啊啊]", + "7070": "[高田熊和高田狗_哇哦]", + "7071": "[高田熊和高田狗_笑趴]", + "7072": "[高田熊和高田狗_压力]", + "7073": "[高田熊和高田狗_一起玩]", + "7074": "[喜羊羊·筐出未来_比心]", + "7075": "[喜羊羊·筐出未来_吃瓜]", + "7076": "[喜羊羊·筐出未来_冲冲冲]", + "7077": "[喜羊羊·筐出未来_呆滞]", + "7078": "[喜羊羊·筐出未来_汗]", + "7079": "[喜羊羊·筐出未来_好帅呀]", + "7080": "[喜羊羊·筐出未来_呼呼]", + "7081": "[喜羊羊·筐出未来_坏笑]", + "7082": "[喜羊羊·筐出未来_加油]", + "7083": "[喜羊羊·筐出未来_惊]", + "7084": "[喜羊羊·筐出未来_哭了]", + "7085": "[喜羊羊·筐出未来_酷]", + "7086": "[喜羊羊·筐出未来_溜了]", + "7087": "[喜羊羊·筐出未来_问号]", + "7088": "[喜羊羊·筐出未来_我不听]", + "7089": "[喜羊羊·筐出未来_捂脸]", + "7090": "[喜羊羊·筐出未来_嘻嘻]", + "7091": "[喜羊羊·筐出未来_星星眼]", + "7092": "[喜羊羊·筐出未来_羊狼合作]", + "7093": "[喜羊羊·筐出未来_赞]", + "7099": "[蓝精灵_比心]", + "7100": "[蓝精灵_不不不]", + "7101": "[蓝精灵_吃我一击]", + "7102": "[蓝精灵_冲啊]", + "7103": "[蓝精灵_达咩]", + "7104": "[蓝精灵_打call]", + "7105": "[蓝精灵_打扫中]", + "7106": "[蓝精灵_嗨起来]", + "7107": "[蓝精灵_华丽登场]", + "7108": "[蓝精灵_开心]", + "7109": "[蓝精灵_看书]", + "7110": "[蓝精灵_哭哭]", + "7111": "[蓝精灵_什么]", + "7112": "[蓝精灵_生气]", + "7113": "[蓝精灵_生日快乐]", + "7114": "[蓝精灵_睡觉]", + "7115": "[蓝精灵_送你花花]", + "7116": "[蓝精灵_贴贴]", + "7117": "[蓝精灵_我来啦]", + "7118": "[蓝精灵_我美吗]", + "7119": "[蓝精灵_握手]", + "7120": "[蓝精灵_下雨]", + "7121": "[蓝精灵_小丑竟是我]", + "7122": "[蓝精灵_笑哭]", + "7123": "[蓝精灵_真棒]", + "7124": "[伍六七_傲娇]", + "7125": "[伍六七_点赞]", + "7126": "[伍六七_干杯]", + "7127": "[伍六七_惊吓]", + "7128": "[伍六七_可怜]", + "7129": "[伍六七_生气]", + "7130": "[伍六七_投币]", + "7131": "[伍六七_嫌弃]", + "7132": "[伍六七_虚脱]", + "7133": "[伍六七_疑惑]", + "7144": "[尼奈_mua]", + "7145": "[尼奈_奔跑]", + "7146": "[尼奈_别在这发癫]", + "7147": "[尼奈_放屁]", + "7148": "[尼奈_含在叫]", + "7149": "[尼奈_汗颜]", + "7150": "[尼奈_好玩吗]", + "7151": "[尼奈_敬礼]", + "7152": "[尼奈_米虫哭]", + "7153": "[尼奈_妙!!]", + "7154": "[尼奈_那可真别]", + "7155": "[尼奈_难耐癫火]", + "7156": "[尼奈_你是?]", + "7157": "[尼奈_恰米]", + "7158": "[尼奈_睡觉]", + "7159": "[尼奈_歪嘴笑]", + "7160": "[尼奈_芜]", + "7161": "[尼奈_仙人指路]", + "7162": "[尼奈_歇了]", + "7163": "[尼奈_一脚盛夏]", + "11267": "[尼奈_大笑]", + "11268": "[尼奈_加油]", + "11269": "[尼奈_害羞]", + "11270": "[尼奈_难顶]", + "11271": "[尼奈_拳头硬了]", + "7164": "[战双帕弥什2_暗中观察]", + "7165": "[战双帕弥什2_保护]", + "7166": "[战双帕弥什2_恭喜]", + "7167": "[战双帕弥什2_喝茶]", + "7168": "[战双帕弥什2_丽芙帕瓦]", + "7169": "[战双帕弥什2_你币有了]", + "7170": "[战双帕弥什2_太好笑了]", + "7171": "[战双帕弥什2_哇库哇库]", + "7172": "[战双帕弥什2_无语]", + "7173": "[战双帕弥什2_芜湖]", + "7174": "[战双帕弥什2_下次一定]", + "7175": "[战双帕弥什2_一脸懵逼]", + "7176": "[战双帕弥什2_优雅]", + "7177": "[战双帕弥什2_炸了你]", + "7178": "[战双帕弥什2_真香]", + "7181": "[张京华_!!!]", + "7182": "[张京华_???]", + "7183": "[张京华_D了]", + "7184": "[张京华_ぴえん]", + "7185": "[张京华_啊???]", + "7186": "[张京华_堵车了]", + "7187": "[张京华_好可爱]", + "7188": "[张京华_好可怕]", + "7189": "[张京华_好耶]", + "7190": "[张京华_喵喵喵]", + "7191": "[张京华_你懂个锤子]", + "7192": "[张京华_你说的对]", + "7193": "[张京华_你邪了门]", + "7194": "[张京华_逆天]", + "7195": "[张京华_确实]", + "7196": "[张京华_生气了]", + "7197": "[张京华_太强了]", + "7198": "[张京华_晚安]", + "7199": "[张京华_汪汪汪]", + "7200": "[张京华_我不好说]", + "7201": "[张京华_我悟了]", + "7202": "[张京华_呜呜呜]", + "7203": "[张京华_羞羞脸]", + "7204": "[张京华_对我很重要]", + "7205": "[张京华_主人]", + "7206": "[虚拟偶像男团LASER_2G]", + "7207": "[虚拟偶像男团LASER_233]", + "7208": "[虚拟偶像男团LASER_OK]", + "7209": "[虚拟偶像男团LASER_TMD好了]", + "7210": "[虚拟偶像男团LASER_啊对对对]", + "7211": "[虚拟偶像男团LASER_熬夜]", + "7212": "[虚拟偶像男团LASER_比心]", + "7213": "[虚拟偶像男团LASER_菠菜菠菜]", + "7214": "[虚拟偶像男团LASER_不要香菜]", + "7215": "[虚拟偶像男团LASER_嘲扬]", + "7216": "[虚拟偶像男团LASER_干饭]", + "7217": "[虚拟偶像男团LASER_好活]", + "7218": "[虚拟偶像男团LASER_拒绝]", + "7219": "[虚拟偶像男团LASER_拍下来了]", + "7220": "[虚拟偶像男团LASER_拳头硬了]", + "7221": "[虚拟偶像男团LASER_让我看看]", + "7222": "[虚拟偶像男团LASER_人没了]", + "7223": "[虚拟偶像男团LASER_无语]", + "7224": "[虚拟偶像男团LASER_勿cue]", + "7225": "[虚拟偶像男团LASER_心满离]", + "7226": "[虚拟偶像男团LASER_下次一定]", + "7227": "[虚拟偶像男团LASER_星星眼]", + "7228": "[虚拟偶像男团LASER_一键三连]", + "7229": "[虚拟偶像男团LASER_质疑]", + "7230": "[虚拟偶像男团LASER_种草]", + "7231": "[虚拟偶像男团MANTA_搬砖]", + "7232": "[虚拟偶像男团MANTA_悲伤]", + "7233": "[虚拟偶像男团MANTA_贝斯笑话]", + "7234": "[虚拟偶像男团MANTA_不愧是我]", + "7235": "[虚拟偶像男团MANTA_沉思]", + "7236": "[虚拟偶像男团MANTA_点赞]", + "7237": "[虚拟偶像男团MANTA_都不学习吗]", + "7238": "[虚拟偶像男团MANTA_服了]", + "7239": "[虚拟偶像男团MANTA_干杯]", + "7240": "[虚拟偶像男团MANTA_乖巧]", + "7241": "[虚拟偶像男团MANTA_晦气]", + "7242": "[虚拟偶像男团MANTA_火钳刘明]", + "7243": "[虚拟偶像男团MANTA_继续啊哥哥]", + "7244": "[虚拟偶像男团MANTA_没钱]", + "7245": "[虚拟偶像男团MANTA_拿捏鸟]", + "7246": "[虚拟偶像男团MANTA_您这边请]", + "7247": "[虚拟偶像男团MANTA_收藏]", + "7248": "[虚拟偶像男团MANTA_说笑了]", + "7249": "[虚拟偶像男团MANTA_送花]", + "7250": "[虚拟偶像男团MANTA_天若有情]", + "7251": "[虚拟偶像男团MANTA_投币]", + "7252": "[虚拟偶像男团MANTA_压力山大]", + "7253": "[虚拟偶像男团MANTA_养身]", + "7254": "[虚拟偶像男团MANTA_元气]", + "7255": "[虚拟偶像男团MANTA_炸厨房]", + "7268": "[七海地雷套装_mua]", + "7269": "[七海地雷套装_night]", + "7270": "[七海地雷套装_respect]", + "7271": "[七海地雷套装_run了]", + "7272": "[七海地雷套装_啊~]", + "7273": "[七海地雷套装_别急]", + "7274": "[七海地雷套装_呲牙]", + "7275": "[七海地雷套装_得意]", + "7276": "[七海地雷套装_哈欠]", + "7277": "[七海地雷套装_加油]", + "7278": "[七海地雷套装_开车]", + "7279": "[七海地雷套装_哭哭]", + "7280": "[七海地雷套装_流汗]", + "7281": "[七海地雷套装_那没办法]", + "7282": "[七海地雷套装_期待]", + "7283": "[七海地雷套装_确实]", + "7284": "[七海地雷套装_鲨鱼开心]", + "7285": "[七海地雷套装_生闷气]", + "7286": "[七海地雷套装_太对了哥]", + "7287": "[七海地雷套装_通融通融]", + "7288": "[七海地雷套装_头痛]", + "7289": "[七海地雷套装_投降]", + "7290": "[七海地雷套装_问号]", + "7291": "[七海地雷套装_早上好]", + "7292": "[七海地雷套装_支持]", + "7304": "[冰糖IO 蜕变·闪耀_救命好可爱]", + "7313": "[冰糖IO 蜕变·闪耀_喜欢]", + "7295": "[冰糖IO 蜕变·闪耀_XXBT]", + "7297": "[冰糖IO 蜕变·闪耀_别急]", + "7314": "[冰糖IO 蜕变·闪耀_嘤嘤嘤]", + "7317": "[冰糖IO 蜕变·闪耀_乖乖]", + "7302": "[冰糖IO 蜕变·闪耀_哈!哈!]", + "7300": "[冰糖IO 蜕变·闪耀_呃呃]", + "7305": "[冰糖IO 蜕变·闪耀_?看看]", + "7303": "[冰糖IO 蜕变·闪耀_寄!]", + "7306": "[冰糖IO 蜕变·闪耀_两眼一黑]", + "7308": "[冰糖IO 蜕变·闪耀_生气气]", + "7311": "[冰糖IO 蜕变·闪耀_投降]", + "7298": "[冰糖IO 蜕变·闪耀_别走好吗]", + "7296": "[冰糖IO 蜕变·闪耀_啊对对对]", + "7309": "[冰糖IO 蜕变·闪耀_太强了]", + "7293": "[冰糖IO 蜕变·闪耀_???]", + "7301": "[冰糖IO 蜕变·闪耀_给你一拳]", + "7294": "[冰糖IO 蜕变·闪耀_tskr]", + "7307": "[冰糖IO 蜕变·闪耀_逆天]", + "7316": "[冰糖IO 蜕变·闪耀_正确的]", + "7310": "[冰糖IO 蜕变·闪耀_思考]", + "7299": "[冰糖IO 蜕变·闪耀_打咩]", + "7315": "[冰糖IO 蜕变·闪耀_永远爱你]", + "7312": "[冰糖IO 蜕变·闪耀_我不好说]", + "7323": "[魔法美少女ZC_Zc来咯]", + "7324": "[魔法美少女ZC_什么情况]", + "7325": "[魔法美少女ZC_使不得]", + "7326": "[魔法美少女ZC_傲娇]", + "7327": "[魔法美少女ZC_刺猬]", + "7328": "[魔法美少女ZC_咕咕]", + "7329": "[魔法美少女ZC_嗨到不行]", + "7330": "[魔法美少女ZC_大家快跑]", + "7331": "[魔法美少女ZC_好活当赏]", + "7332": "[魔法美少女ZC_好热哦]", + "7333": "[魔法美少女ZC_好耶]", + "7334": "[魔法美少女ZC_开门啊]", + "7335": "[魔法美少女ZC_歇了]", + "7336": "[魔法美少女ZC_沉思]", + "7337": "[魔法美少女ZC_疑惑]", + "7338": "[魔法美少女ZC_笑]", + "7339": "[魔法美少女ZC_给我看]", + "7340": "[魔法美少女ZC_重量级]", + "7341": "[魔法美少女ZC_震惊]", + "7342": "[魔法美少女ZC_祝福你]", + "7343": "[鹿乃桜帆_???]", + "7344": "[鹿乃桜帆_3Q]", + "7345": "[鹿乃桜帆_NG]", + "7346": "[鹿乃桜帆_OK]", + "7347": "[鹿乃桜帆_Yeah]", + "7348": "[鹿乃桜帆_爱了]", + "7349": "[鹿乃桜帆_拜拜~]", + "7350": "[鹿乃桜帆_草莓]", + "7351": "[鹿乃桜帆_呆呆]", + "7352": "[鹿乃桜帆_待机]", + "7353": "[鹿乃桜帆_恭喜]", + "7354": "[鹿乃桜帆_机智]", + "7355": "[鹿乃桜帆_惊]", + "7356": "[鹿乃桜帆_哭哭]", + "7357": "[鹿乃桜帆_酷]", + "7358": "[鹿乃桜帆_困]", + "7359": "[鹿乃桜帆_泪目]", + "7360": "[鹿乃桜帆_期待]", + "7361": "[鹿乃桜帆_气气]", + "7362": "[鹿乃桜帆_请多关照]", + "7363": "[鹿乃桜帆_神]", + "7364": "[鹿乃桜帆_受到打击]", + "7365": "[鹿乃桜帆_晚安]", + "7366": "[鹿乃桜帆_我记一下]", + "7367": "[鹿乃桜帆_心动...]", + "7368": "[鹿乃桜帆_辛苦啦]", + "7369": "[鹿乃桜帆_。。。]", + "7370": "[鹿乃桜帆_晕]", + "7371": "[鹿乃桜帆_早上好]", + "7372": "[鹿乃桜帆_尊い]", + "7522": "[黑之召唤士_yes]", + "7523": "[黑之召唤士_暗中观察]", + "7524": "[黑之召唤士_比心]", + "7525": "[黑之召唤士_达咩]", + "7526": "[黑之召唤士_干杯]", + "7527": "[黑之召唤士_害羞]", + "7528": "[黑之召唤士_鉴定]", + "7529": "[黑之召唤士_夸我]", + "7530": "[黑之召唤士_来了]", + "7531": "[黑之召唤士_嗯嗯]", + "7532": "[黑之召唤士_惬意]", + "7533": "[黑之召唤士_失忆]", + "7534": "[黑之召唤士_栓q]", + "7535": "[黑之召唤士_贴贴]", + "7536": "[黑之召唤士_想干饭]", + "7537": "[黑之召唤士_笑]", + "7538": "[黑之召唤士_兴奋]", + "7539": "[黑之召唤士_牙白]", + "7540": "[黑之召唤士_晕倒]", + "7541": "[黑之召唤士_在吗]", + "7542": "[萌宠小鼠_抱抱]", + "7543": "[萌宠小鼠_吃货]", + "7544": "[萌宠小鼠_嘿嘿]", + "7545": "[萌宠小鼠_卖萌]", + "7546": "[萌宠小鼠_伸懒腰]", + "7547": "[萌宠小鼠_生气]", + "7548": "[萌宠小鼠_贴贴]", + "7549": "[萌宠小鼠_委屈]", + "7550": "[萌宠小鼠_无语]", + "7551": "[萌宠小鼠_嘻嘻]", + "7552": "[沙滩泳池派对_冲浪]", + "7553": "[沙滩泳池派对_干杯]", + "7554": "[沙滩泳池派对_咕噜咕噜]", + "7555": "[沙滩泳池派对_来玩水呀]", + "7556": "[沙滩泳池派对_潜水]", + "7557": "[沙滩泳池派对_球球啦]", + "7558": "[沙滩泳池派对_沙雕]", + "7559": "[沙滩泳池派对_沙滩鞋]", + "7560": "[沙滩泳池派对_水枪]", + "7561": "[沙滩泳池派对_歇会儿]", + "7562": "[夏日露营季_出发咯]", + "7563": "[夏日露营季_搭帐篷]", + "7564": "[夏日露营季_露营灯]", + "7565": "[夏日露营季_迷路]", + "7566": "[夏日露营季_拍照]", + "7567": "[夏日露营季_生火]", + "7568": "[夏日露营季_晚安]", + "7569": "[夏日露营季_我太南了]", + "7570": "[夏日露营季_早安]", + "7571": "[夏日露营季_真香]", + "7572": "[夏日美食_啵啵]", + "7573": "[夏日美食_吃瓜]", + "7574": "[夏日美食_干杯]", + "7575": "[夏日美食_好椰]", + "7576": "[夏日美食_降火]", + "7577": "[夏日美食_快热化了]", + "7578": "[夏日美食_凉凉]", + "7579": "[夏日美食_酸了]", + "7580": "[夏日美食_虾虾侬]", + "7581": "[夏日美食_真香]", + "7582": "[夏日音乐会_放烟花]", + "7583": "[夏日音乐会_钢琴]", + "7584": "[夏日音乐会_话筒]", + "7585": "[夏日音乐会_吉他]", + "7586": "[夏日音乐会_架子鼓]", + "7587": "[夏日音乐会_美妙音符]", + "7588": "[夏日音乐会_手风琴]", + "7589": "[夏日音乐会_小号]", + "7590": "[夏日音乐会_小提琴]", + "7591": "[夏日音乐会_奏乐]", + "7592": "[游戏时光_OHHHHH]", + "7593": "[游戏时光_大佬带我]", + "7594": "[游戏时光_开黑吗]", + "7595": "[游戏时光_哭哭]", + "7596": "[游戏时光_厉害]", + "7597": "[游戏时光_欧气喷雾]", + "7598": "[游戏时光_甜蜜双排]", + "7599": "[游戏时光_心态崩了]", + "7600": "[游戏时光_血压]", + "7601": "[游戏时光_再来亿把]", + "7602": "[重返未来_百夫长]", + "7603": "[重返未来_别卷了]", + "7604": "[重返未来_带上它]", + "7605": "[重返未来_冬]", + "7606": "[重返未来_红弩箭]", + "7607": "[重返未来_开摆]", + "7608": "[重返未来_玛丽莲]", + "7609": "[重返未来_魔精]", + "7610": "[重返未来_苹果]", + "7611": "[重返未来_斯奈德]", + "7612": "[重返未来_未锈铠]", + "7613": "[重返未来_雾行者上班]", + "7614": "[重返未来_雾行者]", + "7615": "[重返未来_小春雀]", + "7616": "[重返未来_星锑苏芙比]", + "7617": "[重返未来_星锑]", + "7618": "[猫雷Nyaru_?]", + "7619": "[猫雷Nyaru_bigface]", + "7620": "[猫雷Nyaru_foofoo]", + "7621": "[猫雷Nyaru_mua]", + "7622": "[猫雷Nyaru_ponpon]", + "7623": "[猫雷Nyaru_sodayo]", + "7624": "[猫雷Nyaru_ybb]", + "7625": "[猫雷Nyaru_不]", + "7626": "[猫雷Nyaru_w]", + "7627": "[猫雷Nyaru_吃]", + "7628": "[猫雷Nyaru_对不起]", + "7629": "[猫雷Nyaru_汗]", + "7630": "[猫雷Nyaru_黑眼]", + "7631": "[猫雷Nyaru_寄]", + "7632": "[猫雷Nyaru_加油]", + "7633": "[猫雷Nyaru_结晶化]", + "7634": "[猫雷Nyaru_可爱]", + "7635": "[猫雷Nyaru_雷雷公主]", + "7636": "[猫雷Nyaru_猫雷最强]", + "7637": "[猫雷Nyaru_牛b]", + "7638": "[猫雷Nyaru_是]", + "7639": "[猫雷Nyaru_晚上好]", + "7640": "[猫雷Nyaru_我永远爱你]", + "7641": "[猫雷Nyaru_我在看着你]", + "7642": "[猫雷Nyaru_谢谢大傻喵]", + "7648": "[海伊_比心]", + "7649": "[海伊_唱歌]", + "7650": "[海伊_打call]", + "7651": "[海伊_大哭]", + "7652": "[海伊_得意]", + "7653": "[海伊_坏笑]", + "7654": "[海伊_惊]", + "7655": "[海伊_瞌睡]", + "7656": "[海伊_愣住]", + "7657": "[海伊_升天]", + "7658": "[海伊_生日快乐]", + "7659": "[海伊_无语]", + "7660": "[海伊_星星眼]", + "7661": "[海伊_疑惑]", + "7662": "[海伊_自闭]", + "7663": "[剑与远征_Emm]", + "7664": "[剑与远征_Wink]", + "7665": "[剑与远征_Yue!]", + "7666": "[剑与远征_爱了爱了]", + "7667": "[剑与远征_抱大腿]", + "7668": "[剑与远征_抱住自己]", + "7669": "[剑与远征_不愧是我]", + "7670": "[剑与远征_吃瓜]", + "7671": "[剑与远征_吃惊]", + "7672": "[剑与远征_点赞]", + "7673": "[剑与远征_飞吻]", + "7674": "[剑与远征_给力]", + "7675": "[剑与远征_哄不好]", + "7676": "[剑与远征_哭哭]", + "7677": "[剑与远征_你币有了]", + "7678": "[剑与远征_期待]", + "7679": "[剑与远征_生气]", + "7680": "[剑与远征_盛世美颜]", + "7681": "[剑与远征_思考]", + "7682": "[剑与远征_问号]", + "7683": "[夏日青春欧皇套装_surprise]", + "7684": "[夏日青春欧皇套装_乘凉]", + "7685": "[夏日青春欧皇套装_得意]", + "7686": "[夏日青春欧皇套装_吨吨吨]", + "7687": "[夏日青春欧皇套装_放学了]", + "7688": "[夏日青春欧皇套装_放烟花]", + "7689": "[夏日青春欧皇套装_好热]", + "7690": "[夏日青春欧皇套装_好耶]", + "7691": "[夏日青春欧皇套装_嘿嘿中了]", + "7692": "[夏日青春欧皇套装_怀疑人生]", + "7693": "[夏日青春欧皇套装_欧皇本皇]", + "7694": "[夏日青春欧皇套装_让我康康]", + "7695": "[夏日青春欧皇套装_相视一笑]", + "7696": "[夏日青春欧皇套装_谢谢老板]", + "7697": "[夏日青春欧皇套装_许愿]", + "7698": "[夏日限定宅家手册_SSS]", + "7699": "[夏日限定宅家手册_抱紧]", + "7700": "[夏日限定宅家手册_蹭蹭欧气]", + "7701": "[夏日限定宅家手册_吃零食]", + "7702": "[夏日限定宅家手册_非]", + "7703": "[夏日限定宅家手册_佬]", + "7704": "[夏日限定宅家手册_没有米了]", + "7705": "[夏日限定宅家手册_期待]", + "7706": "[夏日限定宅家手册_求锦鲤]", + "7707": "[夏日限定宅家手册_网上冲浪]", + "7708": "[夏日限定宅家手册_音乐达人]", + "7709": "[夏日限定宅家手册_再来一抽]", + "7710": "[夏日限定宅家手册_宅]", + "7711": "[夏日限定宅家手册_真不戳]", + "7712": "[夏日限定宅家手册_重在参与]", + "7713": "[传统美食_八宝饭]", + "7714": "[传统美食_大蒜]", + "7715": "[传统美食_大肘子]", + "7716": "[传统美食_火锅]", + "7717": "[传统美食_饺子]", + "7718": "[传统美食_年年有鱼]", + "7719": "[传统美食_狮子头]", + "7720": "[传统美食_汤圆]", + "7721": "[传统美食_糖葫芦]", + "7722": "[传统美食_香肠]", + "7723": "[装扮二周年_蛋糕]", + "7724": "[装扮二周年_放礼花]", + "7725": "[装扮二周年_干杯]", + "7726": "[装扮二周年_鼓掌]", + "7727": "[装扮二周年_嗨起来]", + "7728": "[装扮二周年_换新衣]", + "7729": "[装扮二周年_两岁啦]", + "7730": "[装扮二周年_撒花花]", + "7731": "[装扮二周年_生日快乐]", + "7732": "[装扮二周年_摇花扇]", + "7895": "[汉化日记3_ok]", + "7896": "[汉化日记3_respect]", + "7897": "[汉化日记3_啊对对对]", + "7898": "[汉化日记3_别逗]", + "7899": "[汉化日记3_别说话上号]", + "7900": "[汉化日记3_喝茶]", + "7901": "[汉化日记3_加班]", + "7902": "[汉化日记3_摸鱼]", + "7903": "[汉化日记3_收到]", + "7904": "[汉化日记3_摔手机]", + "7905": "[汉化日记3_栓Q]", + "7906": "[汉化日记3_贴贴]", + "7907": "[汉化日记3_退退退!]", + "7908": "[汉化日记3_一种植物]", + "7909": "[汉化日记3_优秀]", + "7910": "[Mysta Rias_流泪]", + "7911": "[Mysta Rias_摸头]", + "7912": "[Mysta Rias_比心]", + "7913": "[Mysta Rias_打call]", + "7914": "[Mysta Rias_观察]", + "7915": "[Mysta Rias_HEHE]", + "7916": "[Mysta Rias_思考]", + "7917": "[Mysta Rias_BONK]", + "7918": "[Mysta Rias_MALD]", + "7919": "[Mysta Rias_生气]", + "7920": "[Mysta Rias_惊恐]", + "7921": "[Mysta Rias_Bo‘om]", + "7922": "[Mysta Rias_冲鸭]", + "7923": "[Mysta Rias_Sadge]", + "7924": "[Mysta Rias_WOW]", + "7925": "[Mysta Rias_KON INNIT!]", + "7926": "[Mysta Rias_SHOCK]", + "7927": "[Mysta Rias_cool]", + "7928": "[Mysta Rias_???]", + "7929": "[Mysta Rias_祈祷]", + "7930": "[Vox Akuma_酒菜盒子]", + "7931": "[Vox Akuma_冷静]", + "7932": "[Vox Akuma_眠了眠了]", + "7933": "[Vox Akuma_技术故障]", + "7934": "[Vox Akuma_威胁]", + "7935": "[Vox Akuma_猩猩]", + "7936": "[Vox Akuma_认可]", + "7937": "[Vox Akuma_淦]", + "7938": "[Vox Akuma_专业]", + "7939": "[Vox Akuma_思考]", + "7940": "[Vox Akuma_嘿嘿]", + "7941": "[Vox Akuma_哼]", + "7942": "[Vox Akuma_祈祷]", + "7943": "[Vox Akuma_无语]", + "7944": "[Vox Akuma_吸氧]", + "7945": "[Vox Akuma_震惊]", + "7946": "[Vox Akuma_难过]", + "7947": "[Vox Akuma_害怕]", + "7948": "[Vox Akuma_帮大忙了]", + "7949": "[Vox Akuma_累了]", + "7950": "[Ike Eveland_anyway]", + "7951": "[Ike Eveland_hate here]", + "7952": "[Ike Eveland_I sleep]", + "7953": "[Ike Eveland_love you]", + "7954": "[Ike Eveland_吃土司]", + "7955": "[Ike Eveland_害羞]", + "7956": "[Ike Eveland_mine]", + "7957": "[Ike Eveland_被吓]", + "7958": "[Ike Eveland_唱歌]", + "7959": "[Ike Eveland_开罐]", + "7960": "[Ike Eveland_strawberry]", + "7961": "[Ike Eveland_哭哭]", + "7962": "[Ike Eveland_hi]", + "7963": "[Ike Eveland_思考]", + "7964": "[Ike Eveland_tskr]", + "7965": "[Ike Eveland_酷]", + "7966": "[Ike Eveland_呆住]", + "7967": "[Ike Eveland_加油]", + "7968": "[Ike Eveland_荧光棒]", + "7969": "[Ike Eveland_祈祷]", + "7970": "[Luca_pog]", + "7971": "[Luca_Unpog]", + "7972": "[Luca_哭哭]", + "7973": "[Luca_比心]", + "7974": "[Luca_元气满满]", + "7975": "[Luca_冒头]", + "7976": "[Luca_冲鸭]", + "7977": "[Luca_亲亲]", + "7978": "[Luca_邪恶]", + "7979": "[Luca_加载中]", + "7980": "[Luca_就这?]", + "7981": "[Luca_生气]", + "7982": "[Luca_脸红]", + "7983": "[Luca_晕厥]", + "7984": "[Luca_打call]", + "7985": "[Luca_拜托]", + "7986": "[Luca_可疑]", + "7987": "[Luca_一起锻炼!]", + "7988": "[Luca_TSKR]", + "7989": "[Luca_摸摸头]", + "7990": "[Shu Yamino_Eyyy]", + "7991": "[Shu Yamino_Pien]", + "7992": "[Shu Yamino_Shu呆子]", + "7993": "[Shu Yamino_问号]", + "7994": "[Shu Yamino_小丑]", + "7995": "[Shu Yamino_害怕]", + "7996": "[Shu Yamino_打call]", + "7997": "[Shu Yamino_探头]", + "7998": "[Shu Yamino_SUS]", + "7999": "[Shu Yamino_墨镜]", + "8000": "[Shu Yamino_困惑]", + "8001": "[Shu Yamino_吸氧]", + "8002": "[Shu Yamino_早点Shu息]", + "8003": "[Shu Yamino_画饼大师]", + "8004": "[Shu Yamino_开心]", + "8005": "[Shu Yamino_上号]", + "8006": "[Shu Yamino_Gottem]", + "8007": "[Shu Yamino_Bonk]", + "8008": "[Shu Yamino_比心]", + "8009": "[Shu Yamino_摸头]", + "8073": "[食草老龙_安详]", + "8074": "[食草老龙_不愧是你]", + "8075": "[食草老龙_不要啊]", + "8076": "[食草老龙_出发]", + "8077": "[食草老龙_盯]", + "8078": "[食草老龙_感动]", + "8079": "[食草老龙_害羞]", + "8080": "[食草老龙_计划通]", + "8081": "[食草老龙_开心]", + "8082": "[食草老龙_满足]", + "8083": "[食草老龙_谁信啊]", + "8084": "[食草老龙_什么]", + "8085": "[食草老龙_晚安]", + "8086": "[食草老龙_委屈]", + "8087": "[食草老龙_吓]", + "8088": "[BML2022_oh]", + "8089": "[BML2022_啊对对对]", + "8090": "[BML2022_爱了爱了]", + "8091": "[BML2022_吃瓜]", + "8092": "[BML2022_充电]", + "8093": "[BML2022_打call]", + "8094": "[BML2022_点赞]", + "8095": "[BML2022_干杯]", + "8096": "[BML2022_好耶]", + "8097": "[BML2022_活捉]", + "8098": "[BML2022_麻了]", + "8099": "[BML2022_燃起来了]", + "8100": "[BML2022_撒花]", + "8101": "[BML2022_双厨狂喜]", + "8102": "[BML2022_智慧的眼神]", + "8103": "[舞蹈嘉年华_emmm]", + "8104": "[舞蹈嘉年华_OK]", + "8105": "[舞蹈嘉年华_奔溃]", + "8106": "[舞蹈嘉年华_冲]", + "8107": "[舞蹈嘉年华_疯狂工作]", + "8108": "[舞蹈嘉年华_格局]", + "8109": "[舞蹈嘉年华_哈哈]", + "8110": "[舞蹈嘉年华_愣住]", + "8111": "[舞蹈嘉年华_溜了]", + "8112": "[舞蹈嘉年华_摸鱼]", + "8113": "[舞蹈嘉年华_拿捏]", + "8114": "[舞蹈嘉年华_嗯嗯]", + "8115": "[舞蹈嘉年华_伤脑筋]", + "8116": "[舞蹈嘉年华_耍赖]", + "8117": "[舞蹈嘉年华_随缘]", + "8118": "[舞蹈嘉年华_贴贴]", + "8119": "[舞蹈嘉年华_委屈]", + "8120": "[舞蹈嘉年华_嘻嘻]", + "8121": "[舞蹈嘉年华_辛苦啦]", + "8122": "[舞蹈嘉年华_优秀]", + "8149": "[仙剑七_仙]", + "8132": "[仙剑七_剑]", + "8140": "[仙剑七_奇]", + "8148": "[仙剑七_侠]", + "8125": "[仙剑七_传]", + "8139": "[仙剑七_七]", + "8138": "[仙剑七_努力]", + "8135": "[仙剑七_可]", + "8129": "[仙剑七_鸽了]", + "8143": "[仙剑七_思考]", + "8127": "[仙剑七_感谢]", + "8147": "[仙剑七_我来了]", + "8126": "[仙剑七_尴尬]", + "8134": "[仙剑七_拒绝]", + "8128": "[仙剑七_干杯]", + "8151": "[仙剑七_疑惑]", + "8136": "[仙剑七_可爱]", + "8123": "[仙剑七_比心]", + "8152": "[仙剑七_祝符]", + "8130": "[仙剑七_加油]", + "8146": "[仙剑七_委屈]", + "8144": "[仙剑七_太难了]", + "8124": "[仙剑七_吃瓜]", + "8150": "[仙剑七_摇头]", + "8131": "[仙剑七_假装不在]", + "8141": "[仙剑七_庆祝]", + "8137": "[仙剑七_哭哭]", + "8145": "[仙剑七_晚安]", + "8133": "[仙剑七_就这]", + "8142": "[仙剑七_生气]", + "8153": "[团团奇米莫_good]", + "8154": "[团团奇米莫_收到]", + "8155": "[团团奇米莫_嗯嗯]", + "8156": "[团团奇米莫_爱你]", + "8157": "[团团奇米莫_球球啦]", + "8158": "[团团奇米莫_好耶]", + "8159": "[团团奇米莫_拜托了]", + "8160": "[团团奇米莫_对不起]", + "8161": "[团团奇米莫_暗中观察]", + "8162": "[团团奇米莫_瘫]", + "8188": "[团团奇米莫_辛苦啦]", + "8189": "[团团奇米莫_惊]", + "8190": "[团团奇米莫_让我康康]", + "8191": "[团团奇米莫_吧唧]", + "8192": "[团团奇米莫_哇]", + "8193": "[团团奇米莫_加油]", + "8194": "[团团奇米莫_得意]", + "8195": "[团团奇米莫_我来啦]", + "8196": "[团团奇米莫_哭]", + "8197": "[团团奇米莫_超凶]", + "8198": "[团团奇米莫_紧张]", + "8199": "[团团奇米莫_谢谢]", + "8200": "[团团奇米莫_问号]", + "8201": "[团团奇米莫_啊啊啊]", + "8202": "[团团奇米莫_晚安]", + "8203": "[团团奇米莫_保重身体]", + "8204": "[团团奇米莫_无语]", + "8205": "[团团奇米莫_啊这]", + "8206": "[团团奇米莫_被你迷倒]", + "8207": "[团团奇米莫_累了]", + "8163": "[陆鳐LuLu_EMO了]", + "8164": "[陆鳐LuLu_NO]", + "8165": "[陆鳐LuLu_YYDS]", + "8166": "[陆鳐LuLu_暗中观察]", + "8167": "[陆鳐LuLu_比心]", + "8168": "[陆鳐LuLu_吃瓜]", + "8169": "[陆鳐LuLu_充电中]", + "8170": "[陆鳐LuLu_打CALL]", + "8171": "[陆鳐LuLu_大无语]", + "8172": "[陆鳐LuLu_爹]", + "8173": "[陆鳐LuLu_狗头保命]", + "8174": "[陆鳐LuLu_滑稽]", + "8175": "[陆鳐LuLu_加油]", + "8176": "[陆鳐LuLu_就这]", + "8177": "[陆鳐LuLu_溜了溜了]", + "8178": "[陆鳐LuLu_摸头]", + "8179": "[陆鳐LuLu_摸鱼]", + "8180": "[陆鳐LuLu_你没事吧]", + "8181": "[陆鳐LuLu_破防]", + "8182": "[陆鳐LuLu_贴贴]", + "8183": "[陆鳐LuLu_笑出节奏]", + "8184": "[陆鳐LuLu_许愿]", + "8185": "[陆鳐LuLu_真不熟啊]", + "8186": "[陆鳐LuLu_真香]", + "8187": "[陆鳐LuLu_重拳出击]", + "8208": "[幕末替身传说_挨揍]", + "8209": "[幕末替身传说_吵架]", + "8210": "[幕末替身传说_吃货]", + "8211": "[幕末替身传说_垂头丧气]", + "8212": "[幕末替身传说_大吵]", + "8213": "[幕末替身传说_干饭]", + "8214": "[幕末替身传说_葛优躺]", + "8215": "[幕末替身传说_害羞]", + "8216": "[幕末替身传说_好吃]", + "8217": "[幕末替身传说_开心]", + "8218": "[幕末替身传说_哭]", + "8219": "[幕末替身传说_困惑]", + "8220": "[幕末替身传说_累死了]", + "8221": "[幕末替身传说_流口水]", + "8222": "[幕末替身传说_美人]", + "8223": "[幕末替身传说_媚眼]", + "8224": "[幕末替身传说_亲亲]", + "8225": "[幕末替身传说_燃起来]", + "8226": "[幕末替身传说_上啊]", + "8227": "[幕末替身传说_生气]", + "8228": "[幕末替身传说_想打人]", + "8229": "[幕末替身传说_一脸冷漠]", + "8230": "[幕末替身传说_运动]", + "8231": "[幕末替身传说_斩]", + "8232": "[幕末替身传说_整理头发]", + "8233": "[沈剑心第三季专属装扮_啊对对对]", + "8234": "[沈剑心第三季专属装扮_啊这]", + "8235": "[沈剑心第三季专属装扮_熬夜]", + "8236": "[沈剑心第三季专属装扮_吃惊]", + "8237": "[沈剑心第三季专属装扮_害怕]", + "8238": "[沈剑心第三季专属装扮_划水]", + "8239": "[沈剑心第三季专属装扮_可恶]", + "8240": "[沈剑心第三季专属装扮_裂开]", + "8241": "[沈剑心第三季专属装扮_流汗]", + "8242": "[沈剑心第三季专属装扮_麻了]", + "8243": "[沈剑心第三季专属装扮_妙啊]", + "8244": "[沈剑心第三季专属装扮_拿捏]", + "8245": "[沈剑心第三季专属装扮_躺了]", + "8246": "[沈剑心第三季专属装扮_摇人]", + "8247": "[沈剑心第三季专属装扮_疑问]", + "8261": "[崩坏3·光辉矢愿_回眸]", + "8251": "[崩坏3·光辉矢愿_比心]", + "8258": "[崩坏3·光辉矢愿_大佬喝可乐]", + "8257": "[崩坏3·光辉矢愿_吃西瓜冰]", + "8255": "[崩坏3·光辉矢愿_彩虹水枪]", + "8250": "[崩坏3·光辉矢愿_遨游]", + "8253": "[崩坏3·光辉矢愿_不可以看]", + "8265": "[崩坏3·光辉矢愿_让我摸摸]", + "8266": "[崩坏3·光辉矢愿_想我了吗]", + "8249": "[崩坏3·光辉矢愿_安详]", + "8264": "[崩坏3·光辉矢愿_瞄准]", + "8256": "[崩坏3·光辉矢愿_策划]", + "8252": "[崩坏3·光辉矢愿_编剧]", + "8259": "[崩坏3·光辉矢愿_大伟哥]", + "8248": "[崩坏3·光辉矢愿_up主]", + "8254": "[崩坏3·光辉矢愿_擦地]", + "8260": "[崩坏3·光辉矢愿_给你纸巾]", + "8262": "[崩坏3·光辉矢愿_剪掉]", + "8263": "[崩坏3·光辉矢愿_惊]", + "8267": "[崩坏3·光辉矢愿_小喇叭]", + "8268": "[贝拉个性装扮2.0_NONONO]", + "8269": "[贝拉个性装扮2.0_拜拜]", + "8270": "[贝拉个性装扮2.0_贝极星]", + "8271": "[贝拉个性装扮2.0_小笨蛋]", + "8272": "[贝拉个性装扮2.0_贝拉碎月]", + "8273": "[贝拉个性装扮2.0_唱歌]", + "8274": "[贝拉个性装扮2.0_吃牛肉干]", + "8275": "[贝拉个性装扮2.0_大聪明]", + "8276": "[贝拉个性装扮2.0_过载]", + "8277": "[贝拉个性装扮2.0_哈哈哈哈哈]", + "8278": "[贝拉个性装扮2.0_哈喽]", + "8279": "[贝拉个性装扮2.0_晃悠悠]", + "8280": "[贝拉个性装扮2.0_剑来]", + "8281": "[贝拉个性装扮2.0_健身]", + "8282": "[贝拉个性装扮2.0_拉伸]", + "8283": "[贝拉个性装扮2.0_拉手摧花]", + "8284": "[贝拉个性装扮2.0_你再想想]", + "8285": "[贝拉个性装扮2.0_起床啦]", + "8286": "[贝拉个性装扮2.0_日程表]", + "8287": "[贝拉个性装扮2.0_天鹅小贝拉]", + "8288": "[贝拉个性装扮2.0_兔兔拳]", + "8289": "[贝拉个性装扮2.0_委屈小贝拉]", + "8290": "[贝拉个性装扮2.0_我们是]", + "8291": "[贝拉个性装扮2.0_小胡子]", + "8292": "[贝拉个性装扮2.0_要相信光]", + "8293": "[灵魂潮汐周年庆典_吃蛋糕]", + "8294": "[灵魂潮汐周年庆典_给心心]", + "8295": "[灵魂潮汐周年庆典_好气啊]", + "8296": "[灵魂潮汐周年庆典_呵呵呵]", + "8297": "[灵魂潮汐周年庆典_嘿嘿嘿]", + "8298": "[灵魂潮汐周年庆典_纠结中]", + "8299": "[灵魂潮汐周年庆典_就这]", + "8300": "[灵魂潮汐周年庆典_看看你]", + "8301": "[灵魂潮汐周年庆典_起飞]", + "8302": "[灵魂潮汐周年庆典_让我来]", + "8303": "[灵魂潮汐周年庆典_认真脸]", + "8304": "[灵魂潮汐周年庆典_撒花花]", + "8305": "[灵魂潮汐周年庆典_睡大觉]", + "8306": "[灵魂潮汐周年庆典_歇了]", + "8307": "[灵魂潮汐周年庆典_哇哦]", + "8308": "[灵魂潮汐周年庆典_再来点]", + "8309": "[灵魂潮汐周年庆典_怎么办]", + "8310": "[灵魂潮汐周年庆典_周年庆快乐]", + "8311": "[阿萨Aza-多面体_nice pee]", + "8312": "[阿萨Aza-多面体_www]", + "8313": "[阿萨Aza-多面体_安慰]", + "8314": "[阿萨Aza-多面体_别在这发癫]", + "8315": "[阿萨Aza-多面体_打游戏咯!]", + "8316": "[阿萨Aza-多面体_戴墨镜]", + "8317": "[阿萨Aza-多面体_愤怒]", + "8318": "[阿萨Aza-多面体_福如东海]", + "8319": "[阿萨Aza-多面体_感觉良好]", + "8320": "[阿萨Aza-多面体_给钱]", + "8321": "[阿萨Aza-多面体_挂!!!]", + "8322": "[阿萨Aza-多面体_来咯]", + "8323": "[阿萨Aza-多面体_欧皇]", + "8324": "[阿萨Aza-多面体_私密马赛]", + "8325": "[阿萨Aza-多面体_思考]", + "8326": "[阿萨Aza-多面体_委屈]", + "8327": "[阿萨Aza-多面体_嘻嘻]", + "8328": "[阿萨Aza-多面体_嫌弃]", + "8329": "[阿萨Aza-多面体_小毛线贴贴]", + "8330": "[阿萨Aza-多面体_阴险]", + "8331": "[阿萨Aza-多面体_宝贝]", + "8332": "[阿萨Aza-多面体_害怕]", + "8333": "[阿萨Aza-多面体_妈]", + "8334": "[阿萨Aza-多面体_吐舌红脸]", + "8335": "[阿萨Aza-多面体_问号]", + "8336": "[初音未来V4C五周年纪念_OHHH]", + "8337": "[初音未来V4C五周年纪念_啊对对对]", + "8338": "[初音未来V4C五周年纪念_暗中观察]", + "8339": "[初音未来V4C五周年纪念_唱歌]", + "8340": "[初音未来V4C五周年纪念_吃瓜]", + "8341": "[初音未来V4C五周年纪念_冲鸭]", + "8342": "[初音未来V4C五周年纪念_戳戳]", + "8343": "[初音未来V4C五周年纪念_达咩]", + "8344": "[初音未来V4C五周年纪念_乖巧]", + "8345": "[初音未来V4C五周年纪念_哈哈哈]", + "8346": "[初音未来V4C五周年纪念_害羞]", + "8347": "[初音未来V4C五周年纪念_哼]", + "8348": "[初音未来V4C五周年纪念_来咯]", + "8349": "[初音未来V4C五周年纪念_老板大气]", + "8351": "[初音未来V4C五周年纪念_溜了]", + "8352": "[初音未来V4C五周年纪念_牛哇]", + "8354": "[初音未来V4C五周年纪念_噗]", + "8356": "[初音未来V4C五周年纪念_球球了]", + "8358": "[初音未来V4C五周年纪念_说不出话]", + "8359": "[初音未来V4C五周年纪念_思考]", + "8360": "[初音未来V4C五周年纪念_哇哦]", + "8361": "[初音未来V4C五周年纪念_晚安]", + "8362": "[初音未来V4C五周年纪念_危]", + "8364": "[初音未来V4C五周年纪念_永远爱你]", + "8365": "[初音未来V4C五周年纪念_装傻]", + "8357": "[初音未来V4C五周年纪念_生日快乐]", + "8355": "[初音未来V4C五周年纪念_庆祝]", + "8350": "[初音未来V4C五周年纪念_礼物]", + "8363": "[初音未来V4C五周年纪念_喜欢]", + "8353": "[初音未来V4C五周年纪念_欧皇附体]", + "8366": "[童话系列·胡桃夹子_冲鸭]", + "8367": "[童话系列·胡桃夹子_立正]", + "8368": "[童话系列·胡桃夹子_敬礼]", + "8369": "[童话系列·胡桃夹子_点赞]", + "8370": "[童话系列·胡桃夹子_无奈]", + "8371": "[童话系列·胡桃夹子_害怕]", + "8372": "[童话系列·胡桃夹子_什么]", + "8373": "[童话系列·胡桃夹子_谢谢]", + "8374": "[童话系列·胡桃夹子_委屈]", + "8375": "[童话系列·胡桃夹子_抱抱]", + "8376": "[童话系列·胡桃夹子_溜了]", + "8377": "[童话系列·胡桃夹子_哈哈]", + "8378": "[童话系列·胡桃夹子_好耶]", + "8379": "[童话系列·胡桃夹子_你好]", + "8380": "[童话系列·胡桃夹子_喜欢]", + "8381": "[童话系列·胡桃夹子_愣住]", + "8382": "[童话系列·胡桃夹子_伤脑筋]", + "8383": "[童话系列·胡桃夹子_嗯嗯]", + "8384": "[童话系列·胡桃夹子_辛苦了]", + "8385": "[童话系列·胡桃夹子_不行]", + "8386": "[童话系列·胡桃夹子_打扰了]", + "8387": "[童话系列·胡桃夹子_偷笑]", + "8388": "[童话系列·胡桃夹子_探头]", + "8389": "[童话系列·胡桃夹子_不知道]", + "8390": "[童话系列·胡桃夹子_害羞]", + "8391": "[懒懒兔_啊]", + "8392": "[懒懒兔_沉思]", + "8393": "[懒懒兔_干杯]", + "8394": "[懒懒兔_给你币]", + "8395": "[懒懒兔_给你心心]", + "8396": "[懒懒兔_无奈]", + "8397": "[懒懒兔_好耶]", + "8398": "[懒懒兔_加油]", + "8399": "[懒懒兔_惊讶]", + "8400": "[懒懒兔_拉手手]", + "8401": "[懒懒兔_期待]", + "8402": "[懒懒兔_亲亲]", + "8403": "[懒懒兔_甜的]", + "8404": "[懒懒兔_偷瞄]", + "8405": "[懒懒兔_兔饼]", + "8406": "[懒懒兔_晚安]", + "8407": "[懒懒兔_呜呜]", + "8408": "[懒懒兔_洗澡]", + "8409": "[懒懒兔_幸运]", + "8410": "[懒懒兔_在家]", + "8411": "[東雪蓮_打拳]", + "8412": "[東雪蓮_对不起]", + "8413": "[東雪蓮_飞吻]", + "8414": "[東雪蓮_攻击性]", + "8415": "[東雪蓮_好热]", + "8416": "[東雪蓮_好耶]", + "8417": "[東雪蓮_滑稽]", + "8418": "[東雪蓮_欢呼]", + "8419": "[東雪蓮_流汗]", + "8420": "[東雪蓮_美妙]", + "8421": "[東雪蓮_难绷]", + "8422": "[東雪蓮_嗯?]", + "8423": "[東雪蓮_期待]", + "8424": "[東雪蓮_气昏]", + "8425": "[東雪蓮_生气]", + "8426": "[東雪蓮_手指]", + "8427": "[東雪蓮_睡觉]", + "8428": "[東雪蓮_太棒了]", + "8429": "[東雪蓮_头大]", + "8430": "[東雪蓮_喂喂喂]", + "8431": "[東雪蓮_我呃呃]", + "8432": "[東雪蓮_我接着含]", + "8433": "[東雪蓮_无语子]", + "8434": "[東雪蓮_宣誓]", + "8435": "[東雪蓮_正确的]", + "8436": "[香香软软的小泡芙_hi]", + "8437": "[香香软软的小泡芙_?!]", + "8438": "[香香软软的小泡芙_安详]", + "8439": "[香香软软的小泡芙_暗中观察]", + "8440": "[香香软软的小泡芙_嗷呜]", + "8441": "[香香软软的小泡芙_不要靠过来]", + "8442": "[香香软软的小泡芙_赌气]", + "8443": "[香香软软的小泡芙_饿倒]", + "8444": "[香香软软的小泡芙_乖巧]", + "8445": "[香香软软的小泡芙_好耶]", + "8446": "[香香软软的小泡芙_昏昏欲睡]", + "8447": "[香香软软的小泡芙_惊]", + "8448": "[香香软软的小泡芙_惊恐]", + "8449": "[香香软软的小泡芙_可爱]", + "8450": "[香香软软的小泡芙_困困]", + "8451": "[香香软软的小泡芙_迷之微笑]", + "8452": "[香香软软的小泡芙_莫挨本宝]", + "8453": "[香香软软的小泡芙_抛瓦]", + "8454": "[香香软软的小泡芙_破口大骂]", + "8455": "[香香软软的小泡芙_求求]", + "8456": "[香香软软的小泡芙_让我干饭]", + "8457": "[香香软软的小泡芙_歪头杀]", + "8458": "[香香软软的小泡芙_委屈]", + "8459": "[香香软软的小泡芙_我不要起床]", + "8460": "[香香软软的小泡芙_无聊]", + "8461": "[香香软软的小泡芙_吸溜]", + "8462": "[香香软软的小泡芙_嫌弃]", + "8463": "[香香软软的小泡芙_凶凶]", + "8464": "[香香软软的小泡芙_眼泪汪汪]", + "8465": "[香香软软的小泡芙_有好吃的]", + "8466": "[少年Pi_Big胆]", + "8467": "[少年Pi_邦邦两拳]", + "8468": "[少年Pi_不乐]", + "8469": "[少年Pi_和解]", + "8470": "[少年Pi_看破]", + "8471": "[少年Pi_酷哥]", + "8472": "[少年Pi_喇叭]", + "8473": "[少年Pi_老鼠汤]", + "8474": "[少年Pi_乐]", + "8475": "[少年Pi_两眼一黑]", + "8476": "[少年Pi_两眼一亮]", + "8477": "[少年Pi_有蟑螂!]", + "8478": "[少年Pi_眠]", + "8479": "[少年Pi_挠头]", + "8480": "[少年Pi_你先别急]", + "8481": "[少年Pi_女仆绝学]", + "8482": "[少年Pi_呕]", + "8483": "[少年Pi_恰外卖]", + "8484": "[少年Pi_嗦面]", + "8485": "[少年Pi_下播]", + "8486": "[少年Pi_想逃?]", + "8487": "[少年Pi_阳间]", + "8488": "[少年Pi_吔?]", + "8489": "[少年Pi_呦西]", + "8490": "[少年Pi_这个好]", + "8491": "[梦音茶糯_???]", + "8492": "[梦音茶糯_mua]", + "8493": "[梦音茶糯_NO]", + "8494": "[梦音茶糯_绷不住了]", + "8495": "[梦音茶糯_超凶]", + "8496": "[梦音茶糯_吃撑]", + "8497": "[梦音茶糯_恭喜]", + "8498": "[梦音茶糯_好听]", + "8499": "[梦音茶糯_好耶]", + "8500": "[梦音茶糯_泪目]", + "8501": "[梦音茶糯_摸鱼]", + "8502": "[梦音茶糯_期待]", + "8503": "[梦音茶糯_三连了]", + "8504": "[梦音茶糯_晚安]", + "8505": "[梦音茶糯_无语]", + "8506": "[梦音茶糯_屑!]", + "8507": "[梦音茶糯_辛苦了]", + "8508": "[梦音茶糯_摇晃菇]", + "8509": "[梦音茶糯_优雅]", + "8510": "[梦音茶糯_震撼]", + "8511": "[BLG布噜噜_BLG加油]", + "8512": "[BLG布噜噜_暗中观察]", + "8513": "[BLG布噜噜_保护]", + "8514": "[BLG布噜噜_比心]", + "8515": "[BLG布噜噜_馋]", + "8516": "[BLG布噜噜_吃货]", + "8517": "[BLG布噜噜_充电中]", + "8518": "[BLG布噜噜_戳]", + "8519": "[BLG布噜噜_打游戏]", + "8520": "[BLG布噜噜_点赞]", + "8521": "[BLG布噜噜_盒里吗]", + "8522": "[BLG布噜噜_紧张]", + "8523": "[BLG布噜噜_开心]", + "8524": "[BLG布噜噜_累了]", + "8525": "[BLG布噜噜_摸摸头]", + "8526": "[BLG布噜噜_亲亲]", + "8527": "[BLG布噜噜_生气]", + "8528": "[BLG布噜噜_委屈]", + "8529": "[BLG布噜噜_问号]", + "8530": "[BLG布噜噜_运动]", + "8532": "[魔兽世界 巫妖王之怒_no]", + "8533": "[魔兽世界 巫妖王之怒_啊对对对]", + "8534": "[魔兽世界 巫妖王之怒_傲娇]", + "8535": "[魔兽世界 巫妖王之怒_拔剑]", + "8536": "[魔兽世界 巫妖王之怒_冲啊]", + "8537": "[魔兽世界 巫妖王之怒_哈哈哈]", + "8538": "[魔兽世界 巫妖王之怒_好耶]", + "8539": "[魔兽世界 巫妖王之怒_呼叫冰龙]", + "8540": "[魔兽世界 巫妖王之怒_怀疑人生]", + "8541": "[魔兽世界 巫妖王之怒_毁灭吧]", + "8542": "[魔兽世界 巫妖王之怒_惊讶]", + "8543": "[魔兽世界 巫妖王之怒_就这]", + "8544": "[魔兽世界 巫妖王之怒_酷]", + "8545": "[魔兽世界 巫妖王之怒_离谱]", + "8546": "[魔兽世界 巫妖王之怒_期待]", + "8547": "[魔兽世界 巫妖王之怒_强大]", + "8548": "[魔兽世界 巫妖王之怒_栓Q]", + "8549": "[魔兽世界 巫妖王之怒_饿了]", + "8550": "[魔兽世界 巫妖王之怒_危]", + "8551": "[魔兽世界 巫妖王之怒_污喵王]", + "8552": "[魔兽世界 巫妖王之怒_芜湖]", + "8553": "[魔兽世界 巫妖王之怒_希望破灭]", + "8554": "[魔兽世界 巫妖王之怒_疑惑]", + "8555": "[魔兽世界 巫妖王之怒_战斗]", + "8556": "[魔兽世界 巫妖王之怒_召唤大军]", + "8558": "[长草颜团子_???]", + "8559": "[长草颜团子_886]", + "8560": "[长草颜团子_OK]", + "8561": "[长草颜团子_Power]", + "8562": "[长草颜团子_SOS]", + "8563": "[长草颜团子_ZZZ]", + "8564": "[长草颜团子_爱你]", + "8565": "[长草颜团子_棒]", + "8566": "[长草颜团子_吃瓜]", + "8567": "[长草颜团子_吹爆]", + "8568": "[长草颜团子_达咩]", + "8569": "[长草颜团子_打]", + "8570": "[长草颜团子_打卡]", + "8571": "[长草颜团子_等]", + "8572": "[长草颜团子_非酋]", + "8573": "[长草颜团子_佛]", + "8574": "[长草颜团子_干杯]", + "8575": "[长草颜团子_给我火]", + "8576": "[长草颜团子_害怕]", + "8577": "[长草颜团子_看看]", + "8578": "[长草颜团子_哭了]", + "8579": "[长草颜团子_摸鱼]", + "8580": "[长草颜团子_你好]", + "8581": "[长草颜团子_潜水]", + "8582": "[长草颜团子_三连]", + "8583": "[长草颜团子_私信]", + "8584": "[长草颜团子_完结撒花]", + "8585": "[长草颜团子_无语]", + "8586": "[长草颜团子_在吗]", + "8587": "[长草颜团子_震惊]", + "8588": "[永雏塔菲·1883_安心]", + "8589": "[永雏塔菲·1883_别急]", + "8590": "[永雏塔菲·1883_大菲柱]", + "8591": "[永雏塔菲·1883_钓鱼]", + "8592": "[永雏塔菲·1883_好的喵]", + "8593": "[永雏塔菲·1883_回旋]", + "8594": "[永雏塔菲·1883_结晶]", + "8595": "[永雏塔菲·1883_开派对咯]", + "8596": "[永雏塔菲·1883_看看你的]", + "8597": "[永雏塔菲·1883_磕头]", + "8598": "[永雏塔菲·1883_米线]", + "8599": "[永雏塔菲·1883_喵]", + "8600": "[永雏塔菲·1883_拿下]", + "8601": "[永雏塔菲·1883_难过]", + "8602": "[永雏塔菲·1883_切割]", + "8603": "[永雏塔菲·1883_收收味]", + "8604": "[永雏塔菲·1883_双手抱胸]", + "8605": "[永雏塔菲·1883_塔不灭]", + "8606": "[永雏塔菲·1883_甜甜甜]", + "8607": "[永雏塔菲·1883_王牌级]", + "8608": "[永雏塔菲·1883_委屈]", + "8609": "[永雏塔菲·1883_阴险]", + "8610": "[永雏塔菲·1883_拥抱]", + "8611": "[永雏塔菲·1883_中秋快乐]", + "8612": "[永雏塔菲·1883_最爱你了]", + "8613": "[干杯!嫦娥兔_投币月饼]", + "8614": "[干杯!嫦娥兔_吃螃蟹]", + "8615": "[干杯!嫦娥兔_赏月]", + "8616": "[干杯!嫦娥兔_干杯]", + "8617": "[干杯!嫦娥兔_点赞]", + "8618": "[干杯!嫦娥兔_爱了爱了]", + "8619": "[干杯!嫦娥兔_愣住]", + "8620": "[干杯!嫦娥兔_哭死]", + "8621": "[干杯!嫦娥兔_尴尬]", + "8622": "[干杯!嫦娥兔_拒绝]", + "8623": "[靠你啦!战神系统_星星眼]", + "8624": "[靠你啦!战神系统_害羞]", + "8625": "[靠你啦!战神系统_哭哭]", + "8626": "[靠你啦!战神系统_期待]", + "8627": "[靠你啦!战神系统_静静看着你]", + "8628": "[靠你啦!战神系统_就是我]", + "8629": "[靠你啦!战神系统_生气]", + "8630": "[靠你啦!战神系统_wink]", + "8631": "[靠你啦!战神系统_思考]", + "8632": "[靠你啦!战神系统_偷笑]", + "8653": "[木糖纯最后的花嫁_mua]", + "8654": "[木糖纯最后的花嫁_被痛苦淹没]", + "8655": "[木糖纯最后的花嫁_不愧是你]", + "8656": "[木糖纯最后的花嫁_吃瓜]", + "8657": "[木糖纯最后的花嫁_饿了]", + "8658": "[木糖纯最后的花嫁_干杯]", + "8659": "[木糖纯最后的花嫁_哭了]", + "8660": "[木糖纯最后的花嫁_泪奔]", + "8661": "[木糖纯最后的花嫁_脸红]", + "8662": "[木糖纯最后的花嫁_摸头]", + "8663": "[木糖纯最后的花嫁_期待]", + "8664": "[木糖纯最后的花嫁_亲一个]", + "8665": "[木糖纯最后的花嫁_上网]", + "8666": "[木糖纯最后的花嫁_贴贴]", + "8667": "[木糖纯最后的花嫁_晚安]", + "8668": "[木糖纯最后的花嫁_围观]", + "8669": "[木糖纯最后的花嫁_我不理解]", + "8670": "[木糖纯最后的花嫁_我方了]", + "8671": "[木糖纯最后的花嫁_我看看]", + "8672": "[木糖纯最后的花嫁_呜呼]", + "8673": "[木糖纯最后的花嫁_有人叫我?]", + "8674": "[木糖纯最后的花嫁_晕]", + "8675": "[木糖纯最后的花嫁_赞]", + "8676": "[木糖纯最后的花嫁_震撼]", + "8677": "[木糖纯最后的花嫁_皱眉]", + "8678": "[Muse Dash_FC]", + "8679": "[Muse Dash_GREAT]", + "8680": "[Muse Dash_爱了爱了]", + "8681": "[Muse Dash_暗中观察]", + "8682": "[Muse Dash_比心]", + "8683": "[Muse Dash_吃瓜]", + "8684": "[Muse Dash_打call]", + "8685": "[Muse Dash_大受震撼]", + "8686": "[Muse Dash_好耶]", + "8687": "[Muse Dash_哼]", + "8688": "[Muse Dash_摸鱼]", + "8689": "[Muse Dash_欧拉欧拉]", + "8690": "[Muse Dash_起飞]", + "8691": "[Muse Dash_恰柠檬]", + "8692": "[Muse Dash_上工]", + "8693": "[Muse Dash_我不理解]", + "8694": "[Muse Dash_呜呜]", + "8695": "[Muse Dash_下次一定]", + "8696": "[Muse Dash_怎么会事]", + "8697": "[Muse Dash_www]", + "8698": "[线条小狗_爱你]", + "8699": "[线条小狗_不是我干的]", + "8700": "[线条小狗_冲啊]", + "8701": "[线条小狗_大佬]", + "8702": "[线条小狗_盯]", + "8703": "[线条小狗_哼]", + "8704": "[线条小狗_挥手]", + "8705": "[线条小狗_击掌]", + "8706": "[线条小狗_静静看着你]", + "8707": "[线条小狗_沮丧]", + "8708": "[线条小狗_快乐]", + "8709": "[线条小狗_难过]", + "8710": "[线条小狗_求抱抱]", + "8711": "[线条小狗_求投喂]", + "8712": "[线条小狗_上学去咯]", + "8713": "[线条小狗_伸懒腰]", + "8714": "[线条小狗_睡觉]", + "8715": "[线条小狗_送礼物]", + "8716": "[线条小狗_天哪]", + "8717": "[线条小狗_哇哦]", + "8718": "[线条小狗_歪头]", + "8719": "[线条小狗_无辜]", + "8720": "[线条小狗_震惊]", + "8721": "[线条小狗_职业假笑]", + "8722": "[线条小狗_走好不送]", + "8723": "[黑门_OK]", + "8724": "[黑门_暴击]", + "8725": "[黑门_比心]", + "8726": "[黑门_吃瓜]", + "8727": "[黑门_点赞]", + "8728": "[黑门_溜了溜了]", + "8729": "[黑门_生气]", + "8730": "[黑门_围观]", + "8731": "[黑门_无语]", + "8732": "[黑门_震惊]", + "8733": "[萌节六周年装扮_OK]", + "8734": "[萌节六周年装扮_爱了]", + "8735": "[萌节六周年装扮_冲]", + "8736": "[萌节六周年装扮_自信]", + "8737": "[萌节六周年装扮_大笑]", + "8738": "[萌节六周年装扮_感谢]", + "8739": "[萌节六周年装扮_嗨]", + "8740": "[萌节六周年装扮_好奇]", + "8741": "[萌节六周年装扮_喝彩]", + "8742": "[萌节六周年装扮_惊吓]", + "8743": "[萌节六周年装扮_累]", + "8744": "[萌节六周年装扮_卖萌]", + "8745": "[萌节六周年装扮_满意]", + "8746": "[萌节六周年装扮_认真]", + "8747": "[萌节六周年装扮_生气]", + "8748": "[萌节六周年装扮_耍酷]", + "8749": "[萌节六周年装扮_萧瑟]", + "8750": "[萌节六周年装扮_疑惑]", + "8751": "[萌节六周年装扮_悠闲]", + "8752": "[萌节六周年装扮_真香]", + "8753": "[NoWorld_???]", + "8754": "[NoWorld_DDOK]", + "8755": "[NoWorld_POWER]", + "8756": "[NoWorld_OMG]", + "8757": "[NoWorld_Rua]", + "8758": "[NoWorld_www]", + "8759": "[NoWorld_弹钢琴]", + "8760": "[NoWorld_盯]", + "8761": "[NoWorld_飞走了]", + "8762": "[NoWorld_过来]", + "8763": "[NoWorld_哈哈哈]", + "8764": "[NoWorld_可怕]", + "8765": "[NoWorld_泪目]", + "8766": "[NoWorld_摸鱼]", + "8767": "[NoWorld_你吃桃]", + "8768": "[NoWorld_乾杯]", + "8769": "[NoWorld_燃起来了]", + "8770": "[NoWorld_生气]", + "8771": "[NoWorld_晚安]", + "8772": "[NoWorld_喜欢]", + "8773": "[NoWorld_下次一定]", + "8774": "[NoWorld_谢谢]", + "8775": "[NoWorld_夜猫子]", + "8776": "[NoWorld_在吗?]", + "8777": "[NoWorld_吃布丁]", + "8778": "[泠鸢yousa十周年_wink]", + "8779": "[泠鸢yousa十周年_啊啊啊!]", + "8780": "[泠鸢yousa十周年_没想到吧]", + "8781": "[泠鸢yousa十周年_暗中观察]", + "8782": "[泠鸢yousa十周年_爆哭]", + "8783": "[泠鸢yousa十周年_比心]", + "8784": "[泠鸢yousa十周年_不可以]", + "8785": "[泠鸢yousa十周年_不如跳舞]", + "8786": "[泠鸢yousa十周年_唱歌]", + "8787": "[泠鸢yousa十周年_腹黑]", + "8788": "[泠鸢yousa十周年_喝点甘露]", + "8789": "[泠鸢yousa十周年_麻了]", + "8790": "[泠鸢yousa十周年_辛苦了]", + "8791": "[泠鸢yousa十周年_这我能看?]", + "8792": "[泠鸢yousa十周年_对不起嘛]", + "8793": "[泠鸢yousa十周年_告辞]", + "8794": "[泠鸢yousa十周年_挠头]", + "8795": "[泠鸢yousa十周年_鸟蛋]", + "8796": "[泠鸢yousa十周年_是你?]", + "8797": "[泠鸢yousa十周年_哇!]", + "8798": "[小彼索_暗中观察]", + "8799": "[小彼索_币给你]", + "8800": "[小彼索_无语]", + "8801": "[小彼索_吃瓜]", + "8802": "[小彼索_冲冲冲]", + "8803": "[小彼索_对对对]", + "8804": "[小彼索_干饭]", + "8805": "[小彼索_好气]", + "8806": "[小彼索_生气]", + "8807": "[小彼索_就这]", + "8808": "[小彼索_晚安]", + "8809": "[小彼索_心碎一地]", + "8810": "[小彼索_赞]", + "8811": "[小彼索_震惊]", + "8812": "[小彼索_emo]", + "8813": "[秦淮八艳-小宛_吃瓜]", + "8814": "[秦淮八艳-小宛_发抖]", + "8815": "[秦淮八艳-小宛_害羞]", + "8816": "[秦淮八艳-小宛_好的]", + "8817": "[秦淮八艳-小宛_哼]", + "8818": "[秦淮八艳-小宛_惊吓]", + "8819": "[秦淮八艳-小宛_哭哭]", + "8820": "[秦淮八艳-小宛_困]", + "8821": "[秦淮八艳-小宛_生气]", + "8822": "[秦淮八艳-小宛_送花花]", + "8823": "[秦淮八艳-小宛_投币]", + "8824": "[秦淮八艳-小宛_晚安]", + "8825": "[秦淮八艳-小宛_委屈巴巴]", + "8826": "[秦淮八艳-小宛_无语]", + "8827": "[秦淮八艳-小宛_疑惑]", + "8828": "[刺客信条15周年_?]", + "8829": "[刺客信条15周年_拔刀]", + "8830": "[刺客信条15周年_比心]", + "8831": "[刺客信条15周年_冲呀]", + "8832": "[刺客信条15周年_达成共识]", + "8833": "[刺客信条15周年_害怕]", + "8834": "[刺客信条15周年_划水]", + "8835": "[刺客信条15周年_毁灭吧]", + "8836": "[刺客信条15周年_康]", + "8837": "[刺客信条15周年_哭哭]", + "8838": "[刺客信条15周年_酷]", + "8839": "[刺客信条15周年_乐]", + "8840": "[刺客信条15周年_期待]", + "8841": "[刺客信条15周年_祈祷]", + "8842": "[刺客信条15周年_强大]", + "8843": "[刺客信条15周年_庆祝]", + "8844": "[刺客信条15周年_上天入地]", + "8845": "[刺客信条15周年_失去同步]", + "8846": "[刺客信条15周年_探头]", + "8847": "[刺客信条15周年_危]", + "8848": "[刺客信条15周年_无所畏惧]", + "8849": "[刺客信条15周年_无语]", + "8850": "[刺客信条15周年_吸猫]", + "8851": "[刺客信条15周年_信仰之跃]", + "8852": "[刺客信条15周年_硬币]", + "8853": "[广州TTG_no]", + "8854": "[广州TTG_yes]", + "8855": "[广州TTG_心碎]", + "8856": "[广州TTG_操作起来]", + "8857": "[广州TTG_馋]", + "8858": "[广州TTG_冲鸭]", + "8859": "[广州TTG_戳]", + "8860": "[广州TTG_薅呆毛]", + "8861": "[广州TTG_加餐]", + "8862": "[广州TTG_就这]", + "8863": "[广州TTG_溜了]", + "8864": "[广州TTG_冒头]", + "8865": "[广州TTG_捧花]", + "8866": "[广州TTG_亲亲]", + "8867": "[广州TTG_偷看]", + "8868": "[广州TTG_呜呜]", + "8869": "[广州TTG_芜湖]", + "8870": "[广州TTG_亿点点]", + "8871": "[广州TTG_元气满满]", + "8872": "[广州TTG_窒息]", + "8873": "[纳豆 nado_GKD]", + "8874": "[纳豆 nado_no]", + "8875": "[纳豆 nado_yes]", + "8876": "[纳豆 nado_比心]", + "8877": "[纳豆 nado_馋]", + "8878": "[纳豆 nado_冲鸭]", + "8879": "[纳豆 nado_戳]", + "8880": "[纳豆 nado_薅呆毛]", + "8881": "[纳豆 nado_就这]", + "8882": "[纳豆 nado_老音乐人]", + "8883": "[纳豆 nado_泪目]", + "8884": "[纳豆 nado_冒头]", + "8885": "[纳豆 nado_柠檬精]", + "8886": "[纳豆 nado_亲亲]", + "8887": "[纳豆 nado_舒服了]", + "8888": "[纳豆 nado_晚安]", + "8889": "[纳豆 nado_问号]", + "8890": "[纳豆 nado_洗手]", + "8891": "[纳豆 nado_亿点点]", + "8892": "[纳豆 nado_元气满满]", + "9389": "[装扮小姐姐·秋日午后_mua]", + "9390": "[装扮小姐姐·秋日午后_V我]", + "9391": "[装扮小姐姐·秋日午后_wink]", + "9392": "[装扮小姐姐·秋日午后_买装扮]", + "9393": "[装扮小姐姐·秋日午后_你做得好]", + "9394": "[装扮小姐姐·秋日午后_准点下班]", + "9395": "[装扮小姐姐·秋日午后_别急]", + "9396": "[装扮小姐姐·秋日午后_哈哈哈]", + "9397": "[装扮小姐姐·秋日午后_好耶]", + "9398": "[装扮小姐姐·秋日午后_委屈]", + "9399": "[装扮小姐姐·秋日午后_害羞]", + "9400": "[装扮小姐姐·秋日午后_急急急]", + "9401": "[装扮小姐姐·秋日午后_扮扮糖]", + "9402": "[装扮小姐姐·秋日午后_抽你]", + "9403": "[装扮小姐姐·秋日午后_摸头]", + "9404": "[装扮小姐姐·秋日午后_收到]", + "9405": "[装扮小姐姐·秋日午后_期待]", + "9406": "[装扮小姐姐·秋日午后_比心]", + "9407": "[装扮小姐姐·秋日午后_没问题]", + "9408": "[装扮小姐姐·秋日午后_猜猜猜]", + "9409": "[装扮小姐姐·秋日午后_疑问]", + "9410": "[装扮小姐姐·秋日午后_看看你的]", + "9411": "[装扮小姐姐·秋日午后_累了]", + "9412": "[装扮小姐姐·秋日午后_要抱抱]", + "9413": "[装扮小姐姐·秋日午后_记仇]", + "9414": "[装扮小姐姐·秋日午后_贴贴]", + "9415": "[装扮小姐姐·秋日午后_超凶]", + "9416": "[装扮小姐姐·秋日午后_辛勤搬砖]", + "9417": "[装扮小姐姐·秋日午后_达咩]", + "9418": "[装扮小姐姐·秋日午后_那我呢?]", + "9196": "[阿梓从小就很可爱新装扮_唉]", + "9197": "[阿梓从小就很可爱新装扮_拜托]", + "9198": "[阿梓从小就很可爱新装扮_比心]", + "9199": "[阿梓从小就很可爱新装扮_吃了没]", + "9200": "[阿梓从小就很可爱新装扮_打call]", + "9201": "[阿梓从小就很可爱新装扮_大家好]", + "9202": "[阿梓从小就很可爱新装扮_大舌头]", + "9203": "[阿梓从小就很可爱新装扮_公主驾到]", + "9204": "[阿梓从小就很可爱新装扮_哈哈哈]", + "9205": "[阿梓从小就很可爱新装扮_嘿嘿]", + "9206": "[阿梓从小就很可爱新装扮_就这啊]", + "9207": "[阿梓从小就很可爱新装扮_可恶]", + "9208": "[阿梓从小就很可爱新装扮_哭哭]", + "9209": "[阿梓从小就很可爱新装扮_略略略]", + "9210": "[阿梓从小就很可爱新装扮_嗯呐]", + "9211": "[阿梓从小就很可爱新装扮_捏猫猫滴]", + "9212": "[阿梓从小就很可爱新装扮_拳头硬了]", + "9213": "[阿梓从小就很可爱新装扮_忍]", + "9214": "[阿梓从小就很可爱新装扮_润]", + "9215": "[阿梓从小就很可爱新装扮_晚安]", + "9216": "[阿梓从小就很可爱新装扮_谢谢你哦]", + "9217": "[阿梓从小就很可爱新装扮_一坨好]", + "9218": "[阿梓从小就很可爱新装扮_真牛哇]", + "9219": "[阿梓从小就很可爱新装扮_震撼]", + "9220": "[阿梓从小就很可爱新装扮_正能量]", + "9419": "[2233人生百戏·牡丹亭_放风]", + "9420": "[2233人生百戏·牡丹亭_害羞]", + "9421": "[2233人生百戏·牡丹亭_好想你]", + "9422": "[2233人生百戏·牡丹亭_好耶]", + "9423": "[2233人生百戏·牡丹亭_嘿嘿]", + "9424": "[2233人生百戏·牡丹亭_摸鱼]", + "9425": "[2233人生百戏·牡丹亭_你真好看]", + "9426": "[2233人生百戏·牡丹亭_牛牛牛]", + "9427": "[2233人生百戏·牡丹亭_瞧]", + "9428": "[2233人生百戏·牡丹亭_探头]", + "9429": "[2233人生百戏·牡丹亭_委屈]", + "9430": "[2233人生百戏·牡丹亭_疑问]", + "9431": "[2233人生百戏·牡丹亭_与子偕老]", + "9432": "[2233人生百戏·牡丹亭_执子之手]", + "9433": "[2233人生百戏·牡丹亭_做梦]", + "9434": "[Asaki大人_各位]", + "9435": "[Asaki大人_汗颜]", + "9436": "[Asaki大人_我人晕了]", + "9437": "[Asaki大人_寄]", + "9438": "[Asaki大人_报到]", + "9439": "[Asaki大人_猪脑过载]", + "9440": "[Asaki大人_乖巧]", + "9441": "[Asaki大人_投降]", + "9442": "[Asaki大人_私密马森]", + "9443": "[Asaki大人_谢谢]", + "9444": "[Asaki大人_别急]", + "9445": "[Asaki大人_两眼一黑]", + "9446": "[Asaki大人_恁牛]", + "9447": "[Asaki大人_快逃]", + "9448": "[Asaki大人_哭泣]", + "9449": "[Asaki大人_偷看]", + "9450": "[Asaki大人_惊呆]", + "9451": "[Asaki大人_笑嘻了]", + "9452": "[Asaki大人_好耶]", + "9453": "[Asaki大人_就是就是]", + "9454": "[Asaki大人_你是猪]", + "9455": "[Asaki大人_润了]", + "9456": "[Asaki大人_小丑]", + "9457": "[Asaki大人_拳头嗯了]", + "9458": "[Asaki大人_爱心]", + "9459": "[东爱璃Lovely_?]", + "9460": "[东爱璃Lovely_5555]", + "9461": "[东爱璃Lovely_比心]", + "9462": "[东爱璃Lovely_啵啵]", + "9463": "[东爱璃Lovely_馋]", + "9464": "[东爱璃Lovely_吃草]", + "9465": "[东爱璃Lovely_打call]", + "9466": "[东爱璃Lovely_给你一拳]", + "9467": "[东爱璃Lovely_好辣]", + "9468": "[东爱璃Lovely_可怜]", + "9469": "[东爱璃Lovely_理发店]", + "9470": "[东爱璃Lovely_流汗]", + "9471": "[东爱璃Lovely_略略略]", + "9472": "[东爱璃Lovely_挠]", + "9473": "[东爱璃Lovely_捏脸]", + "9474": "[东爱璃Lovely_扭秧歌]", + "9475": "[东爱璃Lovely_捧碗]", + "9476": "[东爱璃Lovely_祈祷]", + "9477": "[东爱璃Lovely_人形唢呐]", + "9478": "[东爱璃Lovely_扔鸡蛋]", + "9479": "[东爱璃Lovely_傻狍点赞]", + "9480": "[东爱璃Lovely_晚安]", + "9481": "[东爱璃Lovely_我有牙啦]", + "9482": "[东爱璃Lovely_一起上]", + "9483": "[东爱璃Lovely_月光]", + "9484": "[EDG·2022LPL出征_777]", + "9485": "[EDG·2022LPL出征_EDG]", + "9486": "[EDG·2022LPL出征_EDG加油]", + "9487": "[EDG·2022LPL出征_nice]", + "9488": "[EDG·2022LPL出征_solo]", + "9489": "[EDG·2022LPL出征_爱与和平]", + "9490": "[EDG·2022LPL出征_撤退]", + "9491": "[EDG·2022LPL出征_吃瓜]", + "9492": "[EDG·2022LPL出征_达咩]", + "9493": "[EDG·2022LPL出征_打call]", + "9494": "[EDG·2022LPL出征_等我c!]", + "9495": "[EDG·2022LPL出征_放大了]", + "9496": "[EDG·2022LPL出征_好的]", + "9497": "[EDG·2022LPL出征_好气]", + "9498": "[EDG·2022LPL出征_集合打龙]", + "9499": "[EDG·2022LPL出征_连胜]", + "9500": "[EDG·2022LPL出征_裂开]", + "9501": "[EDG·2022LPL出征_面对疾风吧]", + "9502": "[EDG·2022LPL出征_佩服]", + "9503": "[EDG·2022LPL出征_神操作]", + "9504": "[EDG·2022LPL出征_五排]", + "9505": "[EDG·2022LPL出征_爷来了]", + "9506": "[EDG·2022LPL出征_糟糕]", + "9507": "[EDG·2022LPL出征_正在支援]", + "9508": "[EDG·2022LPL出征_最强王者]", + "9509": "[JDG·2022LPL出征_JDG]", + "9510": "[JDG·2022LPL出征_MVP]", + "9511": "[JDG·2022LPL出征_暗中观察]", + "9512": "[JDG·2022LPL出征_奥利给]", + "9513": "[JDG·2022LPL出征_不错]", + "9514": "[JDG·2022LPL出征_差不多得了]", + "9515": "[JDG·2022LPL出征_吃饭]", + "9516": "[JDG·2022LPL出征_冲啊]", + "9517": "[JDG·2022LPL出征_膏丽风情]", + "9518": "[JDG·2022LPL出征_汗颜]", + "9519": "[JDG·2022LPL出征_嘿嘿]", + "9520": "[JDG·2022LPL出征_接招]", + "9521": "[JDG·2022LPL出征_惊]", + "9522": "[JDG·2022LPL出征_就这?]", + "9523": "[JDG·2022LPL出征_满血复活]", + "9524": "[JDG·2022LPL出征_没问题]", + "9525": "[JDG·2022LPL出征_拿来吧]", + "9526": "[JDG·2022LPL出征_slogan]", + "9527": "[JDG·2022LPL出征_千斤兄弟]", + "9528": "[JDG·2022LPL出征_上分]", + "9529": "[JDG·2022LPL出征_上号]", + "9530": "[JDG·2022LPL出征_栓Q]", + "9531": "[JDG·2022LPL出征_委屈]", + "9532": "[JDG·2022LPL出征_潇洒]", + "9533": "[JDG·2022LPL出征_谢谢]", + "9534": "[RNG·2022LPL出征_666]", + "9535": "[RNG·2022LPL出征_emmm]", + "9536": "[RNG·2022LPL出征_RNG]", + "9537": "[RNG·2022LPL出征_霸气]", + "9538": "[RNG·2022LPL出征_比心]", + "9539": "[RNG·2022LPL出征_不会吧]", + "9540": "[RNG·2022LPL出征_超凶的]", + "9541": "[RNG·2022LPL出征_锤一局]", + "9542": "[RNG·2022LPL出征_紧张]", + "9543": "[RNG·2022LPL出征_绝望]", + "9544": "[RNG·2022LPL出征_来抓人了]", + "9545": "[RNG·2022LPL出征_连跪]", + "9546": "[RNG·2022LPL出征_美滋滋]", + "9547": "[RNG·2022LPL出征_逆风翻盘]", + "9548": "[RNG·2022LPL出征_起飞]", + "9549": "[RNG·2022LPL出征_求求]", + "9550": "[RNG·2022LPL出征_人呢?]", + "9551": "[RNG·2022LPL出征_收到]", + "9552": "[RNG·2022LPL出征_晚安]", + "9553": "[RNG·2022LPL出征_委屈]", + "9554": "[RNG·2022LPL出征_稳住]", + "9555": "[RNG·2022LPL出征_要坚强]", + "9556": "[RNG·2022LPL出征_耶]", + "9557": "[RNG·2022LPL出征_优秀]", + "9558": "[RNG·2022LPL出征_有毒]", + "9559": "[TES·2022LPL出征_love]", + "9560": "[TES·2022LPL出征_big胆]", + "9561": "[TES·2022LPL出征_GG]", + "9562": "[TES·2022LPL出征_okok]", + "9563": "[TES·2022LPL出征_TES]", + "9564": "[TES·2022LPL出征_爆金币]", + "9565": "[TES·2022LPL出征_超神]", + "9566": "[TES·2022LPL出征_冲啊]", + "9567": "[TES·2022LPL出征_大佬带飞]", + "9568": "[TES·2022LPL出征_大招有了]", + "9569": "[TES·2022LPL出征_点赞]", + "9570": "[TES·2022LPL出征_好运滔搏]", + "9572": "[TES·2022LPL出征_加油]", + "9571": "[TES·2022LPL出征_寄了]", + "9573": "[TES·2022LPL出征_敬礼]", + "9574": "[TES·2022LPL出征_开黑]", + "9575": "[TES·2022LPL出征_哭哭]", + "9576": "[TES·2022LPL出征_请求集合]", + "9577": "[TES·2022LPL出征_闪现]", + "9578": "[TES·2022LPL出征_胜利]", + "9579": "[TES·2022LPL出征_收到]", + "9580": "[TES·2022LPL出征_我太难了]", + "9581": "[TES·2022LPL出征_无语]", + "9582": "[TES·2022LPL出征_五杀]", + "9583": "[TES·2022LPL出征_疑问]", + "9584": "[明前奶绿_在吗]", + "9585": "[明前奶绿_?]", + "9586": "[明前奶绿_fa店]", + "9587": "[明前奶绿_MA]", + "9588": "[明前奶绿_抱歉]", + "9589": "[明前奶绿_别急]", + "9590": "[明前奶绿_呃呃]", + "9591": "[明前奶绿_放弃]", + "9592": "[明前奶绿_嘿嘿]", + "9593": "[明前奶绿_哼哼]", + "9594": "[明前奶绿_花花]", + "9595": "[明前奶绿_灰头土脸]", + "9596": "[明前奶绿_加油]", + "9597": "[明前奶绿_惊讶]", + "9598": "[明前奶绿_懒]", + "9599": "[明前奶绿_嗯嗯]", + "9600": "[明前奶绿_起床]", + "9601": "[明前奶绿_亲亲]", + "9602": "[明前奶绿_确实]", + "9603": "[明前奶绿_生气]", + "9604": "[明前奶绿_我会了]", + "9605": "[明前奶绿_蟹蟹!]", + "9606": "[明前奶绿_早安]", + "9607": "[明前奶绿_这是什么]", + "9608": "[明前奶绿_真的假的?]", + "9609": "[PSG.LGD_标记]", + "9610": "[PSG.LGD_别急]", + "9611": "[PSG.LGD_钓鱼]", + "9612": "[PSG.LGD_反了是吧]", + "9613": "[PSG.LGD_尴尬]", + "9614": "[PSG.LGD_给力]", + "9615": "[PSG.LGD_咳嗽]", + "9616": "[PSG.LGD_肯定]", + "9617": "[PSG.LGD_令人兴奋]", + "9618": "[PSG.LGD_妈呀]", + "9619": "[PSG.LGD_猫叫]", + "9620": "[PSG.LGD_问号]", + "9621": "[PSG.LGD_休息]", + "9622": "[PSG.LGD_疑惑]", + "9623": "[PSG.LGD_真的帅]", + "9624": "[RNG DOTA2战队_不想玩辣]", + "9625": "[RNG DOTA2战队_昌平野人]", + "9626": "[RNG DOTA2战队_基地]", + "9627": "[RNG DOTA2战队_卡卡惊讶]", + "9628": "[RNG DOTA2战队_卡卡笑]", + "9629": "[RNG DOTA2战队_可爱的捏]", + "9630": "[RNG DOTA2战队_狂]", + "9631": "[RNG DOTA2战队_流冷汗]", + "9632": "[RNG DOTA2战队_你们一起上]", + "9633": "[RNG DOTA2战队_缺牙笑]", + "9634": "[RNG DOTA2战队_委屈]", + "9635": "[RNG DOTA2战队_小天使]", + "9636": "[RNG DOTA2战队_摇人]", + "9637": "[RNG DOTA2战队_英雄]", + "9638": "[RNG DOTA2战队_自然神]", + "9640": "[DOTA2 2022年国际邀请赛_666]", + "9641": "[DOTA2 2022年国际邀请赛_emmm]", + "9642": "[DOTA2 2022年国际邀请赛_ok]", + "9643": "[DOTA2 2022年国际邀请赛_V我37]", + "9644": "[DOTA2 2022年国际邀请赛_爱你]", + "9645": "[DOTA2 2022年国际邀请赛_背锅]", + "9646": "[DOTA2 2022年国际邀请赛_队友呢]", + "9647": "[DOTA2 2022年国际邀请赛_告辞]", + "9648": "[DOTA2 2022年国际邀请赛_鸽了]", + "9649": "[DOTA2 2022年国际邀请赛_给你脸了]", + "9650": "[DOTA2 2022年国际邀请赛_哈哈哈]", + "9651": "[DOTA2 2022年国际邀请赛_害羞]", + "9652": "[DOTA2 2022年国际邀请赛_划起来咯]", + "9653": "[DOTA2 2022年国际邀请赛_击掌]", + "9654": "[DOTA2 2022年国际邀请赛_技不如人]", + "9655": "[DOTA2 2022年国际邀请赛_加油]", + "9656": "[DOTA2 2022年国际邀请赛_救救救]", + "9657": "[DOTA2 2022年国际邀请赛_哭泣]", + "9658": "[DOTA2 2022年国际邀请赛_来一局]", + "9659": "[DOTA2 2022年国际邀请赛_哦]", + "9660": "[DOTA2 2022年国际邀请赛_漂亮]", + "9661": "[DOTA2 2022年国际邀请赛_润了]", + "9662": "[DOTA2 2022年国际邀请赛_上分机器]", + "9663": "[DOTA2 2022年国际邀请赛_来砍我]", + "9664": "[DOTA2 2022年国际邀请赛_摊手]", + "9665": "[DOTA2 2022年国际邀请赛_弯道超车]", + "9666": "[DOTA2 2022年国际邀请赛_晚安]", + "9667": "[DOTA2 2022年国际邀请赛_我是谁]", + "9668": "[DOTA2 2022年国际邀请赛_我只会心疼]", + "9669": "[DOTA2 2022年国际邀请赛_小飞棍来咯]", + "9670": "[DOTA2 2022年国际邀请赛_谢谢]", + "9671": "[DOTA2 2022年国际邀请赛_蟹不肉]", + "9672": "[DOTA2 2022年国际邀请赛_心态崩了]", + "9673": "[DOTA2 2022年国际邀请赛_秀]", + "9674": "[DOTA2 2022年国际邀请赛_真香]", + "9675": "[点满农民相关技能后,不知为何就变强了_搬砖]", + "9676": "[点满农民相关技能后,不知为何就变强了_不妙]", + "9677": "[点满农民相关技能后,不知为何就变强了_乖巧]", + "9678": "[点满农民相关技能后,不知为何就变强了_害羞]", + "9679": "[点满农民相关技能后,不知为何就变强了_好的]", + "9680": "[点满农民相关技能后,不知为何就变强了_嘿嘿]", + "9681": "[点满农民相关技能后,不知为何就变强了_胡说]", + "9682": "[点满农民相关技能后,不知为何就变强了_看看]", + "9683": "[点满农民相关技能后,不知为何就变强了_命苦]", + "9684": "[点满农民相关技能后,不知为何就变强了_哦]", + "9685": "[点满农民相关技能后,不知为何就变强了_亲亲]", + "9686": "[点满农民相关技能后,不知为何就变强了_燃起来]", + "9687": "[点满农民相关技能后,不知为何就变强了_什么]", + "9688": "[点满农民相关技能后,不知为何就变强了_酸Q]", + "9689": "[点满农民相关技能后,不知为何就变强了_贴贴]", + "9690": "[点满农民相关技能后,不知为何就变强了_委屈]", + "9691": "[点满农民相关技能后,不知为何就变强了_笑]", + "9692": "[点满农民相关技能后,不知为何就变强了_炫饭]", + "9693": "[点满农民相关技能后,不知为何就变强了_在吗]", + "9694": "[点满农民相关技能后,不知为何就变强了_抓住]", + "9695": "[陆夫人的Flag园_omg]", + "9696": "[陆夫人的Flag园_www]", + "9697": "[陆夫人的Flag园_xxlfr]", + "9698": "[陆夫人的Flag园_比心]", + "9699": "[陆夫人的Flag园_闭嘴]", + "9700": "[陆夫人的Flag园_不行]", + "9701": "[陆夫人的Flag园_插旗]", + "9702": "[陆夫人的Flag园_吃瓜]", + "9703": "[陆夫人的Flag园_盯]", + "9704": "[陆夫人的Flag园_灌奶]", + "9705": "[陆夫人的Flag园_好耶]", + "9706": "[陆夫人的Flag园_急]", + "9707": "[陆夫人的Flag园_记得痛饮]", + "9708": "[陆夫人的Flag园_寄]", + "9709": "[陆夫人的Flag园_看戏]", + "9710": "[陆夫人的Flag园_哭]", + "9711": "[陆夫人的Flag园_快跑]", + "9712": "[陆夫人的Flag园_牛哇]", + "9713": "[陆夫人的Flag园_迫击炮]", + "9714": "[陆夫人的Flag园_趣味生煎]", + "9715": "[陆夫人的Flag园_睡了]", + "9716": "[陆夫人的Flag园_问号]", + "9717": "[陆夫人的Flag园_无语]", + "9718": "[陆夫人的Flag园_歇了]", + "9719": "[陆夫人的Flag园_这锅不背]", + "9721": "[暹罗猫小豆泥 · 遇见泥_爱了爱了]", + "9722": "[暹罗猫小豆泥 · 遇见泥_爱泥]", + "9723": "[暹罗猫小豆泥 · 遇见泥_冲]", + "9724": "[暹罗猫小豆泥 · 遇见泥_盯]", + "9725": "[暹罗猫小豆泥 · 遇见泥_哈哈哈]", + "9726": "[暹罗猫小豆泥 · 遇见泥_麻了]", + "9727": "[暹罗猫小豆泥 · 遇见泥_摸鱼]", + "9728": "[暹罗猫小豆泥 · 遇见泥_莫挨老子]", + "9729": "[暹罗猫小豆泥 · 遇见泥_你不对劲]", + "9730": "[暹罗猫小豆泥 · 遇见泥_奇怪的知识]", + "9731": "[暹罗猫小豆泥 · 遇见泥_请你]", + "9732": "[暹罗猫小豆泥 · 遇见泥_让我看看]", + "9733": "[暹罗猫小豆泥 · 遇见泥_散了散了]", + "9734": "[暹罗猫小豆泥 · 遇见泥_生日快乐]", + "9735": "[暹罗猫小豆泥 · 遇见泥_我是学生]", + "9736": "[暹罗猫小豆泥 · 遇见泥_星星眼]", + "9737": "[暹罗猫小豆泥 · 遇见泥_休息]", + "9738": "[暹罗猫小豆泥 · 遇见泥_一键三连]", + "9739": "[暹罗猫小豆泥 · 遇见泥_噫]", + "9740": "[暹罗猫小豆泥 · 遇见泥_针不戳]", + "9741": "[一二和布布_还挺牛]", + "9742": "[一二和布布_爱你哦]", + "9743": "[一二和布布_酷]", + "9744": "[一二和布布_警告]", + "9745": "[一二和布布_好哒]", + "9746": "[一二和布布_额]", + "9747": "[一二和布布_达咩]", + "9748": "[一二和布布_比心]", + "9749": "[一二和布布_走了]", + "9750": "[一二和布布_让我看看]", + "9751": "[一二和布布_哇哦]", + "9752": "[一二和布布_拳头]", + "9753": "[一二和布布_不想理你]", + "9754": "[一二和布布_晕]", + "9755": "[一二和布布_偷看]", + "9756": "[一二和布布_ok]", + "9757": "[一二和布布_勾引]", + "9758": "[一二和布布_可爱]", + "9759": "[一二和布布_拉手手]", + "9760": "[一二和布布_敬礼]", + "9761": "[一二和布布_开心]", + "9762": "[一二和布布_摸头]", + "9763": "[一二和布布_哦]", + "9764": "[一二和布布_贴贴]", + "9765": "[一二和布布_悲伤]", + "9766": "[一二和布布_脑阔疼]", + "9767": "[一二和布布_耶]", + "9768": "[一二和布布_等你]", + "9769": "[一二和布布_卖萌]", + "9770": "[一二和布布_嗨起来]", + "9887": "[一二和布布_大佬坐姿]", + "9888": "[一二和布布_站岗]", + "9889": "[一二和布布_大冤种]", + "9890": "[一二和布布_欺负布布]", + "9891": "[一二和布布_抽我]", + "9771": "[炼气练了3000年_比心]", + "9772": "[炼气练了3000年_端庄]", + "9773": "[炼气练了3000年_期待]", + "9774": "[炼气练了3000年_亲亲]", + "9775": "[炼气练了3000年_时间到了]", + "9776": "[炼气练了3000年_瘫]", + "9777": "[炼气练了3000年_提]", + "9778": "[炼气练了3000年_元气满满]", + "9779": "[炼气练了3000年_筑基成功]", + "9780": "[炼气练了3000年_no]", + "9792": "[胡桃Usa_开心]", + "9798": "[胡桃Usa_生日快乐]", + "9784": "[胡桃Usa_吃我一拳]", + "9787": "[胡桃Usa_对不起]", + "9783": "[胡桃Usa_擦汗]", + "9801": "[胡桃Usa_永远爱你]", + "9788": "[胡桃Usa_鹅鹅鹅]", + "9786": "[胡桃Usa_大喇叭]", + "9791": "[胡桃Usa_惊讶]", + "9782": "[胡桃Usa_比心]", + "9797": "[胡桃Usa_生气]", + "9795": "[胡桃Usa_困困]", + "9789": "[胡桃Usa_饿饿]", + "9794": "[胡桃Usa_哭哭]", + "9793": "[胡桃Usa_可怜]", + "9800": "[胡桃Usa_歪]", + "9785": "[胡桃Usa_打call]", + "9790": "[胡桃Usa_攻击]", + "9796": "[胡桃Usa_闪亮登场]", + "9799": "[胡桃Usa_贴贴]", + "9802": "[明日方舟·音律联觉-灯下定影_得意]", + "9803": "[明日方舟·音律联觉-灯下定影_点赞]", + "9804": "[明日方舟·音律联觉-灯下定影_哈欠]", + "9805": "[明日方舟·音律联觉-灯下定影_喝茶]", + "9806": "[明日方舟·音律联觉-灯下定影_就是你了]", + "9807": "[明日方舟·音律联觉-灯下定影_可爱]", + "9808": "[明日方舟·音律联觉-灯下定影_热情]", + "9809": "[明日方舟·音律联觉-灯下定影_叹气]", + "9810": "[明日方舟·音律联觉-灯下定影_无奈]", + "9811": "[明日方舟·音律联觉-灯下定影_正在记录]", + "9812": "[DOTA2 dodo_吃面]", + "9813": "[DOTA2 dodo_酬勤]", + "9814": "[DOTA2 dodo_大爱心眼]", + "9815": "[DOTA2 dodo_大笑]", + "9816": "[DOTA2 dodo_盖小被]", + "9817": "[DOTA2 dodo_鼓掌]", + "9818": "[DOTA2 dodo_僵住]", + "9819": "[DOTA2 dodo_紧张]", + "9820": "[DOTA2 dodo_卡兵了]", + "9821": "[DOTA2 dodo_开心]", + "9822": "[DOTA2 dodo_哭哭]", + "9823": "[DOTA2 dodo_愣住]", + "9824": "[DOTA2 dodo_两眼一黑]", + "9825": "[DOTA2 dodo_牛牛]", + "9826": "[DOTA2 dodo_上分]", + "9827": "[DOTA2 dodo_生气]", + "9828": "[DOTA2 dodo_省略号]", + "9829": "[DOTA2 dodo_问号]", + "9830": "[DOTA2 dodo_阴险坏笑 ]", + "9831": "[DOTA2 dodo_自闭]", + "9832": "[童话系列·阿拉丁_mua]", + "9833": "[童话系列·阿拉丁_wink]", + "9834": "[童话系列·阿拉丁_啊对对对]", + "9835": "[童话系列·阿拉丁_暗中观察]", + "9836": "[童话系列·阿拉丁_比心]", + "9837": "[童话系列·阿拉丁_害羞]", + "9838": "[童话系列·阿拉丁_寄]", + "9839": "[童话系列·阿拉丁_惊]", + "9840": "[童话系列·阿拉丁_摸摸头]", + "9841": "[童话系列·阿拉丁_奶思]", + "9842": "[童话系列·阿拉丁_拍手手]", + "9843": "[童话系列·阿拉丁_期待]", + "9844": "[童话系列·阿拉丁_撒娇]", + "9845": "[童话系列·阿拉丁_生气气]", + "9846": "[童话系列·阿拉丁_思考]", + "9847": "[童话系列·阿拉丁_叹气]", + "9848": "[童话系列·阿拉丁_贴贴]", + "9849": "[童话系列·阿拉丁_偷笑]", + "9850": "[童话系列·阿拉丁_芜湖]", + "9851": "[童话系列·阿拉丁_捂脸]", + "9852": "[童话系列·阿拉丁_谢谢]", + "9853": "[童话系列·阿拉丁_许愿]", + "9854": "[童话系列·阿拉丁_疑问]", + "9855": "[童话系列·阿拉丁_急急国王]", + "9856": "[童话系列·阿拉丁_装睡]", + "9857": "[猫不理咖啡_。。。]", + "9858": "[猫不理咖啡_?!]", + "9859": "[猫不理咖啡_wink]", + "9860": "[猫不理咖啡_啊这]", + "9861": "[猫不理咖啡_安详]", + "9862": "[猫不理咖啡_暗中观察]", + "9863": "[猫不理咖啡_傲视群雄]", + "9864": "[猫不理咖啡_打你]", + "9865": "[猫不理咖啡_大佬坐姿]", + "9866": "[猫不理咖啡_呆住]", + "9867": "[猫不理咖啡_饿了]", + "9868": "[猫不理咖啡_刚醒]", + "9869": "[猫不理咖啡_高雅]", + "9870": "[猫不理咖啡_给我]", + "9871": "[猫不理咖啡_乖巧]", + "9872": "[猫不理咖啡_好爽]", + "9873": "[猫不理咖啡_嗯?]", + "9874": "[猫不理咖啡_你瞅啥]", + "9875": "[猫不理咖啡_凝视]", + "9876": "[猫不理咖啡_让我看看]", + "9877": "[猫不理咖啡_生气]", + "9878": "[猫不理咖啡_思考]", + "9879": "[猫不理咖啡_瘫]", + "9880": "[猫不理咖啡_探头]", + "9881": "[猫不理咖啡_呜呜]", + "9882": "[猫不理咖啡_无语]", + "9883": "[猫不理咖啡_吸溜]", + "9884": "[猫不理咖啡_嫌弃]", + "9885": "[猫不理咖啡_谢谢]", + "9886": "[猫不理咖啡_装傻]", + "9892": "[花木兰_抱拳]", + "9893": "[花木兰_叉腰]", + "9894": "[花木兰_嘲笑]", + "9895": "[花木兰_打你]", + "9896": "[花木兰_点赞]", + "9897": "[花木兰_告辞]", + "9898": "[花木兰_害羞]", + "9899": "[花木兰_嘿嘿]", + "9900": "[花木兰_练功]", + "9901": "[花木兰_拿捏]", + "9902": "[花木兰_生气]", + "9903": "[花木兰_替父从军]", + "9904": "[花木兰_我没事]", + "9905": "[花木兰_无所畏惧]", + "9906": "[花木兰_心碎]", + "9907": "[兰音_傲娇兔]", + "9908": "[兰音_百万调音师]", + "9909": "[兰音_别急]", + "9910": "[兰音_不想说话]", + "9911": "[兰音_吃瓜]", + "9912": "[兰音_打嗝]", + "9913": "[兰音_胆小兔]", + "9914": "[兰音_钓鱼]", + "9915": "[兰音_歌引青鸟]", + "9916": "[兰音_急了]", + "9917": "[兰音_捡垃圾]", + "9918": "[兰音_理发店]", + "9919": "[兰音_妈妈]", + "9920": "[兰音_女装大佬]", + "9921": "[兰音_求求惹]", + "9922": "[兰音_生气]", + "9923": "[兰音_算术王]", + "9924": "[兰音_贴贴]", + "9925": "[兰音_兔脑过载]", + "9926": "[兰音_歪嘴战神]", + "9927": "[兰音_笑哭]", + "9928": "[兰音_屑师]", + "9929": "[兰音_心疼了]", + "9930": "[兰音_一脸问号]", + "9931": "[兰音_百晕了]", + "9932": "[NHOT BOT首套专属装扮_?????]", + "9933": "[NHOT BOT首套专属装扮_???]", + "9934": "[NHOT BOT首套专属装扮_DD]", + "9935": "[NHOT BOT首套专属装扮_Debu]", + "9936": "[NHOT BOT首套专属装扮_哎呀]", + "9937": "[NHOT BOT首套专属装扮_薄荷牛]", + "9938": "[NHOT BOT首套专属装扮_不可以]", + "9939": "[NHOT BOT首套专属装扮_财布]", + "9940": "[NHOT BOT首套专属装扮_吃饭]", + "9941": "[NHOT BOT首套专属装扮_达咩]", + "9942": "[NHOT BOT首套专属装扮_大声密谋]", + "9943": "[NHOT BOT首套专属装扮_多喝水]", + "9944": "[NHOT BOT首套专属装扮_干杯]", + "9945": "[NHOT BOT首套专属装扮_好吃]", + "9946": "[NHOT BOT首套专属装扮_好耶]", + "9947": "[NHOT BOT首套专属装扮_花园猫]", + "9948": "[NHOT BOT首套专属装扮_幻士]", + "9949": "[NHOT BOT首套专属装扮_您来啦]", + "9950": "[NHOT BOT首套专属装扮_起床]", + "9951": "[NHOT BOT首套专属装扮_土下座]", + "9952": "[NHOT BOT首套专属装扮_哇库哇库]", + "9953": "[NHOT BOT首套专属装扮_晚安]", + "9954": "[NHOT BOT首套专属装扮_我很可爱]", + "9955": "[NHOT BOT首套专属装扮_物资民]", + "9956": "[NHOT BOT首套专属装扮_小雨伞]", + "9957": "[仓鼠_阿巴]", + "9958": "[仓鼠_爱心]", + "9959": "[仓鼠_变傻喷雾]", + "9960": "[仓鼠_仓鼠迷彩]", + "9961": "[仓鼠_催稿]", + "9962": "[仓鼠_口水]", + "9963": "[仓鼠_脸红]", + "9964": "[仓鼠_跑轮子]", + "9965": "[仓鼠_呛鼠]", + "9966": "[仓鼠_睡了]", + "9967": "[仓鼠_甜甜圈]", + "9968": "[仓鼠_甜我姥姥]", + "9969": "[仓鼠_无语]", + "9970": "[仓鼠_五档风扇]", + "9971": "[仓鼠_震惊]", + "9984": "[蕾蕾大表哥_no]", + "9985": "[蕾蕾大表哥_yes]", + "9986": "[蕾蕾大表哥_抱抱]", + "9987": "[蕾蕾大表哥_比心]", + "9988": "[蕾蕾大表哥_测码]", + "9989": "[蕾蕾大表哥_地蕾]", + "9990": "[蕾蕾大表哥_耳机一歪]", + "9991": "[蕾蕾大表哥_欢呼]", + "9992": "[蕾蕾大表哥_火大]", + "9993": "[蕾蕾大表哥_就这]", + "9994": "[蕾蕾大表哥_辣眼睛]", + "9995": "[蕾蕾大表哥_流汗]", + "9996": "[蕾蕾大表哥_美妙开局]", + "9997": "[蕾蕾大表哥_拿来吧你]", + "9998": "[蕾蕾大表哥_嗯?]", + "9999": "[蕾蕾大表哥_弱爆]", + "10000": "[蕾蕾大表哥_喂喂喂]", + "10001": "[蕾蕾大表哥_嘻嘻]", + "10002": "[蕾蕾大表哥_早八]", + "10003": "[蕾蕾大表哥_站长推荐]", + "10004": "[CEN_zzZ]", + "10005": "[CEN_安详]", + "10006": "[CEN_沉思]", + "10007": "[CEN_吃瓜]", + "10008": "[CEN_哒咩]", + "10009": "[CEN_打卡]", + "10010": "[CEN_躲进小被几]", + "10011": "[CEN_好耶]", + "10012": "[CEN_黑线]", + "10013": "[CEN_花花]", + "10014": "[CEN_开创]", + "10015": "[CEN_看戏]", + "10016": "[CEN_啃啃]", + "10017": "[CEN_哭哭]", + "10018": "[CEN_苦涩]", + "10019": "[CEN_乐]", + "10020": "[CEN_愣住]", + "10021": "[CEN_流汗]", + "10022": "[CEN_摸摸]", + "10023": "[CEN_破防]", + "10024": "[CEN_小丑]", + "10025": "[CEN_咬牙切齿]", + "10026": "[CEN_一库搜]", + "10027": "[CEN_疑惑]", + "10028": "[CEN_赞]", + "10029": "[守望先锋_BWEEE]", + "10030": "[守望先锋_OK]", + "10031": "[守望先锋_拜拜]", + "10032": "[守望先锋_交给我吧]", + "10033": "[守望先锋_打扰一下]", + "10034": "[守望先锋_冻住]", + "10035": "[守望先锋_飞吻]", + "10036": "[守望先锋_感受宁静]", + "10037": "[守望先锋_嗨起来]", + "10038": "[守望先锋_惊讶]", + "10039": "[守望先锋_看风景呢]", + "10040": "[守望先锋_猎空谢谢]", + "10041": "[守望先锋_对的]", + "10042": "[守望先锋_卖萌]", + "10043": "[守望先锋_哪里跑]", + "10044": "[守望先锋_奶我]", + "10045": "[守望先锋_你被强化了]", + "10046": "[守望先锋_贴贴]", + "10047": "[守望先锋_完美]", + "10048": "[守望先锋_我看到你了]", + "10049": "[守望先锋_午时已到]", + "10050": "[守望先锋_强壮]", + "10051": "[守望先锋_疑惑]", + "10052": "[守望先锋_英雄不朽]", + "10053": "[守望先锋_在这里集合]", + "10055": "[UPOWER_1555090958_哭了]", + "10056": "[UPOWER_1555090958_汉堡]", + "10057": "[UPOWER_1555090958_射箭]", + "10058": "[UPOWER_1555090958_沉默]", + "10059": "[UPOWER_1555090958_开心]", + "10073": "[杨戬_嘤嘤嘤]", + "10068": "[杨戬_撒娇]", + "10060": "[杨戬_比心]", + "10066": "[杨戬_泪目]", + "10072": "[杨戬_歇了]", + "10069": "[杨戬_赏银来了]", + "10074": "[杨戬_悠闲]", + "10065": "[杨戬_嘛呢]", + "10063": "[杨戬_看开了]", + "10070": "[杨戬_生气]", + "10061": "[杨戬_戳]", + "10067": "[杨戬_媚笑]", + "10071": "[杨戬_退退退]", + "10064": "[杨戬_老脸一红]", + "10062": "[杨戬_干杯]", + "10075": "[UPOWER_1437849928_竹子]", + "10076": "[UPOWER_1437849928_鲸鱼]", + "10077": "[UPOWER_1437849928_叶子]", + "10078": "[UPOWER_1437849928_握手]", + "10080": "[塔克Tako-时湖万象_不关我事]", + "10081": "[塔克Tako-时湖万象_不满意?]", + "10082": "[塔克Tako-时湖万象_呃呃OK]", + "10083": "[塔克Tako-时湖万象_哈啊?]", + "10084": "[塔克Tako-时湖万象_哈哈]", + "10085": "[塔克Tako-时湖万象_好鸭]", + "10086": "[塔克Tako-时湖万象_监工]", + "10087": "[塔克Tako-时湖万象_瞄]", + "10088": "[塔克Tako-时湖万象_男瓜]", + "10089": "[塔克Tako-时湖万象_难绷]", + "10090": "[塔克Tako-时湖万象_你抱井吧]", + "10091": "[塔克Tako-时湖万象_你最好是]", + "10092": "[塔克Tako-时湖万象_请用茶]", + "10093": "[塔克Tako-时湖万象_求求]", + "10094": "[塔克Tako-时湖万象_惹]", + "10095": "[塔克Tako-时湖万象_收收味]", + "10096": "[塔克Tako-时湖万象_我抱井了]", + "10097": "[塔克Tako-时湖万象_我碟]", + "10098": "[塔克Tako-时湖万象_我先哭]", + "10099": "[塔克Tako-时湖万象_小丑]", + "10100": "[塔克Tako-时湖万象_削发为尼]", + "10101": "[塔克Tako-时湖万象_怨]", + "10102": "[塔克Tako-时湖万象_栽种]", + "10103": "[塔克Tako-时湖万象_猪瘾犯了]", + "10104": "[塔克Tako-时湖万象_呲牙咧嘴]", + "10105": "[肥肥鲨_YYDS]", + "10106": "[肥肥鲨_阿巴阿巴]", + "10107": "[肥肥鲨_搬砖人]", + "10108": "[肥肥鲨_菜]", + "10109": "[肥肥鲨_愁]", + "10110": "[肥肥鲨_创你]", + "10111": "[肥肥鲨_达咩]", + "10112": "[肥肥鲨_烦死了]", + "10113": "[肥肥鲨_格局小了]", + "10114": "[肥肥鲨_孤寡]", + "10115": "[肥肥鲨_红眼]", + "10116": "[肥肥鲨_可怜]", + "10117": "[肥肥鲨_拿来吧你]", + "10118": "[肥肥鲨_你礼貌吗]", + "10119": "[肥肥鲨_你醒啦]", + "10120": "[肥肥鲨_容我说几句]", + "10121": "[肥肥鲨_杀马特]", + "10122": "[肥肥鲨_生日快乐]", + "10123": "[肥肥鲨_酸]", + "10124": "[肥肥鲨_我不!]", + "10125": "[肥肥鲨_下头]", + "10126": "[肥肥鲨_小丑是我]", + "10127": "[肥肥鲨_疑问]", + "10128": "[肥肥鲨_勇敢鲨鲨]", + "10129": "[肥肥鲨_指到的人]", + "10130": "[瀬兎一也_爱你]", + "10131": "[瀬兎一也_拜托了]", + "10132": "[瀬兎一也_不可以]", + "10133": "[瀬兎一也_吃惊]", + "10134": "[瀬兎一也_大家好]", + "10135": "[瀬兎一也_叼花]", + "10136": "[瀬兎一也_对不起]", + "10137": "[瀬兎一也_感动]", + "10138": "[瀬兎一也_给你点赞]", + "10139": "[瀬兎一也_害怕]", + "10140": "[瀬兎一也_害羞]", + "10141": "[瀬兎一也_好奇]", + "10142": "[瀬兎一也_黑暗料理]", + "10143": "[瀬兎一也_坏笑]", + "10144": "[瀬兎一也_哭哭]", + "10145": "[瀬兎一也_困了]", + "10146": "[瀬兎一也_流汗了]", + "10147": "[瀬兎一也_迷惑]", + "10148": "[瀬兎一也_努力工作]", + "10149": "[瀬兎一也_亲亲]", + "10150": "[瀬兎一也_生气]", + "10151": "[瀬兎一也_玩游戏]", + "10152": "[瀬兎一也_无语]", + "10153": "[瀬兎一也_谢谢]", + "10154": "[瀬兎一也_阴险]", + "10155": "[小蝙蝠呜呣呜呣_?]", + "10156": "[小蝙蝠呜呣呜呣_啊这]", + "10157": "[小蝙蝠呜呣呜呣_嗷呜]", + "10158": "[小蝙蝠呜呣呜呣_不许捣乱]", + "10159": "[小蝙蝠呜呣呜呣_饿了]", + "10160": "[小蝙蝠呜呣呜呣_给糖]", + "10161": "[小蝙蝠呜呣呜呣_呵]", + "10162": "[小蝙蝠呜呣呜呣_开动!]", + "10163": "[小蝙蝠呜呣呜呣_眠眠了]", + "10164": "[小蝙蝠呜呣呜呣_糖果]", + "10165": "[小蝙蝠呜呣呜呣_委屈]", + "10166": "[小蝙蝠呜呣呜呣_呜呣]", + "10167": "[小蝙蝠呜呣呜呣_无语]", + "10168": "[小蝙蝠呜呣呜呣_吓你]", + "10169": "[小蝙蝠呜呣呜呣_震惊]", + "10170": "[小小约yoo_BYD]", + "10171": "[小小约yoo_别啊]", + "10172": "[小小约yoo_别发颤]", + "10173": "[小小约yoo_不过如此]", + "10174": "[小小约yoo_不是吧]", + "10175": "[小小约yoo_低情商]", + "10176": "[小小约yoo_高情商]", + "10177": "[小小约yoo_火]", + "10178": "[小小约yoo_急了]", + "10179": "[小小约yoo_就好这口]", + "10180": "[小小约yoo_跨个批脸]", + "10181": "[小小约yoo_泪目]", + "10182": "[小小约yoo_流汗]", + "10183": "[小小约yoo_爆大米]", + "10184": "[小小约yoo_摸头]", + "10185": "[小小约yoo_拿捏]", + "10186": "[小小约yoo_祈祷]", + "10187": "[小小约yoo_切割]", + "10188": "[小小约yoo_让我看看]", + "10189": "[小小约yoo_啥]", + "10190": "[小小约yoo_舔]", + "10191": "[小小约yoo_投降]", + "10192": "[小小约yoo_小丑]", + "10193": "[小小约yoo_中肯]", + "10509": "[小小约yoo_惊呆了]", + "10194": "[足球啦啦队_比心]", + "10195": "[足球啦啦队_冲]", + "10196": "[足球啦啦队_燃起来了]", + "10197": "[足球啦啦队_大哭]", + "10198": "[足球啦啦队_夺冠]", + "10199": "[足球啦啦队_害]", + "10200": "[足球啦啦队_好球]", + "10201": "[足球啦啦队_进球啦]", + "10202": "[足球啦啦队_认真看球]", + "10203": "[足球啦啦队_退钱]", + "10204": "[足球啦啦队_捂脸]", + "10205": "[足球啦啦队_压着打]", + "10206": "[足球啦啦队_一脸期待]", + "10207": "[足球啦啦队_震惊]", + "10208": "[足球啦啦队_助威]", + "10209": "[BURBERRY_WOW]", + "10210": "[BURBERRY_走起]", + "10211": "[BURBERRY_OK]", + "10212": "[BURBERRY_看着不错]", + "10213": "[BURBERRY_快了快了]", + "10214": "[BURBERRY_等等]", + "10215": "[BURBERRY_再见]", + "10216": "[BURBERRY_谢谢]", + "10217": "[BURBERRY_你好]", + "10218": "[BURBERRY_很酷]", + "10244": "[UPOWER_423895_mua]", + "10245": "[UPOWER_423895_惊了]", + "10246": "[UPOWER_423895_看呆]", + "10247": "[UPOWER_423895_你说呢]", + "10248": "[UPOWER_423895_哦哟]", + "10249": "[UPOWER_423895_顺从]", + "10250": "[UPOWER_423895_哇哦]", + "10251": "[UPOWER_423895_呀]", + "10252": "[UPOWER_423895_扬唇一笑]", + "10253": "[UPOWER_423895_吔]", + "10254": "[眠眠兔_吃瓜群众]", + "10255": "[眠眠兔_啃啃硬币]", + "10256": "[眠眠兔_困困]", + "10257": "[眠眠兔_疲惫]", + "10258": "[眠眠兔_偷看]", + "10259": "[眠眠兔_哇哦]", + "10260": "[眠眠兔_无语]", + "10261": "[眠眠兔_喜欢]", + "10262": "[眠眠兔_疑惑]", + "10263": "[眠眠兔_赞]", + "10388": "[眠眠兔_抱抱]", + "10389": "[眠眠兔_抱大腿]", + "10390": "[眠眠兔_掉小珍珠]", + "10391": "[眠眠兔_多喝热水]", + "10392": "[眠眠兔_躲进小被]", + "10393": "[眠眠兔_害羞]", + "10394": "[眠眠兔_举高高]", + "10395": "[眠眠兔_开心]", + "10396": "[眠眠兔_啃萝卜]", + "10397": "[眠眠兔_摸摸脑袋]", + "10398": "[眠眠兔_气气]", + "10399": "[眠眠兔_揉揉]", + "10400": "[眠眠兔_叹气]", + "10401": "[眠眠兔_贴贴]", + "10402": "[眠眠兔_晕乎乎]", + "10264": "[乖巧宝宝_?]", + "10265": "[乖巧宝宝_AWSL]", + "10266": "[乖巧宝宝_抱大腿]", + "10267": "[乖巧宝宝_比心]", + "10268": "[乖巧宝宝_吃瓜]", + "10269": "[乖巧宝宝_哒咩]", + "10270": "[乖巧宝宝_打call]", + "10271": "[乖巧宝宝_大哭]", + "10272": "[乖巧宝宝_盯]", + "10273": "[乖巧宝宝_恶龙咆哮]", + "10274": "[乖巧宝宝_干杯]", + "10275": "[乖巧宝宝_汗]", + "10276": "[乖巧宝宝_救救孩子]", + "10277": "[乖巧宝宝_就这]", + "10278": "[乖巧宝宝_苦鲁西]", + "10279": "[乖巧宝宝_快乐摸鱼]", + "10280": "[乖巧宝宝_来啦]", + "10281": "[乖巧宝宝_奶思]", + "10282": "[乖巧宝宝_你继续吹]", + "10283": "[乖巧宝宝_你没事吧]", + "10284": "[乖巧宝宝_让我康康]", + "10285": "[乖巧宝宝_生气]", + "10286": "[乖巧宝宝_栓Q]", + "10287": "[乖巧宝宝_贴贴]", + "10288": "[乖巧宝宝_晚安]", + "10289": "[乖巧宝宝_我好方]", + "10290": "[乖巧宝宝_举起]", + "10291": "[乖巧宝宝_无语]", + "10292": "[乖巧宝宝_想要]", + "10293": "[乖巧宝宝_赞]", + "10313": "[红叔_无所畏惧]", + "10314": "[红叔_星星眼]", + "10315": "[红叔_一键三连]", + "10316": "[红叔_赞]", + "10317": "[红叔_你他喵的]", + "10294": "[红叔_挨打]", + "10295": "[红叔_不行]", + "10296": "[红叔_擦眼泪]", + "10297": "[红叔_叉腰]", + "10298": "[红叔_狗都无语]", + "10299": "[红叔_好耶]", + "10300": "[红叔_红茶]", + "10301": "[红叔_坏心眼]", + "10302": "[红叔_惊]", + "10303": "[红叔_老年手速]", + "10304": "[红叔_流汗]", + "10305": "[红叔_你没事吧]", + "10306": "[红叔_你是人吗]", + "10307": "[红叔_泣]", + "10308": "[红叔_省略号]", + "10309": "[红叔_躺]", + "10310": "[红叔_贴贴]", + "10311": "[红叔_挺好的]", + "10312": "[红叔_问号]", + "10318": "[星汐seki_DJ]", + "10319": "[星汐seki_吃我一拳]", + "10320": "[星汐seki_大笑]", + "10321": "[星汐seki_呃]", + "10322": "[星汐seki_发呆]", + "10323": "[星汐seki_嗨]", + "10324": "[星汐seki_害羞]", + "10325": "[星汐seki_好诶]", + "10326": "[星汐seki_好帅呀]", + "10327": "[星汐seki_惊]", + "10328": "[星汐seki_哭泣]", + "10329": "[星汐seki_流汗了]", + "10330": "[星汐seki_玫瑰]", + "10331": "[星汐seki_媚眼]", + "10332": "[星汐seki_飘过]", + "10333": "[星汐seki_气气]", + "10334": "[星汐seki_亲亲]", + "10335": "[星汐seki_太牛啦]", + "10336": "[星汐seki_探头]", + "10337": "[星汐seki_哇]", + "10338": "[星汐seki_晚安]", + "10339": "[星汐seki_委屈]", + "10340": "[星汐seki_辛苦了]", + "10341": "[星汐seki_疑惑]", + "10342": "[星汐seki_幽你一默]", + "10343": "[小鲨鱼_大哭]", + "10344": "[小鲨鱼_点赞]", + "10345": "[小鲨鱼_惊慌]", + "10346": "[小鲨鱼_酷]", + "10347": "[小鲨鱼_嗯嗯]", + "10348": "[小鲨鱼_生气]", + "10349": "[小鲨鱼_贴贴]", + "10350": "[小鲨鱼_投币]", + "10351": "[小鲨鱼_疑问]", + "10352": "[小鲨鱼_震惊]", + "10378": "[小鲨鱼_暗中观察]", + "10379": "[小鲨鱼_吃瓜]", + "10380": "[小鲨鱼_吃药]", + "10381": "[小鲨鱼_戳中]", + "10382": "[小鲨鱼_打call]", + "10383": "[小鲨鱼_害羞]", + "10384": "[小鲨鱼_前排]", + "10385": "[小鲨鱼_有问题]", + "10386": "[小鲨鱼_晕]", + "10387": "[小鲨鱼_真菜]", + "10353": "[牧场少女谬可可_kiss]", + "10354": "[牧场少女谬可可_不会吧]", + "10355": "[牧场少女谬可可_超凶]", + "10356": "[牧场少女谬可可_吃草]", + "10357": "[牧场少女谬可可_冲]", + "10358": "[牧场少女谬可可_大哭]", + "10359": "[牧场少女谬可可_吨吨吨]", + "10360": "[牧场少女谬可可_好好好]", + "10361": "[牧场少女谬可可_好心情]", + "10362": "[牧场少女谬可可_看看你的]", + "10363": "[牧场少女谬可可_乐]", + "10364": "[牧场少女谬可可_牛]", + "10365": "[牧场少女谬可可_贴贴]", + "10366": "[牧场少女谬可可_我奶呢?]", + "10367": "[牧场少女谬可可_许愿]", + "10678": "[牧场少女谬可可_V我]", + "10679": "[牧场少女谬可可_大头]", + "10680": "[牧场少女谬可可_互粉]", + "10681": "[牧场少女谬可可_流汗]", + "10682": "[牧场少女谬可可_牛牛药炸了]", + "10368": "[UPOWER_591892279_喜欢]", + "10369": "[UPOWER_591892279_屑]", + "10370": "[UPOWER_591892279_点赞]", + "10371": "[UPOWER_591892279_大哭]", + "10372": "[UPOWER_591892279_乞讨]", + "10373": "[UPOWER_591892279_吃蟾蜍]", + "10374": "[UPOWER_591892279_星星眼]", + "10375": "[UPOWER_591892279_流汗]", + "10376": "[UPOWER_591892279_呆滞]", + "10377": "[UPOWER_591892279_生气]", + "10433": "[魔道祖师动画_sos]", + "10434": "[魔道祖师动画_wink]", + "10435": "[魔道祖师动画_唉]", + "10436": "[魔道祖师动画_不敢说话]", + "10437": "[魔道祖师动画_超赞]", + "10438": "[魔道祖师动画_冲]", + "10439": "[魔道祖师动画_崇拜]", + "10440": "[魔道祖师动画_哈哈哈]", + "10441": "[魔道祖师动画_害羞]", + "10442": "[魔道祖师动画_好耶]", + "10443": "[魔道祖师动画_惊]", + "10444": "[魔道祖师动画_惊恐]", + "10445": "[魔道祖师动画_没眼看]", + "10446": "[魔道祖师动画_努力]", + "10447": "[魔道祖师动画_怒]", + "10448": "[魔道祖师动画_气到变形]", + "10449": "[魔道祖师动画_救救孩子]", + "10450": "[魔道祖师动画_气晕]", + "10451": "[魔道祖师动画_人麻了]", + "10452": "[魔道祖师动画_思考]", + "10453": "[魔道祖师动画_吐]", + "10454": "[魔道祖师动画_无语]", + "10455": "[魔道祖师动画_星星眼]", + "10456": "[魔道祖师动画_许愿]", + "10457": "[魔道祖师动画_永远的神]", + "10458": "[小樱花and小年糕_巴适]", + "10459": "[小樱花and小年糕_抱住]", + "10460": "[小樱花and小年糕_不许看]", + "10461": "[小樱花and小年糕_馋了]", + "10462": "[小樱花and小年糕_冲]", + "10463": "[小樱花and小年糕_姐弟情深]", + "10464": "[小樱花and小年糕_惊呆]", + "10465": "[小樱花and小年糕_开心]", + "10466": "[小樱花and小年糕_哭了]", + "10467": "[小樱花and小年糕_困]", + "10468": "[小樱花and小年糕_溜猪啦!]", + "10469": "[小樱花and小年糕_命运喉咙]", + "10470": "[小樱花and小年糕_那么大!]", + "10471": "[小樱花and小年糕_皮?]", + "10472": "[小樱花and小年糕_亲亲]", + "10473": "[小樱花and小年糕_晚安]", + "10474": "[小樱花and小年糕_我错了]", + "10475": "[小樱花and小年糕_嘤嘤嘤]", + "10476": "[小樱花and小年糕_真乖]", + "10477": "[小樱花and小年糕_转圈圈]", + "10479": "[蜜桃猫_??]", + "10480": "[蜜桃猫_Hello]", + "10481": "[蜜桃猫_阿巴阿巴]", + "10482": "[蜜桃猫_啊这]", + "10483": "[蜜桃猫_比心]", + "10484": "[蜜桃猫_飙泪]", + "10485": "[蜜桃猫_超凶]", + "10486": "[蜜桃猫_打call]", + "10487": "[蜜桃猫_大冤种]", + "10488": "[蜜桃猫_盯]", + "10489": "[蜜桃猫_飞吻]", + "10490": "[蜜桃猫_害羞]", + "10491": "[蜜桃猫_哼]", + "10492": "[蜜桃猫_坏笑]", + "10493": "[蜜桃猫_举高高]", + "10494": "[蜜桃猫_苦鲁西]", + "10495": "[蜜桃猫_来啦]", + "10496": "[蜜桃猫_懒得理你]", + "10497": "[蜜桃猫_摸鱼]", + "10498": "[蜜桃猫_你没事吧]", + "10499": "[蜜桃猫_啪]", + "10500": "[蜜桃猫_躺]", + "10501": "[蜜桃猫_贴贴]", + "10502": "[蜜桃猫_拖走]", + "10503": "[蜜桃猫_晚安]", + "10504": "[蜜桃猫_委屈巴巴]", + "10505": "[蜜桃猫_无语]", + "10506": "[蜜桃猫_喜欢]", + "10507": "[蜜桃猫_星星眼]", + "10508": "[蜜桃猫_赞]", + "10511": "[旌旗卷平川_哀怨]", + "10512": "[旌旗卷平川_吃咸鱼]", + "10510": "[旌旗卷平川_吃瓜]", + "10513": "[旌旗卷平川_告辞]", + "10514": "[旌旗卷平川_给我整一个]", + "10515": "[旌旗卷平川_可恶]", + "10516": "[旌旗卷平川_来呀来呀]", + "10517": "[旌旗卷平川_你醒啦]", + "10518": "[旌旗卷平川_你真棒]", + "10519": "[旌旗卷平川_让我看看]", + "10520": "[旌旗卷平川_社会]", + "10521": "[旌旗卷平川_酸了]", + "10522": "[旌旗卷平川_我很好]", + "10523": "[旌旗卷平川_震惊]", + "10524": "[旌旗卷平川_自闭]", + "10525": "[三团幼稚园_mua~]", + "10526": "[三团幼稚园_wow]", + "10527": "[三团幼稚园_抱抱]", + "10528": "[三团幼稚园_不想上班]", + "10529": "[三团幼稚园_蹭蹭]", + "10530": "[三团幼稚园_打瞌睡]", + "10531": "[三团幼稚园_到哪了]", + "10532": "[三团幼稚园_等等]", + "10533": "[三团幼稚园_发射爱心]", + "10534": "[三团幼稚园_害羞]", + "10535": "[三团幼稚园_黑心]", + "10536": "[三团幼稚园_加油]", + "10537": "[三团幼稚园_看好你]", + "10538": "[三团幼稚园_狼]", + "10539": "[三团幼稚园_摸头]", + "10540": "[三团幼稚园_你输了]", + "10541": "[三团幼稚园_噢?]", + "10542": "[三团幼稚园_脾气挺大]", + "10543": "[三团幼稚园_亲亲]", + "10544": "[三团幼稚园_撒花]", + "10545": "[三团幼稚园_兔]", + "10546": "[三团幼稚园_委屈]", + "10547": "[三团幼稚园_洗洗睡啦]", + "10548": "[三团幼稚园_想干嘛]", + "10549": "[三团幼稚园_想你了]", + "10550": "[三团幼稚园_谢谢]", + "10551": "[三团幼稚园_兄弟萌]", + "10552": "[茶茶龙_emo]", + "10553": "[茶茶龙_爱你]", + "10554": "[茶茶龙_抱抱]", + "10555": "[茶茶龙_吃瓜]", + "10556": "[茶茶龙_呆]", + "10557": "[茶茶龙_盯]", + "10558": "[茶茶龙_吃饭饭]", + "10559": "[茶茶龙_慌]", + "10560": "[茶茶龙_开心]", + "10561": "[茶茶龙_哭哭]", + "10562": "[茶茶龙_溜了溜了]", + "10563": "[茶茶龙_生气]", + "10564": "[茶茶龙_悠闲]", + "10565": "[茶茶龙_拳头硬了]", + "10566": "[茶茶龙_揉揉脸]", + "10567": "[茶茶龙_三连]", + "10568": "[茶茶龙_收到]", + "10569": "[茶茶龙_睡觉觉]", + "10570": "[茶茶龙_酸了]", + "10571": "[茶茶龙_叹气]", + "10572": "[茶茶龙_羞]", + "10573": "[茶茶龙_呀哒]", + "10574": "[茶茶龙_疑惑]", + "10575": "[茶茶龙_阔以]", + "10576": "[茶茶龙_震惊]", + "10578": "[奶油兔_emm]", + "10579": "[奶油兔_什么]", + "10580": "[奶油兔_可爱]", + "10581": "[奶油兔_呜呜]", + "10582": "[奶油兔_品茗]", + "10583": "[奶油兔_嗯嗯]", + "10584": "[奶油兔_急了]", + "10585": "[奶油兔_悠闲]", + "10586": "[奶油兔_我来了]", + "10587": "[奶油兔_托腮]", + "10588": "[奶油兔_无聊]", + "10589": "[奶油兔_早上好]", + "10590": "[奶油兔_有人吗]", + "10591": "[奶油兔_棒]", + "10592": "[奶油兔_比心]", + "10593": "[奶油兔_睡了]", + "10594": "[奶油兔_突然出现]", + "10595": "[奶油兔_美味]", + "10597": "[奶油兔_超凶]", + "10598": "[奶油兔_达咩]", + "10599": "[奶油兔_爆笑]", + "10600": "[奶油兔_点个赞]", + "10601": "[奶油兔_三连]", + "10602": "[奶油兔_投个币]", + "10603": "[奶油兔_自闭]", + "10604": "[ROUTINEARSON_啊对]", + "10605": "[ROUTINEARSON_崩溃]", + "10606": "[ROUTINEARSON_嘲讽]", + "10607": "[ROUTINEARSON_饭饭]", + "10608": "[ROUTINEARSON_害羞]", + "10609": "[ROUTINEARSON_惊了]", + "10610": "[ROUTINEARSON_困]", + "10611": "[ROUTINEARSON_乐]", + "10612": "[ROUTINEARSON_落泪]", + "10613": "[ROUTINEARSON_难受]", + "10614": "[ROUTINEARSON_怒了]", + "10615": "[ROUTINEARSON_升天]", + "10616": "[ROUTINEARSON_无语]", + "10617": "[ROUTINEARSON_下跪]", + "10618": "[ROUTINEARSON_支持]", + "15607": "[UPOWER_686127_吃我一剑]", + "15608": "[UPOWER_686127_笨拙诅咒]", + "10621": "[UPOWER_686127_方头方脑]", + "10622": "[UPOWER_686127_开怀大笑]", + "10623": "[UPOWER_686127_闪亮登场]", + "10624": "[UPOWER_686127_实力]", + "10625": "[UPOWER_686127_头大]", + "10626": "[UPOWER_686127_我的神镐]", + "10627": "[UPOWER_686127_心痛]", + "15616": "[UPOWER_686127_生态环保]", + "10629": "[乃琳个性装扮2.0_我们是]", + "10630": "[乃琳个性装扮2.0_奶淇琳]", + "10631": "[乃琳个性装扮2.0_交朋友]", + "10632": "[乃琳个性装扮2.0_耶]", + "10633": "[乃琳个性装扮2.0_贴贴捏]", + "10634": "[乃琳个性装扮2.0_小狐狸]", + "10635": "[乃琳个性装扮2.0_征服]", + "10636": "[乃琳个性装扮2.0_鸳鸯火锅拳]", + "10637": "[乃琳个性装扮2.0_翻跟头]", + "10638": "[乃琳个性装扮2.0_再想想]", + "10639": "[乃琳个性装扮2.0_困]", + "10640": "[乃琳个性装扮2.0_流汗]", + "10641": "[乃琳个性装扮2.0_撒娇]", + "10642": "[乃琳个性装扮2.0_不合礼法]", + "10643": "[乃琳个性装扮2.0_打招呼]", + "10644": "[乃琳个性装扮2.0_放烟花]", + "10645": "[乃琳个性装扮2.0_好姐姐]", + "10646": "[乃琳个性装扮2.0_墨镜]", + "10647": "[乃琳个性装扮2.0_大凤沟]", + "10648": "[乃琳个性装扮2.0_招商]", + "10649": "[乃琳个性装扮2.0_谢谢关注嘎]", + "10650": "[乃琳个性装扮2.0_气晕]", + "10651": "[乃琳个性装扮2.0_朋友们]", + "10652": "[乃琳个性装扮2.0_什么欠债]", + "10653": "[乃琳个性装扮2.0_晚安]", + "10654": "[废柴狐阿桔_抱大腿]", + "10655": "[废柴狐阿桔_苟住]", + "10656": "[废柴狐阿桔_摸摸]", + "10657": "[废柴狐阿桔_委屈屈]", + "10658": "[废柴狐阿桔_疑问]", + "10659": "[废柴狐阿桔_悠闲]", + "10660": "[废柴狐阿桔_啊对对对]", + "10661": "[废柴狐阿桔_吃瓜]", + "10662": "[废柴狐阿桔_出来挨打]", + "10663": "[废柴狐阿桔_诶?]", + "10664": "[废柴狐阿桔_发达啦]", + "10665": "[废柴狐阿桔_关门放狗]", + "10666": "[废柴狐阿桔_掀桌]", + "10667": "[废柴狐阿桔_啦啦啦]", + "10668": "[废柴狐阿桔_让你得瑟]", + "10669": "[废柴狐阿桔_思考]", + "10670": "[废柴狐阿桔_贴贴]", + "10671": "[废柴狐阿桔_晚安]", + "10672": "[废柴狐阿桔_围观]", + "10673": "[废柴狐阿桔_一本正经]", + "10674": "[废柴狐阿桔_咦惹]", + "10675": "[废柴狐阿桔_真香]", + "10676": "[废柴狐阿桔_屈服]", + "10677": "[废柴狐阿桔_AWSL]", + "10803": "[废柴狐阿桔_吃土]", + "10683": "[莞儿睡不醒_mua]", + "10684": "[莞儿睡不醒_安心]", + "10685": "[莞儿睡不醒_不安]", + "10686": "[莞儿睡不醒_高音]", + "10687": "[莞儿睡不醒_莞安]", + "10688": "[莞儿睡不醒_开心]", + "10689": "[莞儿睡不醒_哭泣]", + "10690": "[莞儿睡不醒_什么意思]", + "10691": "[莞儿睡不醒_生气]", + "10692": "[莞儿睡不醒_唐突]", + "10693": "[莞儿睡不醒_我要闹了]", + "10694": "[莞儿睡不醒_小厨娘]", + "10695": "[莞儿睡不醒_小莞熊]", + "10696": "[莞儿睡不醒_巡逻中]", + "10697": "[莞儿睡不醒_真的吗]", + "14412": "[莞儿睡不醒_我们是EOE]", + "14434": "[莞儿睡不醒_啵烂]", + "14435": "[莞儿睡不醒_懂你意思]", + "14413": "[莞儿睡不醒_投降]", + "14436": "[莞儿睡不醒_饿了]", + "14414": "[莞儿睡不醒_睡不醒]", + "14415": "[莞儿睡不醒_麻麻]", + "14416": "[莞儿睡不醒_可靠]", + "14417": "[莞儿睡不醒_好耶]", + "14418": "[莞儿睡不醒_给你一铲]", + "10698": "[露早GOGO_GOGO队]", + "10699": "[露早GOGO_mua]", + "10700": "[露早GOGO_给你一拳]", + "10701": "[露早GOGO_姐有招]", + "10702": "[露早GOGO_紧张]", + "10703": "[露早GOGO_开心]", + "10704": "[露早GOGO_侃侃而谈]", + "10705": "[露早GOGO_哭泣]", + "10706": "[露早GOGO_偶像包袱]", + "10707": "[露早GOGO_生气]", + "10708": "[露早GOGO_睡着]", + "10709": "[露早GOGO_偷吃]", + "10710": "[露早GOGO_歪嘴]", + "10711": "[露早GOGO_在看吗?]", + "10712": "[露早GOGO_宅家]", + "14392": "[露早GOGO_补签卡]", + "14393": "[露早GOGO_蒽,]", + "14394": "[露早GOGO_公主殿下]", + "14395": "[露早GOGO_敬礼]", + "14396": "[露早GOGO_流汗]", + "14397": "[露早GOGO_哇袄]", + "14398": "[露早GOGO_谢谢你]", + "14399": "[露早GOGO_早]", + "14400": "[露早GOGO_我们是EOE]", + "14401": "[露早GOGO_GO叫]", + "10713": "[米诺高分少女_哎呀米诺]", + "10714": "[米诺高分少女_诶]", + "10715": "[米诺高分少女_分我一口]", + "10716": "[米诺高分少女_开编]", + "10717": "[米诺高分少女_开大]", + "10718": "[米诺高分少女_开心]", + "10719": "[米诺高分少女_哭]", + "10720": "[米诺高分少女_酷诺米]", + "10721": "[米诺高分少女_么么哒]", + "10722": "[米诺高分少女_米蝓]", + "10723": "[米诺高分少女_摸鱼]", + "10724": "[米诺高分少女_生气]", + "10725": "[米诺高分少女_刷碗]", + "10726": "[米诺高分少女_说的道理]", + "10727": "[米诺高分少女_疑问]", + "14382": "[米诺高分少女_EOE]", + "14383": "[米诺高分少女_prpr]", + "14384": "[米诺高分少女_绷]", + "14385": "[米诺高分少女_别急]", + "14386": "[米诺高分少女_光鼙鼓]", + "14387": "[米诺高分少女_流汗]", + "14388": "[米诺高分少女_是碟]", + "14389": "[米诺高分少女_讨口子]", + "14390": "[米诺高分少女_吐血]", + "14391": "[米诺高分少女_易拉罐]", + "10728": "[柚恩不加糖_SSW]", + "10729": "[柚恩不加糖_呆滞]", + "10730": "[柚恩不加糖_腹黑]", + "10731": "[柚恩不加糖_古神语]", + "10732": "[柚恩不加糖_航海]", + "10733": "[柚恩不加糖_嘿嘿]", + "10734": "[柚恩不加糖_卷]", + "10735": "[柚恩不加糖_开心]", + "10736": "[柚恩不加糖_撇嘴]", + "10737": "[柚恩不加糖_生气]", + "10738": "[柚恩不加糖_哇]", + "10739": "[柚恩不加糖_温柔]", + "10740": "[柚恩不加糖_无语]", + "10741": "[柚恩不加糖_要抱抱]", + "10742": "[柚恩不加糖_柚恩蜜]", + "14402": "[柚恩不加糖_出去]", + "14403": "[柚恩不加糖_对不起]", + "14404": "[柚恩不加糖_回来]", + "14405": "[柚恩不加糖_急的长痘痘]", + "14406": "[柚恩不加糖_饺子皮]", + "14407": "[柚恩不加糖_可爱]", + "14408": "[柚恩不加糖_下雪]", + "14409": "[柚恩不加糖_柚师傅]", + "14410": "[柚恩不加糖_知道道歉吗]", + "14411": "[柚恩不加糖_EOE]", + "10743": "[虞莫MOMO_别拍别拍]", + "10744": "[虞莫MOMO_不吃主食]", + "10745": "[虞莫MOMO_嗨起来]", + "10746": "[虞莫MOMO_害怕]", + "10747": "[虞莫MOMO_紧张]", + "10748": "[虞莫MOMO_开心]", + "10749": "[虞莫MOMO_哭泣]", + "10750": "[虞莫MOMO_买买买]", + "10751": "[虞莫MOMO_没钱]", + "10752": "[虞莫MOMO_美人虞]", + "10753": "[虞莫MOMO_茄步]", + "10754": "[虞莫MOMO_茄子]", + "10755": "[虞莫MOMO_扔飞镖]", + "10756": "[虞莫MOMO_生气]", + "10757": "[虞莫MOMO_舞担]", + "14372": "[虞莫MOMO_打牌]", + "14373": "[虞莫MOMO_卡住了]", + "14374": "[虞莫MOMO_来了]", + "14375": "[虞莫MOMO_买它]", + "14376": "[虞莫MOMO_茄皇]", + "14377": "[虞莫MOMO_投降]", + "14378": "[虞莫MOMO_我们是]", + "14379": "[虞莫MOMO_现充]", + "14380": "[虞莫MOMO_嘴硬]", + "14381": "[虞莫MOMO_摇花手]", + "10758": "[猫诺装扮套装_暗中观察]", + "10759": "[猫诺装扮套装_察觉]", + "10760": "[猫诺装扮套装_打CALL]", + "10761": "[猫诺装扮套装_大气]", + "10762": "[猫诺装扮套装_干杯]", + "10763": "[猫诺装扮套装_可恶!]", + "10764": "[猫诺装扮套装_哭泣]", + "10765": "[猫诺装扮套装_来咯!]", + "10766": "[猫诺装扮套装_闷气]", + "10767": "[猫诺装扮套装_喵喵]", + "10768": "[猫诺装扮套装_摸摸头]", + "10769": "[猫诺装扮套装_摸鱼]", + "10770": "[猫诺装扮套装_噢不!]", + "10771": "[猫诺装扮套装_亲亲]", + "10772": "[猫诺装扮套装_生气]", + "10773": "[猫诺装扮套装_探头]", + "10774": "[猫诺装扮套装_贴贴]", + "10775": "[猫诺装扮套装_晚安]", + "10776": "[猫诺装扮套装_无语]", + "10777": "[猫诺装扮套装_喜欢]", + "10778": "[小柔Channel_ssw]", + "10779": "[小柔Channel_啊对对对]", + "10780": "[小柔Channel_傲娇]", + "10781": "[小柔Channel_保密]", + "10782": "[小柔Channel_不得了]", + "10783": "[小柔Channel_打咩]", + "10784": "[小柔Channel_逮捕]", + "10785": "[小柔Channel_吨吨吨]", + "10786": "[小柔Channel_二次元声呐]", + "10787": "[小柔Channel_过年啦]", + "10788": "[小柔Channel_好好休息]", + "10789": "[小柔Channel_赫赫]", + "10790": "[小柔Channel_啾啾]", + "10791": "[小柔Channel_开饭了]", + "10792": "[小柔Channel_夸夸]", + "10793": "[小柔Channel_乐]", + "10794": "[小柔Channel_令人头大]", + "10795": "[小柔Channel_趴趴]", + "10796": "[小柔Channel_燃尽了]", + "10797": "[小柔Channel_柔老头]", + "10798": "[小柔Channel_柔仔很忙]", + "10799": "[小柔Channel_太痛了]", + "10800": "[小柔Channel_小笨蛋]", + "10801": "[小柔Channel_亚萨西]", + "10802": "[小柔Channel_祝福]", + "10804": "[蛋酒一家_送心]", + "10805": "[蛋酒一家_啊对对对]", + "10806": "[蛋酒一家_阿巴]", + "10807": "[蛋酒一家_emo]", + "10808": "[蛋酒一家_水枪攻击]", + "10809": "[蛋酒一家_抬杠]", + "10810": "[蛋酒一家_赞啊]", + "10811": "[蛋酒一家_看你洗]", + "10812": "[蛋酒一家_伤心]", + "10813": "[蛋酒一家_上火]", + "10814": "[蛋酒一家_帮你灭火]", + "10815": "[蛋酒一家_往这看]", + "10816": "[蛋酒一家_知道了]", + "10817": "[蛋酒一家_达成共识]", + "10818": "[蛋酒一家_我丢]", + "10819": "[蛋酒一家_锤你]", + "10820": "[蛋酒一家_流口水]", + "10821": "[蛋酒一家_尬住]", + "10822": "[蛋酒一家_大佬]", + "10823": "[蛋酒一家_?]", + "10824": "[蛋酒一家_冲啊]", + "10825": "[蛋酒一家_好耶]", + "10826": "[蛋酒一家_有内鬼]", + "10827": "[蛋酒一家_溜了]", + "10828": "[蛋酒一家_电锯警告]", + "10829": "[蛋酒一家_假笑]", + "10830": "[蛋酒一家_霸屏]", + "10831": "[蛋酒一家_盯上你了]", + "10842": "[蛋酒一家_警告]", + "10843": "[蛋酒一家_tui]", + "10832": "[妹子与科学_傲娇]", + "10833": "[妹子与科学_哈?]", + "10834": "[妹子与科学_害羞]", + "10835": "[妹子与科学_惊呆]", + "10836": "[妹子与科学_叹气]", + "10837": "[妹子与科学_吐了]", + "10838": "[妹子与科学_哇]", + "10839": "[妹子与科学_委屈]", + "10840": "[妹子与科学_心碎]", + "10841": "[妹子与科学_震惊]", + "10845": "[小鸟芋圆_v我]", + "10846": "[小鸟芋圆_俺无语]", + "10847": "[小鸟芋圆_吃瓜]", + "10848": "[小鸟芋圆_打嗝]", + "10849": "[小鸟芋圆_饿饿饭饭]", + "10850": "[小鸟芋圆_好活]", + "10851": "[小鸟芋圆_激动]", + "10852": "[小鸟芋圆_教教我]", + "10853": "[小鸟芋圆_惊呆鸟]", + "10854": "[小鸟芋圆_母单鹦鹉]", + "10855": "[小鸟芋圆_你理猫吗]", + "10856": "[小鸟芋圆_鸟不起]", + "10857": "[小鸟芋圆_鸟解]", + "10858": "[小鸟芋圆_蒜鸟]", + "10859": "[小鸟芋圆_太感谢鸟]", + "10860": "[小鸟芋圆_太好磕鸟]", + "10861": "[小鸟芋圆_吐鸟]", + "10862": "[小鸟芋圆_完蛋]", + "10863": "[小鸟芋圆_晚安鸟]", + "10864": "[小鸟芋圆_小鸟依鸟]", + "10865": "[小鸟芋圆_笑出鸟叫]", + "10866": "[小鸟芋圆_鹦该的]", + "10867": "[小鸟芋圆_有禽提示]", + "10868": "[小鸟芋圆_再见鸟]", + "10869": "[小鸟芋圆_这很滑鸡]", + "10870": "[万圣节的新娘_酷]", + "10871": "[万圣节的新娘_微笑]", + "10872": "[万圣节的新娘_开心]", + "10873": "[万圣节的新娘_看]", + "10874": "[万圣节的新娘_盯]", + "10875": "[万圣节的新娘_邪魅一笑]", + "10876": "[万圣节的新娘_爱的凝视]", + "10877": "[万圣节的新娘_闪亮登场]", + "10878": "[万圣节的新娘_帅气]", + "10879": "[万圣节的新娘_疑惑]", + "10880": "[2233人生百戏·梁祝_比心]", + "10881": "[2233人生百戏·梁祝_比翼双飞]", + "10882": "[2233人生百戏·梁祝_担心]", + "10883": "[2233人生百戏·梁祝_告别]", + "10884": "[2233人生百戏·梁祝_化蝶]", + "10885": "[2233人生百戏·梁祝_念书]", + "10886": "[2233人生百戏·梁祝_深情]", + "10887": "[2233人生百戏·梁祝_思索]", + "10888": "[2233人生百戏·梁祝_送花]", + "10889": "[2233人生百戏·梁祝_提亲]", + "10890": "[2233人生百戏·梁祝_呜呜]", + "10891": "[2233人生百戏·梁祝_相思]", + "10892": "[2233人生百戏·梁祝_心虚]", + "10893": "[2233人生百戏·梁祝_至死不渝]", + "10894": "[2233人生百戏·梁祝_终生相守]", + "10895": "[两不疑2_Emmm]", + "10896": "[两不疑2_OK]", + "10897": "[两不疑2_不屑]", + "10898": "[两不疑2_崇拜]", + "10899": "[两不疑2_开心]", + "10900": "[两不疑2_泪流]", + "10901": "[两不疑2_泪目]", + "10902": "[两不疑2_卖惨]", + "10903": "[两不疑2_亲亲]", + "10904": "[两不疑2_色迷迷]", + "10905": "[两不疑2_栓Q]", + "10906": "[两不疑2_我来啦]", + "10907": "[两不疑2_元气满满]", + "10908": "[两不疑2_原来如此]", + "10909": "[两不疑2_晕]", + "10910": "[偶像梦幻祭2周年_No]", + "10911": "[偶像梦幻祭2周年_OK]", + "10912": "[偶像梦幻祭2周年_wink]", + "10913": "[偶像梦幻祭2周年_阿妹胫骨]", + "10914": "[偶像梦幻祭2周年_阿这]", + "10915": "[偶像梦幻祭2周年_爱你]", + "10916": "[偶像梦幻祭2周年_别烦我]", + "10917": "[偶像梦幻祭2周年_不好意思]", + "10918": "[偶像梦幻祭2周年_认真学习]", + "10919": "[偶像梦幻祭2周年_沉默]", + "10920": "[偶像梦幻祭2周年_冲冲]", + "10921": "[偶像梦幻祭2周年_打招呼]", + "10922": "[偶像梦幻祭2周年_大吉大利]", + "10923": "[偶像梦幻祭2周年_得意]", + "10924": "[偶像梦幻祭2周年_点赞]", + "10925": "[偶像梦幻祭2周年_动脑筋]", + "10926": "[偶像梦幻祭2周年_度假]", + "10927": "[偶像梦幻祭2周年_额咦]", + "10928": "[偶像梦幻祭2周年_搞不懂]", + "10929": "[偶像梦幻祭2周年_搞钱]", + "10930": "[偶像梦幻祭2周年_害羞]", + "10931": "[偶像梦幻祭2周年_好累]", + "10932": "[偶像梦幻祭2周年_好喜欢]", + "10933": "[偶像梦幻祭2周年_加油运动]", + "10934": "[偶像梦幻祭2周年_检查]", + "10935": "[偶像梦幻祭2周年_骄傲]", + "10936": "[偶像梦幻祭2周年_警惕]", + "10937": "[偶像梦幻祭2周年_啾咪]", + "10938": "[偶像梦幻祭2周年_开饭]", + "10939": "[偶像梦幻祭2周年_看我的]", + "10940": "[偶像梦幻祭2周年_酷]", + "10941": "[偶像梦幻祭2周年_快跑]", + "10942": "[偶像梦幻祭2周年_来呀]", + "10943": "[偶像梦幻祭2周年_美滋滋]", + "10944": "[偶像梦幻祭2周年_魅力时刻]", + "10945": "[偶像梦幻祭2周年_趴趴]", + "10946": "[偶像梦幻祭2周年_伸懒腰]", + "10947": "[偶像梦幻祭2周年_什么]", + "10948": "[偶像梦幻祭2周年_收到]", + "10949": "[偶像梦幻祭2周年_睡不着]", + "10950": "[偶像梦幻祭2周年_叹气]", + "10951": "[偶像梦幻祭2周年_头疼]", + "10952": "[偶像梦幻祭2周年_哇哦]", + "10953": "[偶像梦幻祭2周年_晚安]", + "10954": "[偶像梦幻祭2周年_委屈]", + "10955": "[偶像梦幻祭2周年_无所谓]", + "10956": "[偶像梦幻祭2周年_洗澡]", + "10957": "[偶像梦幻祭2周年_想吃]", + "10958": "[偶像梦幻祭2周年_笑眯眯]", + "10959": "[偶像梦幻祭2周年_研究]", + "10960": "[偶像梦幻祭2周年_疑问]", + "10961": "[偶像梦幻祭2周年_哟嚯]", + "10962": "[偶像梦幻祭2周年_悠闲]", + "10963": "[偶像梦幻祭2周年_着急]", + "10964": "[偶像梦幻祭2周年_祝贺]", + "10965": "[非人哉_啊哦]", + "10966": "[非人哉_爱你]", + "10967": "[非人哉_被拿捏]", + "10968": "[非人哉_必胜]", + "10969": "[非人哉_馋]", + "10970": "[非人哉_出现]", + "10971": "[非人哉_达成共识]", + "10972": "[非人哉_诶呦]", + "10973": "[非人哉_加油]", + "10974": "[非人哉_开心]", + "10975": "[非人哉_可靠]", + "10976": "[非人哉_累垮]", + "10977": "[非人哉_没眼看]", + "10978": "[非人哉_难过]", + "10979": "[非人哉_生气]", + "10980": "[非人哉_委屈]", + "10981": "[非人哉_嘻嘻]", + "10982": "[非人哉_喜欢]", + "10983": "[非人哉_嫌弃]", + "10984": "[非人哉_疑惑]", + "10985": "[我是大神仙_奥利给]", + "10986": "[我是大神仙_宝宝心里苦]", + "10987": "[我是大神仙_暴富]", + "10988": "[我是大神仙_冲鸭]", + "10989": "[我是大神仙_打call]", + "10990": "[我是大神仙_害羞]", + "10991": "[我是大神仙_呵呵]", + "10992": "[我是大神仙_你算?]", + "10993": "[我是大神仙_穷酸]", + "10994": "[我是大神仙_早安打工人]", + "10995": "[茶啊二中居居男孩日常_emmm]", + "10996": "[茶啊二中居居男孩日常_抱大腿]", + "10997": "[茶啊二中居居男孩日常_饿]", + "10998": "[茶啊二中居居男孩日常_喝可乐]", + "10999": "[茶啊二中居居男孩日常_减肥]", + "11000": "[茶啊二中居居男孩日常_惊讶]", + "11001": "[茶啊二中居居男孩日常_开心]", + "11002": "[茶啊二中居居男孩日常_困]", + "11003": "[茶啊二中居居男孩日常_略略略]", + "11004": "[茶啊二中居居男孩日常_穷]", + "11005": "[茶啊二中居居男孩日常_啥]", + "11006": "[茶啊二中居居男孩日常_吐]", + "11007": "[茶啊二中居居男孩日常_晚安]", + "11008": "[茶啊二中居居男孩日常_微笑]", + "11009": "[茶啊二中居居男孩日常_耶]", + "11010": "[团团猫_AWSL]", + "11011": "[团团猫_暗中观察]", + "11012": "[团团猫_吃瓜]", + "11013": "[团团猫_大哭]", + "11014": "[团团猫_干杯]", + "11015": "[团团猫_好耶]", + "11016": "[团团猫_呼呼睡]", + "11017": "[团团猫_惊吓]", + "11018": "[团团猫_灵魂出窍]", + "11019": "[团团猫_摸摸]", + "11020": "[团团猫_奈斯]", + "11021": "[团团猫_气呼呼]", + "11022": "[团团猫_前排]", + "11023": "[团团猫_热门许愿]", + "11024": "[团团猫_嘶哈嘶哈]", + "11025": "[团团猫_贴贴]", + "11026": "[团团猫_投币]", + "11027": "[团团猫_问号]", + "11028": "[团团猫_有点方]", + "11029": "[团团猫_赞]", + "11030": "[卡慕SaMa_!]", + "11031": "[卡慕SaMa_?]", + "11032": "[卡慕SaMa_mua~]", + "11033": "[卡慕SaMa_rwkk]", + "11034": "[卡慕SaMa_yue]", + "11035": "[卡慕SaMa_比心心]", + "11036": "[卡慕SaMa_吃瓜]", + "11037": "[卡慕SaMa_吃什么]", + "11038": "[卡慕SaMa_冲冲冲]", + "11039": "[卡慕SaMa_点个外卖]", + "11040": "[卡慕SaMa_叼玫瑰]", + "11041": "[卡慕SaMa_给你一拳]", + "11042": "[卡慕SaMa_哈哈哈哈哈]", + "11043": "[卡慕SaMa_烤黑猫]", + "11044": "[卡慕SaMa_烤坚果]", + "11045": "[卡慕SaMa_烤茄子]", + "11046": "[卡慕SaMa_烤兔子]", + "11047": "[卡慕SaMa_你先别急]", + "11048": "[卡慕SaMa_嘶哈嘶哈]", + "11049": "[卡慕SaMa_晚安]", + "11050": "[卡慕SaMa_我急了]", + "11051": "[卡慕SaMa_我哭了]", + "11052": "[卡慕SaMa_早八人]", + "11053": "[卡慕SaMa_猪脑过载]", + "11055": "[UPOWER_631070414_暗中观察]", + "11056": "[UPOWER_631070414_比心]", + "11057": "[UPOWER_631070414_打call]", + "11058": "[UPOWER_631070414_黑脸]", + "11059": "[UPOWER_631070414_哼]", + "11060": "[UPOWER_631070414_可爱]", + "11061": "[UPOWER_631070414_哭哭]", + "11062": "[UPOWER_631070414_摸脸脸]", + "11063": "[UPOWER_631070414_哇]", + "11064": "[UPOWER_631070414_疑问]", + "11065": "[银河之心_严肃]", + "11066": "[银河之心_思考]", + "11067": "[银河之心_耍帅]", + "11068": "[银河之心_派对开始]", + "11069": "[银河之心_开心]", + "11070": "[银河之心_惊讶]", + "11071": "[银河之心_为您效忠]", + "11072": "[银河之心_扶额]", + "11073": "[银河之心_生气]", + "11074": "[银河之心_哭]", + "11075": "[冬季小企鹅_奥利给]", + "11076": "[冬季小企鹅_比心]", + "11077": "[冬季小企鹅_单板滑雪]", + "11078": "[冬季小企鹅_堆雪人]", + "11079": "[冬季小企鹅_干饭准备]", + "11080": "[冬季小企鹅_期待]", + "11081": "[冬季小企鹅_起床]", + "11082": "[冬季小企鹅_起飞]", + "11083": "[冬季小企鹅_收到]", + "11084": "[冬季小企鹅_糖葫芦]", + "11085": "[冬季北极熊_暗中观察]", + "11086": "[冬季北极熊_比心]", + "11087": "[冬季北极熊_弹吉他]", + "11088": "[冬季北极熊_钓鱼]", + "11089": "[冬季北极熊_干杯]", + "11090": "[冬季北极熊_害羞]", + "11091": "[冬季北极熊_溜冰]", + "11092": "[冬季北极熊_生气]", + "11093": "[冬季北极熊_晚安]", + "11094": "[冬季北极熊_熊抱]", + "11095": "[喜迎2023_power]", + "11096": "[喜迎2023_大桔大利]", + "11097": "[喜迎2023_倒计时]", + "11098": "[喜迎2023_好运来]", + "11099": "[喜迎2023_红包拿来]", + "11100": "[喜迎2023_你好2023]", + "11101": "[喜迎2023_年年有鱼]", + "11102": "[喜迎2023_新年快乐]", + "11103": "[喜迎2023_烟花]", + "11104": "[喜迎2023_再见2022]", + "11105": "[中老年2.0_给你点赞]", + "11106": "[中老年2.0_好好好]", + "11107": "[中老年2.0_玫瑰]", + "11108": "[中老年2.0_你好]", + "11783": "[中老年2.0_你真棒]", + "11109": "[中老年2.0_晚上好]", + "11110": "[中老年2.0_相识是缘]", + "11111": "[中老年2.0_谢谢]", + "11112": "[中老年2.0_早上好]", + "11113": "[中老年2.0_知足常乐]", + "11114": "[龙女·月星沉_OK]", + "11115": "[龙女·月星沉_抱歉]", + "11116": "[龙女·月星沉_吃瓜]", + "11117": "[龙女·月星沉_吃惊]", + "11118": "[龙女·月星沉_饿饿饭饭]", + "11119": "[龙女·月星沉_给我看看]", + "11120": "[龙女·月星沉_哭泣]", + "11121": "[龙女·月星沉_摸鱼]", + "11122": "[龙女·月星沉_庆祝]", + "11123": "[龙女·月星沉_投币]", + "11124": "[龙女·月星沉_无奈]", + "11125": "[龙女·月星沉_无语]", + "11126": "[龙女·月星沉_喜欢]", + "11127": "[龙女·月星沉_疑问]", + "11128": "[龙女·月星沉_赞]", + "11620": "[龙女·月星沉_多喝热水]", + "11621": "[龙女·月星沉_暗中观察]", + "11622": "[龙女·月星沉_睡了]", + "11623": "[龙女·月星沉_献花]", + "11624": "[龙女·月星沉_许愿]", + "11129": "[圣诞2022_姜饼人]", + "11130": "[圣诞2022_檞寄生]", + "11131": "[圣诞2022_铃儿响叮当]", + "11132": "[圣诞2022_麋鹿]", + "11133": "[圣诞2022_平安果]", + "11134": "[圣诞2022_圣诞快乐]", + "11135": "[圣诞2022_圣诞老人]", + "11136": "[圣诞2022_圣诞礼物]", + "11137": "[圣诞2022_圣诞袜]", + "11138": "[圣诞2022_雪人]", + "11139": "[天气_不想起床]", + "11140": "[天气_吃火锅]", + "11141": "[天气_冻住]", + "11142": "[天气_堆雪人]", + "11143": "[天气_给你秋裤]", + "11144": "[天气_滑雪]", + "11145": "[天气_奶茶续命]", + "11146": "[天气_取暖]", + "11147": "[天气_晚安]", + "11148": "[天气_下雪啦]", + "11159": "[阿山和他的小伙伴们_emmm]", + "11160": "[阿山和他的小伙伴们_阿这]", + "11161": "[阿山和他的小伙伴们_爱了爱了]", + "11162": "[阿山和他的小伙伴们_暴露了]", + "11163": "[阿山和他的小伙伴们_不敢看]", + "11164": "[阿山和他的小伙伴们_不行]", + "11165": "[阿山和他的小伙伴们_吃瓜]", + "11166": "[阿山和他的小伙伴们_动物之友]", + "11167": "[阿山和他的小伙伴们_欢迎回家]", + "11168": "[阿山和他的小伙伴们_惊吓]", + "11169": "[阿山和他的小伙伴们_酷]", + "11170": "[阿山和他的小伙伴们_流汗]", + "11171": "[阿山和他的小伙伴们_牛]", + "11172": "[阿山和他的小伙伴们_怒]", + "11173": "[阿山和他的小伙伴们_酸了]", + "11174": "[阿山和他的小伙伴们_问号]", + "11175": "[阿山和他的小伙伴们_呜呜]", + "11176": "[阿山和他的小伙伴们_笑]", + "11177": "[阿山和他的小伙伴们_炸弹来咯]", + "11178": "[阿山和他的小伙伴们_自信叉腰]", + "11179": "[配音演员小N_???]", + "11180": "[配音演员小N_拜托拜托]", + "11181": "[配音演员小N_达咩]", + "11182": "[配音演员小N_对不起]", + "11183": "[配音演员小N_感受N典]", + "11184": "[配音演员小N_好耶]", + "11189": "[配音演员小N_盒子精]", + "11186": "[配音演员小N_盒1]", + "11187": "[配音演员小N_盒2]", + "11188": "[配音演员小N_盒3]", + "11185": "[配音演员小N_看戏]", + "11190": "[配音演员小N_可爱捏]", + "11191": "[配音演员小N_哭哭]", + "11192": "[配音演员小N_来啦来啦]", + "11193": "[配音演员小N_摸摸头]", + "11194": "[配音演员小N_摸鱼]", + "11195": "[配音演员小N_你先别急]", + "11196": "[配音演员小N_钱钱没有了]", + "11198": "[配音演员小N_生气]", + "11199": "[配音演员小N_我酸了]", + "11197": "[配音演员小N_射中]", + "11200": "[配音演员小N_你]", + "11201": "[配音演员小N_的]", + "11202": "[配音演员小N_心]", + "11203": "[配音演员小N_晚安]", + "11204": "[九重紫_doge]", + "11205": "[九重紫_FBI]", + "11206": "[九重紫_mua]", + "11207": "[九重紫_OMG]", + "11208": "[九重紫_V50]", + "11209": "[九重紫_吃鸡]", + "11210": "[九重紫_多喝热水]", + "11211": "[九重紫_害怕]", + "11212": "[九重紫_好的]", + "11213": "[九重紫_好耶]", + "11214": "[九重紫_寂寞]", + "11215": "[九重紫_加油]", + "11216": "[九重紫_哭笑]", + "11217": "[九重紫_两眼一黑]", + "11218": "[九重紫_摸鱼]", + "11219": "[九重紫_神龙]", + "11220": "[九重紫_土]", + "11221": "[九重紫_我爱死他了]", + "11222": "[九重紫_我的回合]", + "11223": "[九重紫_我佛慈悲]", + "11224": "[九重紫_辛苦了]", + "11225": "[九重紫_虚空]", + "11226": "[九重紫_大闸蟹]", + "11227": "[九重紫_秋刀鱼]", + "11228": "[九重紫_万柿如意]", + "11229": "[2022王者荣耀世界冠军杯_YYDS]", + "11230": "[2022王者荣耀世界冠军杯_宝贝双C]", + "11231": "[2022王者荣耀世界冠军杯_被驴踢了]", + "11232": "[2022王者荣耀世界冠军杯_不便透露]", + "11233": "[2022王者荣耀世界冠军杯_不配吃饭]", + "11234": "[2022王者荣耀世界冠军杯_刺痛我]", + "11235": "[2022王者荣耀世界冠军杯_干得漂亮]", + "11236": "[2022王者荣耀世界冠军杯_加鸡腿]", + "11237": "[2022王者荣耀世界冠军杯_进厂]", + "11238": "[2022王者荣耀世界冠军杯_就这]", + "11239": "[2022王者荣耀世界冠军杯_裂开]", + "11240": "[2022王者荣耀世界冠军杯_马上走开]", + "11241": "[2022王者荣耀世界冠军杯_名刀司命]", + "11242": "[2022王者荣耀世界冠军杯_逆子]", + "11243": "[2022王者荣耀世界冠军杯_谁更辣]", + "11244": "[2022王者荣耀世界冠军杯_说一个点]", + "11245": "[2022王者荣耀世界冠军杯_太香了]", + "11246": "[2022王者荣耀世界冠军杯_痛苦面具]", + "11247": "[2022王者荣耀世界冠军杯_我不理解]", + "11248": "[2022王者荣耀世界冠军杯_我来C]", + "11249": "[2022王者荣耀世界冠军杯_我是马可]", + "11250": "[2022王者荣耀世界冠军杯_一诺行为]", + "11251": "[2022王者荣耀世界冠军杯_装起来了]", + "12097": "[2022王者荣耀世界冠军杯_KPL出手]", + "12098": "[2022王者荣耀世界冠军杯_彻底疯狂]", + "12099": "[2022王者荣耀世界冠军杯_驯龙高手]", + "12613": "[2022王者荣耀世界冠军杯_冲冲冲]", + "12614": "[2022王者荣耀世界冠军杯_冠军]", + "12615": "[2022王者荣耀世界冠军杯_无人在易]", + "12670": "[2022王者荣耀世界冠军杯_无限猖狂]", + "13080": "[2022王者荣耀世界冠军杯_逆天]", + "13081": "[2022王者荣耀世界冠军杯_世冠加油]", + "13082": "[2022王者荣耀世界冠军杯_物极必反]", + "13083": "[2022王者荣耀世界冠军杯_相信光吗]", + "11252": "[武汉eStarPro_eStarPro]", + "11253": "[武汉eStarPro_victory]", + "11254": "[武汉eStarPro_擦星星]", + "11255": "[武汉eStarPro_冠军]", + "11256": "[武汉eStarPro_还活着]", + "11257": "[武汉eStarPro_害羞]", + "11258": "[武汉eStarPro_就该si]", + "11259": "[武汉eStarPro_累]", + "11260": "[武汉eStarPro_麻麻鱼]", + "11261": "[武汉eStarPro_马可]", + "11262": "[武汉eStarPro_拿捏]", + "11263": "[武汉eStarPro_山呼海啸]", + "11264": "[武汉eStarPro_生闷气]", + "11265": "[武汉eStarPro_天下无敌]", + "11266": "[武汉eStarPro_真滴C]", + "11272": "[shoto_???]", + "11273": "[shoto_啊啊啊]", + "11274": "[shoto_棒]", + "11275": "[shoto_抱歉]", + "11276": "[shoto_打call]", + "11277": "[shoto_干杯]", + "11278": "[shoto_乖巧]", + "11279": "[shoto_哈哈]", + "11280": "[shoto_哈喽]", + "11281": "[shoto_害怕]", + "11282": "[shoto_坏笑]", + "11283": "[shoto_开心]", + "11284": "[shoto_可爱]", + "11285": "[shoto_流口水]", + "11286": "[shoto_生气]", + "11287": "[shoto_思考]", + "11288": "[shoto_晚安]", + "11289": "[shoto_委屈]", + "11290": "[shoto_谢谢]", + "11291": "[shoto_阴险]", + "11292": "[shoto_怎么办]", + "11293": "[shoto_震撼]", + "11294": "[shoto_好困]", + "11295": "[shoto_吃烧烤]", + "11296": "[shoto_喝奶茶]", + "11297": "[雫るる制服ver._lu喵]", + "11298": "[雫るる制服ver._MUA]", + "11299": "[雫るる制服ver._l别急]", + "11300": "[雫るる制服ver._不屑]", + "11301": "[雫るる制服ver._打call]", + "11302": "[雫るる制服ver._吨吨吨]", + "11303": "[雫るる制服ver._番茄射手]", + "11304": "[雫るる制服ver._感动]", + "11305": "[雫るる制服ver._急了]", + "11306": "[雫るる制服ver._流汗]", + "11307": "[雫るる制服ver._难为情]", + "11308": "[雫るる制服ver._你是懂的]", + "11309": "[雫るる制服ver._期待]", + "11310": "[雫るる制服ver._热]", + "11311": "[雫るる制服ver._撒娇]", + "11312": "[雫るる制服ver._生气]", + "11313": "[雫るる制服ver._天选国V]", + "11314": "[雫るる制服ver._晚安]", + "11315": "[雫るる制服ver._屑]", + "11316": "[雫るる制服ver._学习]", + "11317": "[雫るる制服ver._疑惑]", + "11318": "[雫るる制服ver._自信]", + "11319": "[雫るる制服ver._哭]", + "11320": "[雫るる制服ver._贴贴]", + "11321": "[雫るる制服ver._料理天才]", + "11346": "[小怪兽_一键三连]", + "11347": "[小怪兽_你手短短]", + "11348": "[小怪兽_偷听]", + "11349": "[小怪兽_冒头]", + "11350": "[小怪兽_吃]", + "11351": "[小怪兽_咆哮]", + "11352": "[小怪兽_哭]", + "11353": "[小怪兽_奸笑]", + "11354": "[小怪兽_害羞]", + "11355": "[小怪兽_开心]", + "11356": "[小怪兽_怪兽光线]", + "11357": "[小怪兽_慌张]", + "11358": "[小怪兽_拿来吧你]", + "11359": "[小怪兽_无语]", + "11360": "[小怪兽_点赞]", + "11361": "[小怪兽_生气]", + "11362": "[小怪兽_疑问]", + "11363": "[小怪兽_睡觉]", + "11364": "[小怪兽_石化]", + "11365": "[小怪兽_笑哭]", + "11366": "[小怪兽_耍酷]", + "11367": "[小怪兽_贴贴]", + "11463": "[小怪兽_被惊到了]", + "11464": "[小怪兽_饿饿饭饭]", + "11465": "[小怪兽_心碎]", + "11336": "[装扮小姐姐·偶像舞台_wink]", + "11337": "[装扮小姐姐·偶像舞台_努力营业]", + "11338": "[装扮小姐姐·偶像舞台_单推]", + "11339": "[装扮小姐姐·偶像舞台_唯一的姐]", + "11340": "[装扮小姐姐·偶像舞台_大声唱]", + "11341": "[装扮小姐姐·偶像舞台_pose]", + "11342": "[装扮小姐姐·偶像舞台_showtime]", + "11343": "[装扮小姐姐·偶像舞台_打call]", + "11344": "[装扮小姐姐·偶像舞台_美女登场]", + "11345": "[装扮小姐姐·偶像舞台_难忘今宵]", + "11368": "[兔子镇_哈喽]", + "11369": "[兔子镇_对对对]", + "11370": "[兔子镇_学习呀]", + "11371": "[兔子镇_来我怀里]", + "11372": "[兔子镇_求抱抱]", + "11373": "[兔子镇_晚安]", + "11374": "[兔子镇_真好]", + "11375": "[兔子镇_耍赖]", + "11376": "[兔子镇_惊吓]", + "11377": "[兔子镇_嘤嘤嘤]", + "11378": "[兔子镇_略略略]", + "11379": "[兔子镇_兔兔出拳]", + "11380": "[兔子镇_你好棒棒]", + "11381": "[兔子镇_emmm]", + "11382": "[兔子镇_emo了]", + "11383": "[兔子镇_一键三连]", + "11384": "[兔子镇_画个圈圈]", + "11385": "[兔子镇_专心干饭]", + "11386": "[兔子镇_嗯你说]", + "11387": "[兔子镇_贴贴]", + "11413": "[元气O崽_可怜]", + "11414": "[元气O崽_退退退]", + "11415": "[元气O崽_记仇]", + "11416": "[元气O崽_100]", + "11417": "[元气O崽_真的会谢]", + "11418": "[元气O崽_一颗真心]", + "11419": "[元气O崽_吃我一屁]", + "11420": "[元气O崽_倒计时]", + "11421": "[元气O崽_??]", + "11422": "[元气O崽_给你花花]", + "11423": "[元气O崽_ 生猛气]", + "11424": "[元气O崽_无语]", + "11425": "[元气O崽_坏笑]", + "11426": "[元气O崽_牛哇]", + "11427": "[元气O崽_搬砖狗]", + "11428": "[元气O崽_空降坐标]", + "11429": "[元气O崽_比耶]", + "11430": "[元气O崽_毁灭吧]", + "11431": "[元气O崽_阿巴]", + "11432": "[元气O崽_呜呜呜]", + "11433": "[元气O崽_达成共识]", + "11434": "[元气O崽_kiss kiss]", + "11435": "[元气O崽_冲鸭]", + "11436": "[元气O崽_晚安]", + "11437": "[元气O崽_喵喵喊麦]", + "11438": "[黑鸦鸦_暗中观察]", + "11439": "[黑鸦鸦_多谢款待]", + "11440": "[黑鸦鸦_鸦鸦落泪]", + "11441": "[黑鸦鸦_蒙蔽双眼]", + "11442": "[黑鸦鸦_闪亮登场]", + "11443": "[黑鸦鸦_鸦力山大]", + "11444": "[黑鸦鸦_给心心]", + "11445": "[黑鸦鸦_着急鸦]", + "11446": "[黑鸦鸦_ OK]", + "11447": "[黑鸦鸦_鸦类本能]", + "11448": "[黑鸦鸦_贴贴]", + "11449": "[黑鸦鸦_wink]", + "11450": "[黑鸦鸦_双鸦齐赞]", + "11451": "[黑鸦鸦_鸦鸦警觉]", + "11452": "[黑鸦鸦_蛋!]", + "11453": "[UPOWER_1856528671_Kiss]", + "11454": "[UPOWER_1856528671_别急]", + "11455": "[UPOWER_1856528671_加油]", + "11456": "[UPOWER_1856528671_单推]", + "11457": "[UPOWER_1856528671_哈哈]", + "11458": "[UPOWER_1856528671_早安]", + "13488": "[UPOWER_1856528671_急急急]", + "11460": "[UPOWER_1856528671_网上冲浪]", + "11461": "[UPOWER_1856528671_震惊]", + "13487": "[UPOWER_1856528671_V我50]", + "11466": "[暖暖十周年_hi]", + "11467": "[暖暖十周年_哈哈哈]", + "11468": "[暖暖十周年_打call]", + "11469": "[暖暖十周年_许愿]", + "11470": "[暖暖十周年_抱抱]", + "11471": "[暖暖十周年_让我康康]", + "11472": "[暖暖十周年_wink]", + "11473": "[暖暖十周年_问号]", + "11474": "[暖暖十周年_比心]", + "11475": "[暖暖十周年_生气]", + "11476": "[暖暖十周年_哭哭]", + "11477": "[暖暖十周年_再见]", + "11478": "[暖暖十周年_害怕]", + "11479": "[暖暖十周年_顶瓜瓜]", + "11480": "[暖暖十周年_晚安]", + "11481": "[暖暖十周年_干杯]", + "11482": "[暖暖十周年_十周年快乐]", + "11483": "[暖暖十周年_摸鱼]", + "11484": "[暖暖十周年_下次一定]", + "11485": "[暖暖十周年_无语]", + "11486": "[米洛与米姐姐的冒险_哼]", + "11487": "[米洛与米姐姐的冒险_偷笑]", + "11488": "[米洛与米姐姐的冒险_捧心心]", + "11489": "[米洛与米姐姐的冒险_擦汗]", + "11490": "[米洛与米姐姐的冒险_石化]", + "11491": "[米洛与米姐姐的冒险_说个茄子]", + "11492": "[米洛与米姐姐的冒险_倒地不起]", + "11493": "[米洛与米姐姐的冒险_生气发火]", + "11494": "[米洛与米姐姐的冒险_好耶]", + "11495": "[米洛与米姐姐的冒险_害怕]", + "11496": "[米洛与米姐姐的冒险_???]", + "11497": "[米洛与米姐姐的冒险_哭]", + "11498": "[米洛与米姐姐的冒险_冲冲冲]", + "11499": "[米洛与米姐姐的冒险_震惊]", + "11500": "[米洛与米姐姐的冒险_大笑]", + "11501": "[米洛与米姐姐的冒险_棒棒]", + "11502": "[米洛与米姐姐的冒险_无大语]", + "11503": "[米洛与米姐姐的冒险_星星眼]", + "11504": "[米洛与米姐姐的冒险_卖萌]", + "11505": "[米洛与米姐姐的冒险_调侃]", + "11506": "[米洛与米姐姐的冒险_酸了]", + "11507": "[米洛与米姐姐的冒险_滑稽]", + "11508": "[米洛与米姐姐的冒险_阴险]", + "11509": "[米洛与米姐姐的冒险_流鼻血]", + "11510": "[米洛与米姐姐的冒险_尬住]", + "11511": "[小紫才没有摸鱼_猪脑过载]", + "11512": "[小紫才没有摸鱼_想你]", + "11513": "[小紫才没有摸鱼_上厕所]", + "11514": "[小紫才没有摸鱼_达咩]", + "11515": "[小紫才没有摸鱼_西内]", + "11516": "[小紫才没有摸鱼_疑惑]", + "11517": "[小紫才没有摸鱼_晚安]", + "11518": "[小紫才没有摸鱼_害羞]", + "11519": "[小紫才没有摸鱼_抱大腿]", + "11520": "[小紫才没有摸鱼_心动]", + "11521": "[小紫才没有摸鱼_摸鱼]", + "11522": "[小紫才没有摸鱼_摆烂]", + "11523": "[小紫才没有摸鱼_晕晕]", + "11524": "[小紫才没有摸鱼_打call]", + "11525": "[小紫才没有摸鱼_骄傲]", + "11526": "[小紫才没有摸鱼_震惊]", + "11527": "[小紫才没有摸鱼_拿捏]", + "11528": "[小紫才没有摸鱼_干杯]", + "11529": "[小紫才没有摸鱼_流口水]", + "11530": "[小紫才没有摸鱼_又哭又闹]", + "11531": "[小紫才没有摸鱼_好耶]", + "11532": "[小紫才没有摸鱼_吃瓜]", + "11533": "[小紫才没有摸鱼_尴尬]", + "11534": "[小紫才没有摸鱼_充电中]", + "11535": "[小紫才没有摸鱼_鼓包]", + "11536": "[猫咪公寓_我太难了]", + "11537": "[猫咪公寓_三点几啦]", + "11538": "[猫咪公寓_盯]", + "11539": "[猫咪公寓_加班]", + "11540": "[猫咪公寓_加油]", + "11541": "[猫咪公寓_好耶]", + "11542": "[猫咪公寓_点赞]", + "11543": "[猫咪公寓_饮茶先啦]", + "11544": "[猫咪公寓_兴奋]", + "11545": "[猫咪公寓_害羞]", + "11546": "[猫咪公寓_加鸡腿]", + "11547": "[猫咪公寓_打卡]", + "11548": "[猫咪公寓_再来亿遍]", + "11549": "[猫咪公寓_悠闲]", + "11550": "[猫咪公寓_晚安]", + "11551": "[猫咪公寓_摸鱼]", + "11552": "[2022王者世冠限时_痛苦面具]", + "11553": "[2022王者世冠限时_刺痛我]", + "11554": "[2022王者世冠限时_马上走开]", + "11555": "[2022王者世冠限时_我来C]", + "11556": "[2022王者世冠限时_加鸡腿]", + "11557": "[2022王者世冠限时_太香了]", + "11558": "[2022王者世冠限时_不配吃饭]", + "11559": "[2022王者世冠限时_宝贝双C]", + "11560": "[2022王者世冠限时_谁更辣]", + "11561": "[2022王者世冠限时_名刀司命]", + "11562": "[2022王者世冠限时_我不理解]", + "11563": "[2022王者世冠限时_装起来了]", + "11564": "[2022王者世冠限时_说一个点]", + "11565": "[2022王者世冠限时_我是马可]", + "11566": "[2022王者世冠限时_一诺行为]", + "11567": "[2022王者世冠限时_进厂]", + "11568": "[2022王者世冠限时_逆子]", + "11569": "[2022王者世冠限时_YYDS]", + "11570": "[2022王者世冠限时_裂开]", + "11571": "[2022王者世冠限时_就这]", + "11572": "[2022王者世冠限时_不便透露]", + "11573": "[2022王者世冠限时_干得漂亮]", + "11574": "[2022王者世冠限时_被驴踢了]", + "12666": "[2022王者世冠限时_冲冲冲]", + "12667": "[2022王者世冠限时_冠军]", + "12668": "[2022王者世冠限时_无人在易]", + "12669": "[2022王者世冠限时_无限猖狂]", + "12671": "[2022王者世冠限时_驯龙高手]", + "12672": "[2022王者世冠限时_彻底疯狂]", + "12673": "[2022王者世冠限时_KPL出手]", + "11578": "[古剑奇谭三·无所朝夕_墨镜]", + "11583": "[古剑奇谭三·无所朝夕_钱包空空]", + "11582": "[古剑奇谭三·无所朝夕_地铁老头]", + "11584": "[古剑奇谭三·无所朝夕_麻烦先停停]", + "11594": "[古剑奇谭三·无所朝夕_不过如此]", + "11589": "[古剑奇谭三·无所朝夕_不许摸]", + "11592": "[古剑奇谭三·无所朝夕_弱小]", + "11597": "[古剑奇谭三·无所朝夕_思考]", + "11590": "[古剑奇谭三·无所朝夕_宕机]", + "11579": "[古剑奇谭三·无所朝夕_守护]", + "11598": "[古剑奇谭三·无所朝夕_生气]", + "11575": "[古剑奇谭三·无所朝夕_拈花]", + "11585": "[古剑奇谭三·无所朝夕_喝茶]", + "11588": "[古剑奇谭三·无所朝夕_黑脸]", + "11580": "[古剑奇谭三·无所朝夕_探头]", + "11587": "[古剑奇谭三·无所朝夕_达咩]", + "11576": "[古剑奇谭三·无所朝夕_歪头问号]", + "11581": "[古剑奇谭三·无所朝夕_举]", + "11593": "[古剑奇谭三·无所朝夕_叹气]", + "11586": "[古剑奇谭三·无所朝夕_递花]", + "11591": "[古剑奇谭三·无所朝夕_抱腿]", + "11577": "[古剑奇谭三·无所朝夕_打call]", + "11599": "[古剑奇谭三·无所朝夕_递茶]", + "11596": "[古剑奇谭三·无所朝夕_憧憬]", + "11595": "[古剑奇谭三·无所朝夕_云无柿]", + "11600": "[焦绿猫_886]", + "11601": "[焦绿猫_ 好困]", + "11602": "[焦绿猫_收到]", + "11603": "[焦绿猫_我看看]", + "11604": "[焦绿猫_啥?]", + "11605": "[焦绿猫_来了]", + "11606": "[焦绿猫_无语]", + "11607": "[焦绿猫_吃饭]", + "11608": "[焦绿猫_拜托]", + "11609": "[焦绿猫_学习]", + "11610": "[焦绿猫_ 啊啊啊]", + "11611": "[焦绿猫_摸鱼]", + "11612": "[焦绿猫_ 棒]", + "11613": "[焦绿猫_ 蟹啦]", + "11614": "[焦绿猫_难搞哦]", + "11615": "[焦绿猫_给我投币]", + "11616": "[焦绿猫_+1]", + "11617": "[焦绿猫_敲木鱼]", + "11618": "[焦绿猫_就?这?]", + "11619": "[焦绿猫_多谢]", + "11625": "[装扮小姐姐红墙踏雪_追蝴蝶]", + "11626": "[装扮小姐姐红墙踏雪_弹琵琶]", + "11627": "[装扮小姐姐红墙踏雪_请安]", + "11628": "[装扮小姐姐红墙踏雪_撑伞]", + "11629": "[装扮小姐姐红墙踏雪_惊鸿舞]", + "11630": "[装扮小姐姐红墙踏雪_吟诗作赋]", + "11631": "[装扮小姐姐红墙踏雪_拈花]", + "11632": "[装扮小姐姐红墙踏雪_煮茶]", + "11633": "[装扮小姐姐红墙踏雪_抚琴]", + "11634": "[装扮小姐姐红墙踏雪_赏雪]", + "11637": "[三体_再等一会]", + "11635": "[三体_三体文明]", + "11639": "[三体_面壁计划开启]", + "11638": "[三体_观察]", + "11642": "[三体_那是我的荣幸]", + "11640": "[三体_思想钢印]", + "11641": "[三体_面壁者罗辑]", + "11644": "[三体_他猜对了]", + "11643": "[三体_行动]", + "11636": "[三体_它们已经出发了]", + "11645": "[十字架与三棱锥_加油]", + "11646": "[十字架与三棱锥_生气了]", + "11647": "[十字架与三棱锥_害羞]", + "11648": "[十字架与三棱锥_喵喵]", + "11649": "[十字架与三棱锥_汗]", + "11650": "[十字架与三棱锥_爱心]", + "11651": "[十字架与三棱锥_搞咩呀]", + "11652": "[十字架与三棱锥_不行]", + "11653": "[十字架与三棱锥_探头]", + "11654": "[十字架与三棱锥_赞]", + "11655": "[十字架与三棱锥_和善]", + "11656": "[十字架与三棱锥_疑惑]", + "11657": "[十字架与三棱锥_mua]", + "11658": "[十字架与三棱锥_555]", + "11659": "[十字架与三棱锥_打call]", + "11660": "[2233赛博朋克_冲鸭]", + "11661": "[2233赛博朋克_出发]", + "11662": "[2233赛博朋克_兜风]", + "11663": "[2233赛博朋克_击掌]", + "11664": "[2233赛博朋克_加油]", + "11665": "[2233赛博朋克_上车]", + "11666": "[2233赛博朋克_耍帅]", + "11667": "[2233赛博朋克_赢了]", + "11668": "[2233赛博朋克_在路上]", + "11669": "[2233赛博朋克_注意安全]", + "11670": "[2233山海故事_飞天]", + "11671": "[2233山海故事_弹琴]", + "11672": "[2233山海故事_告辞]", + "11673": "[2233山海故事_爱心发射]", + "11674": "[2233山海故事_思考中]", + "11675": "[2233山海故事_端庄]", + "11676": "[2233山海故事_奔月]", + "11677": "[2233山海故事_勿扰]", + "11678": "[2233山海故事_一起跳舞]", + "11679": "[2233山海故事_好温暖]", + "11680": "[冬日颂歌欧皇套装_6]", + "11691": "[冬日颂歌欧皇套装_抱团取暖]", + "11692": "[冬日颂歌欧皇套装_被雪淹没]", + "11693": "[冬日颂歌欧皇套装_比心]", + "11694": "[冬日颂歌欧皇套装_揣手手]", + "11695": "[冬日颂歌欧皇套装_打雪仗]", + "11696": "[冬日颂歌欧皇套装_冬眠]", + "11697": "[冬日颂歌欧皇套装_堆雪人]", + "11698": "[冬日颂歌欧皇套装_多穿衣服]", + "11699": "[冬日颂歌欧皇套装_非酋反弹]", + "11700": "[冬日颂歌欧皇套装_滑雪]", + "11701": "[冬日颂歌欧皇套装_看看手气]", + "11702": "[冬日颂歌欧皇套装_烤火]", + "11703": "[冬日颂歌欧皇套装_困]", + "11704": "[冬日颂歌欧皇套装_溜冰]", + "11705": "[冬日颂歌欧皇套装_欧皇是你]", + "11706": "[冬日颂歌欧皇套装_泡温泉]", + "11707": "[冬日颂歌欧皇套装_下雪时]", + "11708": "[冬日颂歌欧皇套装_幸运鹅]", + "11709": "[冬日颂歌欧皇套装_郁闷]", + "11681": "[2233暗黑童话_盯]", + "11682": "[2233暗黑童话_恐吓]", + "11683": "[2233暗黑童话_喵]", + "11684": "[2233暗黑童话_fine]", + "11685": "[2233暗黑童话_我飘了]", + "11686": "[2233暗黑童话_诅咒]", + "11687": "[2233暗黑童话_好运魔法]", + "11688": "[2233暗黑童话_魔鬼和天使]", + "11689": "[2233暗黑童话_魔药]", + "11690": "[2233暗黑童话_丘比特之箭]", + "11710": "[汤圆酱_这个好]", + "11711": "[汤圆酱_不对不对]", + "11712": "[汤圆酱_哼]", + "11713": "[汤圆酱_不好意思]", + "11714": "[汤圆酱_不屑]", + "11715": "[汤圆酱_咦]", + "11716": "[汤圆酱_搓手]", + "11717": "[汤圆酱_打call]", + "11718": "[汤圆酱_大脑过载]", + "11719": "[汤圆酱_大笑]", + "11720": "[汤圆酱_点赞]", + "11721": "[汤圆酱_懂你]", + "11722": "[汤圆酱_疯狂点赞]", + "11723": "[汤圆酱_功德+1]", + "11724": "[汤圆酱_害羞]", + "11725": "[汤圆酱_红眼]", + "11726": "[汤圆酱_惊讶]", + "11727": "[汤圆酱_可爱]", + "11728": "[汤圆酱_沙雕]", + "11729": "[汤圆酱_傻笑]", + "11730": "[汤圆酱_耍酷]", + "11731": "[汤圆酱_说的就是你]", + "11732": "[汤圆酱_酸了]", + "11733": "[汤圆酱_偷瞄]", + "11734": "[汤圆酱_偷笑]", + "11735": "[汤圆酱_投币]", + "11736": "[汤圆酱_细品]", + "11737": "[汤圆酱_星星眼]", + "11738": "[汤圆酱_疑问]", + "11739": "[汤圆酱_装傻]", + "11740": "[UPOWER_43272050_暗中观察]", + "11741": "[UPOWER_43272050_戳戳]", + "11742": "[UPOWER_43272050_好酸]", + "11743": "[UPOWER_43272050_泪目]", + "11744": "[UPOWER_43272050_喷鼻血]", + "11745": "[UPOWER_43272050_让我康康]", + "11746": "[UPOWER_43272050_笑嘻了]", + "11747": "[UPOWER_43272050_震惊]", + "11748": "[UPOWER_43272050_知识增加]", + "11749": "[UPOWER_43272050_加大力度]", + "11750": "[黑猫大少爷_喜欢]", + "11751": "[黑猫大少爷_嘻嘻]", + "11752": "[黑猫大少爷_信号丢失]", + "11753": "[黑猫大少爷_感觉不对]", + "11754": "[黑猫大少爷_歇咯]", + "11755": "[黑猫大少爷_zzz]", + "11756": "[黑猫大少爷_vme50]", + "11757": "[黑猫大少爷_小猫偷看]", + "11758": "[黑猫大少爷_哭哭]", + "11759": "[黑猫大少爷_听不得]", + "11760": "[黑猫大少爷_吃得饱饱]", + "11761": "[黑猫大少爷_菜菜]", + "11762": "[黑猫大少爷_小猫来啦!]", + "11763": "[黑猫大少爷_我装的]", + "11764": "[黑猫大少爷_力量三]", + "11765": "[黑猫大少爷_小猫出拳]", + "11766": "[黑猫大少爷_清澈]", + "11767": "[黑猫大少爷_谢谢]", + "11768": "[黑猫大少爷_自闭啦]", + "11769": "[黑猫大少爷_一键三连]", + "11770": "[黑猫大少爷_原来如此!]", + "11771": "[黑猫大少爷_麦外敷]", + "11772": "[UPOWER_8881297_6]", + "11773": "[UPOWER_8881297_kira]", + "11774": "[UPOWER_8881297_欸嘿]", + "11775": "[UPOWER_8881297_抱抱妮]", + "11776": "[UPOWER_8881297_哒咩]", + "11777": "[UPOWER_8881297_放电妮]", + "11778": "[UPOWER_8881297_摊摊妮]", + "11779": "[UPOWER_8881297_ICU]", + "11780": "[UPOWER_8881297_贴贴]", + "11781": "[UPOWER_8881297_果咩]", + "11788": "[仙王第二弹_盯]", + "11794": "[仙王第二弹_期待]", + "11790": "[仙王第二弹_和善]", + "11797": "[仙王第二弹_突然出现]", + "11787": "[仙王第二弹_得意]", + "11798": "[仙王第二弹_委屈]", + "11796": "[仙王第二弹_石化]", + "11795": "[仙王第二弹_生日快乐]", + "11785": "[仙王第二弹_蹭]", + "11792": "[仙王第二弹_怒]", + "11793": "[仙王第二弹_贫穷]", + "11791": "[仙王第二弹_计划通]", + "11789": "[仙王第二弹_放飞]", + "11784": "[仙王第二弹_抱]", + "11786": "[仙王第二弹_吃瓜]", + "11799": "[UPOWER_4637682_递裙子]", + "11800": "[UPOWER_4637682_剪刀]", + "11801": "[UPOWER_4637682_盯]", + "11802": "[UPOWER_4637682_喝红茶]", + "11803": "[UPOWER_4637682_辣眼睛]", + "11804": "[UPOWER_4637682_吃面包]", + "11806": "[UPOWER_4637682_咖啡]", + "13205": "[UPOWER_4637682_若有所思]", + "13206": "[UPOWER_4637682_感兴趣]", + "13207": "[UPOWER_4637682_QAQ]", + "11809": "[早稻叽潮妹_你条粉肠]", + "11810": "[早稻叽潮妹_你小子]", + "11811": "[早稻叽潮妹_果咩]", + "11812": "[早稻叽潮妹_寄]", + "11813": "[早稻叽潮妹_猪鼻]", + "11814": "[早稻叽潮妹_急急急]", + "11815": "[早稻叽潮妹_嘿嘿]", + "11816": "[早稻叽潮妹_安晚]", + "11817": "[早稻叽潮妹_食不食油饼]", + "11818": "[早稻叽潮妹_甜甜甜]", + "11819": "[早稻叽潮妹_流汗了]", + "11820": "[早稻叽潮妹_两眼一黑]", + "11821": "[早稻叽潮妹_赚大米]", + "11822": "[早稻叽潮妹_出生]", + "11823": "[早稻叽潮妹_略略略]", + "11824": "[早稻叽潮妹_玩玩你的]", + "11825": "[早稻叽潮妹_ovO]", + "11826": "[早稻叽潮妹_不愧是我]", + "11827": "[早稻叽潮妹_叽脑过载]", + "11828": "[早稻叽潮妹_厚礼谢]", + "11829": "[早稻叽潮妹_庆祝]", + "11830": "[早稻叽潮妹_两眼一亮]", + "11831": "[早稻叽潮妹_笨笨笨]", + "11832": "[早稻叽潮妹_你先别急]", + "11833": "[早稻叽潮妹_看看你的]", + "11834": "[装扮小姐姐梦幻冬季_揉脸]", + "11835": "[装扮小姐姐梦幻冬季_啾咪]", + "11836": "[装扮小姐姐梦幻冬季_下雪了]", + "11837": "[装扮小姐姐梦幻冬季_圣诞老人]", + "11838": "[装扮小姐姐梦幻冬季_多喝热水]", + "11839": "[装扮小姐姐梦幻冬季_扔]", + "11840": "[装扮小姐姐梦幻冬季_生气]", + "11841": "[装扮小姐姐梦幻冬季_贴贴]", + "11842": "[装扮小姐姐梦幻冬季_没米了]", + "11843": "[装扮小姐姐梦幻冬季_冲鸭]", + "11844": "[装扮小姐姐梦幻冬季_累了]", + "11845": "[装扮小姐姐梦幻冬季_斯密马赛]", + "11846": "[装扮小姐姐梦幻冬季_告辞]", + "11847": "[装扮小姐姐梦幻冬季_吃我一拳]", + "11848": "[装扮小姐姐梦幻冬季_乌拉]", + "11849": "[装扮小姐姐梦幻冬季_委屈]", + "11850": "[装扮小姐姐梦幻冬季_溜冰]", + "11851": "[装扮小姐姐梦幻冬季_好耶]", + "11852": "[装扮小姐姐梦幻冬季_硬撑罢了]", + "11853": "[装扮小姐姐梦幻冬季_摸鱼]", + "11854": "[装扮小姐姐梦幻冬季_注意保暖]", + "11855": "[装扮小姐姐梦幻冬季_抽我]", + "11856": "[装扮小姐姐梦幻冬季_疑问]", + "11857": "[装扮小姐姐梦幻冬季_抱抱]", + "11858": "[装扮小姐姐梦幻冬季_烤红薯]", + "11859": "[装扮小姐姐梦幻冬季_Power!]", + "11860": "[装扮小姐姐梦幻冬季_堆雪人]", + "11861": "[装扮小姐姐梦幻冬季_好的]", + "11862": "[装扮小姐姐梦幻冬季_滑雪]", + "11863": "[装扮小姐姐梦幻冬季_吃火锅]", + "11875": "[天曰小雏圣诞_圣诞小雏]", + "11883": "[天曰小雏圣诞_圣诞猫猫]", + "11885": "[天曰小雏圣诞_麋鹿]", + "11864": "[天曰小雏圣诞_猪蹄]", + "11865": "[天曰小雏圣诞_233]", + "11866": "[天曰小雏圣诞_mua]", + "11867": "[天曰小雏圣诞_疑问]", + "11868": "[天曰小雏圣诞_早安]", + "11869": "[天曰小雏圣诞_盯]", + "11870": "[天曰小雏圣诞_困]", + "11871": "[天曰小雏圣诞_no]", + "11872": "[天曰小雏圣诞_抱抱]", + "11873": "[天曰小雏圣诞_流汗]", + "11874": "[天曰小雏圣诞_睡着]", + "11876": "[天曰小雏圣诞_猫猫]", + "11877": "[天曰小雏圣诞_逮捕]", + "11878": "[天曰小雏圣诞_哭泣]", + "11879": "[天曰小雏圣诞_害羞]", + "11880": "[天曰小雏圣诞_加油]", + "11881": "[天曰小雏圣诞_失落]", + "11882": "[天曰小雏圣诞_黑化]", + "11884": "[天曰小雏圣诞_晚上好]", + "11886": "[天曰小雏圣诞_爱心]", + "11887": "[天曰小雏圣诞_大笑]", + "11888": "[天曰小雏圣诞_生气]", + "11889": "[冷兔宝宝_是个人才]", + "11890": "[冷兔宝宝_哼]", + "11891": "[冷兔宝宝_偷看]", + "11892": "[冷兔宝宝_我自闭了]", + "11893": "[冷兔宝宝_哦]", + "11894": "[冷兔宝宝_你走]", + "11895": "[冷兔宝宝_幺幺零吗]", + "11896": "[冷兔宝宝_撒娇]", + "11897": "[冷兔宝宝_宝宝来咯]", + "11898": "[冷兔宝宝_发现笨蛋]", + "11899": "[冷兔宝宝_看不见我]", + "11900": "[冷兔宝宝_开心]", + "11901": "[冷兔宝宝_搬砖]", + "11902": "[冷兔宝宝_我错了]", + "11903": "[冷兔宝宝_哭]", + "11904": "[冷兔宝宝_好喜欢]", + "11905": "[冷兔宝宝_得瑟]", + "11906": "[冷兔宝宝_NO]", + "11907": "[冷兔宝宝_聊十块钱]", + "11908": "[冷兔宝宝_唉]", + "11909": "[冷兔宝宝_算我求你]", + "11910": "[冷兔宝宝_记小本本]", + "11911": "[冷兔宝宝_飘飘欲仙]", + "11912": "[冷兔宝宝_委屈]", + "11913": "[冷兔宝宝_我来啦]", + "11914": "[王牌御史_冲击]", + "11915": "[王牌御史_发动]", + "11916": "[王牌御史_冷酷]", + "11917": "[王牌御史_生无可恋]", + "11918": "[王牌御史_咆哮]", + "11919": "[王牌御史_我想开了]", + "11920": "[王牌御史_承让了]", + "11921": "[王牌御史_点赞]", + "11922": "[王牌御史_啊啊啊啊啊]", + "11923": "[王牌御史_蓝瘦香菇]", + "11932": "[冥冥meichan_太爱钱了]", + "11924": "[冥冥meichan_不可以]", + "11928": "[冥冥meichan_优雅红茶]", + "11936": "[冥冥meichan_哭哭]", + "11946": "[冥冥meichan_不想输]", + "11937": "[冥冥meichan_开车]", + "11943": "[冥冥meichan_大钻戒]", + "11933": "[冥冥meichan_带走]", + "11948": "[冥冥meichan_我好了]", + "11940": "[冥冥meichan_嗨呀]", + "11927": "[冥冥meichan_好喜欢哦]", + "11934": "[冥冥meichan_给你一拳]", + "11929": "[冥冥meichan_理发店]", + "11944": "[冥冥meichan_典]", + "11931": "[冥冥meichan_信积拉奶]", + "11925": "[冥冥meichan_饭饭]", + "11926": "[冥冥meichan_盯]", + "11941": "[冥冥meichan_流汗]", + "11942": "[冥冥meichan_晚安]", + "11938": "[冥冥meichan_mua]", + "11939": "[冥冥meichan_关注冥冥]", + "11947": "[冥冥meichan_你是懂的]", + "11935": "[冥冥meichan_你懂个P]", + "11945": "[冥冥meichan_钱不够啊]", + "11930": "[冥冥meichan_寄]", + "11963": "[巴萨俱乐部_很慌]", + "11964": "[巴萨俱乐部_无语]", + "11965": "[巴萨俱乐部_帽子戏法]", + "11966": "[巴萨俱乐部_加油]", + "11967": "[巴萨俱乐部_生气]", + "11968": "[巴萨俱乐部_黄牌警告]", + "11969": "[巴萨俱乐部_为什么]", + "11970": "[巴萨俱乐部_击掌]", + "11971": "[巴萨俱乐部_绝杀]", + "11972": "[巴萨俱乐部_安慰]", + "11973": "[巴萨俱乐部_必胜]", + "11974": "[巴萨俱乐部_怒]", + "11975": "[巴萨俱乐部_红牌罚下]", + "11976": "[巴萨俱乐部_沮丧]", + "11977": "[巴萨俱乐部_不甘心]", + "11978": "[巴萨俱乐部_滑跪]", + "11979": "[巴萨俱乐部_死守]", + "11980": "[巴萨俱乐部_熬夜]", + "11981": "[巴萨俱乐部_冠军]", + "11982": "[巴萨俱乐部_好球]", + "11983": "[巴萨俱乐部_点球]", + "11984": "[巴萨俱乐部_打脸]", + "11985": "[巴萨俱乐部_射门]", + "11986": "[巴萨俱乐部_球进啦!]", + "11987": "[巴萨俱乐部_顶你]", + "11988": "[巴萨俱乐部_干杯]", + "11989": "[巴萨俱乐部_颠球]", + "11990": "[巴萨俱乐部_不忍直视]", + "11991": "[巴萨俱乐部_撞胸]", + "11992": "[巴萨俱乐部_扑球]", + "12234": "[巴萨俱乐部_红蓝巴萨]", + "12022": "[可爱联盟_哭哭]", + "12023": "[可爱联盟_吐舌头]", + "12024": "[可爱联盟_纳闷]", + "12025": "[可爱联盟_疑问]", + "12026": "[可爱联盟_拜拜]", + "12027": "[可爱联盟_生气]", + "12028": "[可爱联盟_无语]", + "12029": "[可爱联盟_乖巧]", + "12030": "[可爱联盟_爱心]", + "12031": "[可爱联盟_心动]", + "12032": "[可爱联盟_惊吓]", + "12033": "[可爱联盟_激动]", + "12034": "[可爱联盟_骄傲]", + "12035": "[可爱联盟_酷]", + "12036": "[可爱联盟_亲亲]", + "12037": "[可爱联盟_捶]", + "12038": "[可爱联盟_害羞]", + "12039": "[可爱联盟_搬砖]", + "12040": "[可爱联盟_冲]", + "12041": "[可爱联盟_滑板]", + "12042": "[可爱联盟_耶]", + "12043": "[可爱联盟_看戏]", + "12044": "[可爱联盟_叉出去]", + "12045": "[可爱联盟_抱抱]", + "12046": "[可爱联盟_瘫坐]", + "12048": "[UPOWER_590490400_卖萌]", + "12049": "[UPOWER_590490400_哭哭]", + "12050": "[UPOWER_590490400_么么哒]", + "12051": "[UPOWER_590490400_哈哈哈]", + "12052": "[UPOWER_590490400_来玩]", + "12053": "[UPOWER_590490400_优雅]", + "12054": "[UPOWER_590490400_不高兴]", + "12055": "[UPOWER_590490400_贴贴]", + "12056": "[UPOWER_590490400_喵]", + "13568": "[UPOWER_590490400_哈喽]", + "13569": "[UPOWER_590490400_暗中观察]", + "13570": "[UPOWER_590490400_啊]", + "13572": "[UPOWER_590490400_轻蔑]", + "13573": "[UPOWER_590490400_什么]", + "13574": "[UPOWER_590490400_躺]", + "13575": "[UPOWER_590490400_凶]", + "12065": "[雪狐桑生日纪念_好辛苦呀]", + "12057": "[雪狐桑生日纪念_大笨蛋]", + "12063": "[雪狐桑生日纪念_伸耳朵]", + "12068": "[雪狐桑生日纪念_装傻]", + "12066": "[雪狐桑生日纪念_喂桃]", + "12069": "[雪狐桑生日纪念_睡了]", + "12081": "[雪狐桑生日纪念_开心]", + "12071": "[雪狐桑生日纪念_不屑]", + "12062": "[雪狐桑生日纪念_反弹]", + "12072": "[雪狐桑生日纪念_大尾巴]", + "12076": "[雪狐桑生日纪念_惊]", + "12060": "[雪狐桑生日纪念_委屈]", + "12074": "[雪狐桑生日纪念_不干了]", + "12059": "[雪狐桑生日纪念_要闹了]", + "12078": "[雪狐桑生日纪念_撒花]", + "12079": "[雪狐桑生日纪念_捂脸]", + "12067": "[雪狐桑生日纪念_让我康康]", + "12073": "[雪狐桑生日纪念_害怕]", + "12075": "[雪狐桑生日纪念_饿饿]", + "12061": "[雪狐桑生日纪念_挠头]", + "12080": "[雪狐桑生日纪念_得加钱]", + "12058": "[雪狐桑生日纪念_加班]", + "12064": "[雪狐桑生日纪念_发光]", + "12070": "[雪狐桑生日纪念_思考]", + "12077": "[雪狐桑生日纪念_麻袋]", + "12082": "[UZI_洗澡]", + "12083": "[UZI_痛苦面具]", + "12084": "[UZI_喷水]", + "12085": "[UZI_疑惑]", + "12086": "[UZI_你瞅啥]", + "12087": "[UZI_比心]", + "12088": "[UZI_目瞪狗呆]", + "12089": "[UZI_脑壳疼]", + "12090": "[UZI_散财童子]", + "12091": "[UZI_重拳出击]", + "12092": "[UZI_乌理学]", + "12093": "[UZI_emmm]", + "12094": "[UZI_木已成舟]", + "12095": "[UZI_不听不听]", + "12096": "[UZI_无语]", + "12100": "[传武_切~]", + "12101": "[传武_严肃脸]", + "12102": "[传武_令人火大]", + "12103": "[传武_害羞]", + "12104": "[传武_哇哈哈哈]", + "12105": "[传武_行吧]", + "12106": "[传武_蛤?]", + "12107": "[传武_咦?]", + "12108": "[传武_抓狂]", + "12109": "[传武_哼~]", + "12110": "[桃桃喵和荔枝喵_心碎]", + "12111": "[桃桃喵和荔枝喵_下次一定]", + "12112": "[桃桃喵和荔枝喵_没钱啦]", + "12113": "[桃桃喵和荔枝喵_爱你]", + "12114": "[桃桃喵和荔枝喵_拜托]", + "12115": "[桃桃喵和荔枝喵_生气]", + "12116": "[桃桃喵和荔枝喵_学习]", + "12117": "[桃桃喵和荔枝喵_加油]", + "12118": "[桃桃喵和荔枝喵_什么]", + "12119": "[桃桃喵和荔枝喵_惊讶]", + "12120": "[桃桃喵和荔枝喵_不要]", + "12121": "[桃桃喵和荔枝喵_在么]", + "12122": "[桃桃喵和荔枝喵_开心]", + "12123": "[桃桃喵和荔枝喵_无语]", + "12124": "[桃桃喵和荔枝喵_一键三连]", + "12125": "[哈娜hanna_叼花]", + "12126": "[哈娜hanna_冲]", + "12127": "[哈娜hanna_撒花]", + "12128": "[哈娜hanna_抱抱]", + "12129": "[哈娜hanna_问号]", + "12130": "[哈娜hanna_ok]", + "12131": "[哈娜hanna_苦力怕]", + "12132": "[哈娜hanna_无奈]", + "12133": "[哈娜hanna_震惊]", + "12134": "[哈娜hanna_流汗]", + "12135": "[哈娜hanna_倒拇指]", + "12136": "[哈娜hanna_害怕]", + "12137": "[哈娜hanna_递心]", + "12138": "[哈娜hanna_晚安]", + "12139": "[哈娜hanna_呜呜]", + "12140": "[哈娜hanna_急了]", + "12141": "[哈娜hanna_给你一拳]", + "12142": "[哈娜hanna_好耶]", + "12143": "[哈娜hanna_求求了]", + "12144": "[哈娜hanna_大拇指]", + "12161": "[万圣街_笑摸狗头]", + "12155": "[万圣街_撒币了]", + "12164": "[万圣街_诅咒你]", + "12163": "[万圣街_揍你]", + "12151": "[万圣街_好耶]", + "12160": "[万圣街_先走一步]", + "12162": "[万圣街_营业微笑]", + "12149": "[万圣街_乖巧]", + "12152": "[万圣街_困晕]", + "12150": "[万圣街_好兄弟]", + "12157": "[万圣街_问就是买]", + "12146": "[万圣街_比心]", + "12148": "[万圣街_多喝热水]", + "12145": "[万圣街_报警了]", + "12154": "[万圣街_求求了]", + "12156": "[万圣街_贴贴]", + "12147": "[万圣街_打工中]", + "12159": "[万圣街_吸猫中]", + "12158": "[万圣街_我盯着呢]", + "12153": "[万圣街_亲兄弟]", + "12165": "[Stella和Dianna_震惊]", + "12166": "[Stella和Dianna_花痴脸]", + "12167": "[Stella和Dianna_撒花]", + "12168": "[Stella和Dianna_微笑]", + "12169": "[Stella和Dianna_生气]", + "12170": "[Stella和Dianna_哈哈哈]", + "12171": "[Stella和Dianna_没钱]", + "12172": "[Stella和Dianna_喜欢]", + "12173": "[Stella和Dianna_扶墙]", + "12174": "[Stella和Dianna_赞]", + "12175": "[Stella和Dianna_脸红]", + "12176": "[Stella和Dianna_问号]", + "12177": "[Stella和Dianna_emmm]", + "12178": "[Stella和Dianna_OKAY]", + "12179": "[Stella和Dianna_暗中观察]", + "12180": "[Stella和Dianna_给你心心]", + "12181": "[Stella和Dianna_仙女棒]", + "12182": "[Stella和Dianna_冲呀]", + "12183": "[Stella和Dianna_爱心+1]", + "12184": "[Stella和Dianna_心碎]", + "12185": "[Stella和Dianna_石化]", + "12186": "[Stella和Dianna_耶]", + "12187": "[Stella和Dianna_帅]", + "12188": "[Stella和Dianna_什么?!]", + "12189": "[Stella和Dianna_绝望]", + "12190": "[Stella和Dianna_酷]", + "12191": "[Stella和Dianna_学习]", + "12192": "[暖雪_出手]", + "12193": "[暖雪_大佬]", + "12194": "[暖雪_比心]", + "12195": "[暖雪_厉害!]", + "12196": "[暖雪_走火入魔]", + "12197": "[暖雪_再见]", + "12198": "[暖雪_开宝箱]", + "12199": "[暖雪_看我的!]", + "12200": "[暖雪_就这?]", + "12201": "[暖雪_寄]", + "12202": "[暖雪_开心]", + "12203": "[暖雪_冒头]", + "12204": "[小王子_思考]", + "12205": "[小王子_委屈]", + "12206": "[小王子_偷看]", + "12207": "[小王子_睡觉]", + "12208": "[小王子_生气]", + "12209": "[小王子_期待]", + "12210": "[小王子_震惊]", + "12211": "[小王子_比心]", + "12212": "[小王子_小熊站岗]", + "12213": "[小王子_不好意思]", + "12214": "[虎皮喵一家_吃惊]", + "12215": "[虎皮喵一家_呕]", + "12216": "[虎皮喵一家_头笑掉了]", + "12217": "[虎皮喵一家_比心]", + "12218": "[虎皮喵一家_OK]", + "12219": "[虎皮喵一家_略略略]", + "12220": "[虎皮喵一家_刺激]", + "12221": "[虎皮喵一家_狗带]", + "12222": "[虎皮喵一家_喜欢]", + "12223": "[虎皮喵一家_吹牛]", + "12224": "[虎皮喵一家_开心]", + "12225": "[虎皮喵一家_啵叽]", + "12226": "[虎皮喵一家_沙雕]", + "12227": "[虎皮喵一家_摸摸]", + "12228": "[虎皮喵一家_哭哭]", + "12229": "[虎皮喵一家_赞]", + "12230": "[虎皮喵一家_干杯~]", + "12231": "[虎皮喵一家_谢谢]", + "12232": "[虎皮喵一家_超棒]", + "12233": "[虎皮喵一家_冲鸭]", + "12235": "[魔狼咪莉娅_色小鬼]", + "12236": "[魔狼咪莉娅_对不起]", + "12237": "[魔狼咪莉娅_不要]", + "12238": "[魔狼咪莉娅_魑魅魍魉]", + "12239": "[魔狼咪莉娅_G]", + "12240": "[魔狼咪莉娅_给你一拳]", + "12241": "[魔狼咪莉娅_奸笑]", + "12242": "[魔狼咪莉娅_prpr]", + "12243": "[魔狼咪莉娅_mua]", + "12244": "[魔狼咪莉娅_咪~!]", + "12245": "[魔狼咪莉娅_天才]", + "12246": "[魔狼咪莉娅_5454]", + "12247": "[魔狼咪莉娅_辛苦了]", + "12248": "[魔狼咪莉娅_别骂了]", + "12249": "[魔狼咪莉娅_HP0]", + "12250": "[魔狼咪莉娅_S S W]", + "12251": "[魔狼咪莉娅_汪]", + "12252": "[魔狼咪莉娅_变态]", + "12253": "[魔狼咪莉娅_哭泣]", + "12254": "[魔狼咪莉娅_?]", + "12255": "[魔狼咪莉娅_嚼嚼嚼]", + "12256": "[魔狼咪莉娅_阿咪陀佛]", + "12257": "[魔狼咪莉娅_晚安]", + "12258": "[魔狼咪莉娅_debubu]", + "12259": "[魔狼咪莉娅_谢谢]", + "12260": "[M木糖M_炸机]", + "12261": "[M木糖M_裂开]", + "12262": "[M木糖M_阴霾]", + "12263": "[M木糖M_记仇]", + "12264": "[M木糖M_疑惑]", + "12265": "[M木糖M_生气]", + "12266": "[M木糖M_一定是这样]", + "12267": "[M木糖M_想到了]", + "12268": "[M木糖M_掏键盘]", + "12269": "[M木糖M_砸键盘]", + "12270": "[M木糖M_无神]", + "12271": "[M木糖M_害羞]", + "12272": "[M木糖M_维修]", + "12273": "[M木糖M_鼠标]", + "12274": "[M木糖M_冒烟]", + "12275": "[M木糖M_开心]", + "12276": "[M木糖M_敲键盘]", + "12277": "[M木糖M_爆锤]", + "12278": "[M木糖M_bug]", + "12279": "[M木糖M_helloworld]", + "12303": "[高田熊圣诞特辑_兴奋]", + "12304": "[高田熊圣诞特辑_贴贴]", + "12305": "[高田熊圣诞特辑_惊恐]", + "12306": "[高田熊圣诞特辑_升天]", + "12307": "[高田熊圣诞特辑_emo]", + "12309": "[高田熊圣诞特辑_手舞足蹈]", + "12310": "[高田熊圣诞特辑_问号]", + "12311": "[高田熊圣诞特辑_大哭]", + "12312": "[高田熊圣诞特辑_耶]", + "12313": "[高田熊圣诞特辑_穿衣保暖]", + "12314": "[高田熊圣诞特辑_吐魂]", + "12316": "[高田熊圣诞特辑_惊]", + "12317": "[高田熊圣诞特辑_亲亲]", + "12318": "[高田熊圣诞特辑_慌张]", + "12320": "[高田熊圣诞特辑_啵啵]", + "12321": "[高田熊圣诞特辑_无语]", + "12322": "[高田熊圣诞特辑_疑问]", + "12324": "[高田熊圣诞特辑_发火]", + "12325": "[高田熊圣诞特辑_晴天]", + "12326": "[高田熊圣诞特辑_下雨]", + "12327": "[高田熊圣诞特辑_愤怒]", + "12328": "[高田熊圣诞特辑_咬牙切齿]", + "12329": "[高田熊圣诞特辑_装死]", + "12330": "[高田熊圣诞特辑_点赞]", + "12331": "[高田熊圣诞特辑_开心]", + "12332": "[高田熊圣诞特辑_探头]", + "12308": "[高田熊圣诞特辑_雪人]", + "12315": "[高田熊圣诞特辑_恭敬]", + "12323": "[高田熊圣诞特辑_硬撑]", + "12319": "[高田熊圣诞特辑_伸脚]", + "12363": "[UPOWER_33605910_关我啊事]", + "12364": "[UPOWER_33605910_要喝牛奶]", + "12366": "[UPOWER_33605910_可爱捏]", + "12367": "[UPOWER_33605910_哇]", + "12368": "[UPOWER_33605910_怎么]", + "12369": "[UPOWER_33605910_气]", + "12370": "[UPOWER_33605910_抱抱]", + "12371": "[UPOWER_33605910_哭哭]", + "12372": "[UPOWER_33605910_别急]", + "14521": "[UPOWER_33605910_棒棒]", + "14522": "[UPOWER_33605910_流汗]", + "14524": "[UPOWER_33605910_好]", + "14526": "[UPOWER_33605910_嗨嗨]", + "14527": "[UPOWER_33605910_醉奶]", + "14529": "[UPOWER_33605910_你饿吗]", + "14555": "[UPOWER_33605910_笑拇指]", + "14556": "[UPOWER_33605910_嗦嗨嗨]", + "12373": "[还有醒着的么2.0_PRPR]", + "12374": "[还有醒着的么2.0_W]", + "12375": "[还有醒着的么2.0_暗中观察]", + "12376": "[还有醒着的么2.0_爆破]", + "12377": "[还有醒着的么2.0_别急]", + "12378": "[还有醒着的么2.0_达咩]", + "12379": "[还有醒着的么2.0_电量低]", + "12380": "[还有醒着的么2.0_放我出去]", + "12381": "[还有醒着的么2.0_给点]", + "12382": "[还有醒着的么2.0_好困]", + "12383": "[还有醒着的么2.0_急]", + "12384": "[还有醒着的么2.0_开车]", + "12385": "[还有醒着的么2.0_泪目]", + "12386": "[还有醒着的么2.0_生气]", + "12387": "[还有醒着的么2.0_探头]", + "12388": "[还有醒着的么2.0_铁咩]", + "12389": "[还有醒着的么2.0_晚安]", + "12390": "[还有醒着的么2.0_无语]", + "12391": "[还有醒着的么2.0_嘻嘻]", + "12392": "[还有醒着的么2.0_小丑]", + "12393": "[还有醒着的么2.0_歇了]", + "12394": "[还有醒着的么2.0_震惊]", + "12395": "[还有醒着的么2.0_打包]", + "12396": "[还有醒着的么2.0_戴好]", + "12397": "[还有醒着的么2.0_礼物]", + "12398": "[狸喵唤!太子_阿巴]", + "12399": "[狸喵唤!太子_冲冲冲]", + "12400": "[狸喵唤!太子_帅气]", + "12401": "[狸喵唤!太子_震惊]", + "12402": "[狸喵唤!太子_晚安]", + "12403": "[狸喵唤!太子_???]", + "12404": "[狸喵唤!太子_吃瓜]", + "12405": "[狸喵唤!太子_Hi]", + "12406": "[狸喵唤!太子_酷]", + "12407": "[狸喵唤!太子_哇哦]", + "12408": "[狸喵唤!太子_拿去]", + "12409": "[狸喵唤!太子_尾巴点赞]", + "12410": "[狸喵唤!太子_HAHAHA]", + "12411": "[狸喵唤!太子_泪目]", + "12412": "[狸喵唤!太子_冒出]", + "12413": "[狸喵唤!太子_害羞]", + "12414": "[狸喵唤!太子_饮茶啦]", + "12415": "[狸喵唤!太子_升华了]", + "12416": "[狸喵唤!太子_黄豆流汗]", + "12417": "[狸喵唤!太子_秃了]", + "12418": "[狸喵唤!太子_我来了]", + "12419": "[狸喵唤!太子_捧心心]", + "12420": "[狸喵唤!太子_工作]", + "12421": "[狸喵唤!太子_吨吨吨]", + "12422": "[狸喵唤!太子_黑化]", + "12453": "[邓峰萌萌_你干嘛呀]", + "12454": "[邓峰萌萌_困了]", + "12455": "[邓峰萌萌_无语]", + "12456": "[邓峰萌萌_救命啊]", + "12457": "[邓峰萌萌_废物挺好]", + "12458": "[邓峰萌萌_?]", + "12459": "[邓峰萌萌_!!?]", + "12460": "[邓峰萌萌_心死了]", + "12461": "[邓峰萌萌_反了你了]", + "12462": "[邓峰萌萌_麻了]", + "12463": "[邓峰萌萌_呜呜]", + "12464": "[邓峰萌萌_恭喜发财]", + "12465": "[邓峰萌萌_你醒啦]", + "12466": "[邓峰萌萌_Hi]", + "12467": "[邓峰萌萌_切]", + "12468": "[邓峰萌萌_暗中观察]", + "12469": "[邓峰萌萌_脑子进水]", + "12470": "[邓峰萌萌_呔]", + "12471": "[邓峰萌萌_愁啊]", + "12472": "[邓峰萌萌_投降]", + "12473": "[邓峰萌萌_无助]", + "12474": "[邓峰萌萌_surprise]", + "12475": "[邓峰萌萌_生活我]", + "12476": "[邓峰萌萌_点点点]", + "12477": "[邓峰萌萌_wink]", + "12478": "[邓峰萌萌_我会]", + "12479": "[邓峰萌萌_哼]", + "12480": "[邓峰萌萌_变猪]", + "12481": "[邓峰萌萌_我不理解]", + "12482": "[邓峰萌萌_垃圾挺好]", + "12483": "[双王_超级喜欢]", + "12484": "[双王_等消息]", + "12485": "[双王_学习]", + "12486": "[双王_捏脸]", + "12487": "[双王_喜欢]", + "12488": "[双王_晚安]", + "12489": "[双王_怎么还不回]", + "12490": "[双王_哇哦]", + "12491": "[双王_糟糕]", + "12492": "[双王_啊哈]", + "12493": "[鬼刀风玲_吃瓜]", + "12494": "[鬼刀风玲_美女贴贴]", + "12496": "[鬼刀风玲_生气]", + "12497": "[鬼刀风玲_鲨了你]", + "12498": "[鬼刀风玲_多读书]", + "12499": "[鬼刀风玲_比心]", + "12500": "[鬼刀风玲_一键三连]", + "12501": "[鬼刀风玲_睡了]", + "12502": "[鬼刀风玲_干杯!]", + "12503": "[鬼刀风玲_wink]", + "12504": "[鬼刀风玲_拖走]", + "12505": "[鬼刀风玲_摸鱼]", + "12506": "[鬼刀风玲_?]", + "12507": "[鬼刀风玲_额]", + "12508": "[鬼刀风玲_达咩]", + "12509": "[百变星瞳_80]", + "12510": "[百变星瞳_别急]", + "12511": "[百变星瞳_才八点]", + "12512": "[百变星瞳_大小眼疑惑]", + "12513": "[百变星瞳_大笑]", + "12514": "[百变星瞳_豆豆眼]", + "12515": "[百变星瞳_好好好]", + "12516": "[百变星瞳_红温]", + "12517": "[百变星瞳_回马枪]", + "12518": "[百变星瞳_急了]", + "12519": "[百变星瞳_假哭]", + "12520": "[百变星瞳_姐好着呢]", + "12521": "[百变星瞳_两眼一黑]", + "12522": "[百变星瞳_六爷]", + "12523": "[百变星瞳_舞步]", + "12524": "[百变星瞳_小星星]", + "12525": "[百变星瞳_正步]", + "12526": "[百变星瞳_圣诞美女]", + "12527": "[百变星瞳_要礼物]", + "12528": "[百变星瞳_礼物盒跳出]", + "14222": "[百变星瞳_差不多得了]", + "14223": "[百变星瞳_发癫]", + "14224": "[百变星瞳_敬礼!]", + "14225": "[百变星瞳_猫娘拳]", + "14226": "[百变星瞳_亲亲]", + "12529": "[阿狸_啊]", + "12530": "[阿狸_爱你]", + "12531": "[阿狸_叉腰]", + "12532": "[阿狸_单纯]", + "12533": "[阿狸_点灯灯]", + "12534": "[阿狸_堆雪人]", + "12535": "[阿狸_鸽了鸽了]", + "12536": "[阿狸_好耶]", + "12537": "[阿狸_惊喜哟]", + "12538": "[阿狸_你币有了]", + "12539": "[阿狸_起飞]", + "12540": "[阿狸_圣诞袜]", + "12541": "[阿狸_偷吃]", + "12542": "[阿狸_投币]", + "12543": "[阿狸_托脸脸]", + "12544": "[阿狸_晚安]", + "12545": "[阿狸_为爱发电]", + "12546": "[阿狸_我来啦]", + "12547": "[阿狸_我溜了]", + "12548": "[阿狸_芜湖]", + "12549": "[人生百戏-白蛇传_心碎]", + "12550": "[人生百戏-白蛇传_祈祷]", + "12551": "[人生百戏-白蛇传_小青]", + "12552": "[人生百戏-白蛇传_小白]", + "12553": "[人生百戏-白蛇传_委屈]", + "12554": "[人生百戏-白蛇传_送花]", + "12555": "[人生百戏-白蛇传_达咩]", + "12556": "[人生百戏-白蛇传_晚安]", + "12557": "[人生百戏-白蛇传_我裂开了]", + "12558": "[人生百戏-白蛇传_天哪]", + "12559": "[人生百戏-白蛇传_亲亲]", + "12560": "[人生百戏-白蛇传_思念]", + "12561": "[人生百戏-白蛇传_起范]", + "12562": "[人生百戏-白蛇传_比心]", + "12563": "[人生百戏-白蛇传_告别]", + "12564": "[蓝溪镇_昂?]", + "12565": "[蓝溪镇_补妆]", + "12566": "[蓝溪镇_不耐烦]", + "12567": "[蓝溪镇_不想学习]", + "12568": "[蓝溪镇_呆]", + "12569": "[蓝溪镇_得意]", + "12570": "[蓝溪镇_给你花花]", + "12571": "[蓝溪镇_回味]", + "12572": "[蓝溪镇_悔恨]", + "12573": "[蓝溪镇_看书]", + "12574": "[蓝溪镇_哭哭]", + "12575": "[蓝溪镇_垮脸]", + "12576": "[蓝溪镇_美人落泪]", + "12577": "[蓝溪镇_嗯?]", + "12578": "[蓝溪镇_你真棒]", + "12579": "[蓝溪镇_喷火]", + "12580": "[蓝溪镇_威慑]", + "12581": "[蓝溪镇_学累了]", + "12582": "[蓝溪镇_嘤嘤抖]", + "12583": "[蓝溪镇_元气笑容]", + "12584": "[小矛动物园_no]", + "12585": "[小矛动物园_yes]", + "12586": "[小矛动物园_棒]", + "12587": "[小矛动物园_比心]", + "12588": "[小矛动物园_擦汗]", + "12589": "[小矛动物园_馋]", + "12590": "[小矛动物园_冲鸭]", + "12591": "[小矛动物园_戳]", + "12592": "[小矛动物园_打你]", + "12593": "[小矛动物园_烦]", + "12594": "[小矛动物园_汗]", + "12595": "[小矛动物园_薅呆毛]", + "12596": "[小矛动物园_就这?]", + "12597": "[小矛动物园_泪目]", + "12598": "[小矛动物园_冒头]", + "12599": "[小矛动物园_凄惨]", + "12600": "[小矛动物园_期待]", + "12601": "[小矛动物园_亲亲]", + "12602": "[小矛动物园_什么?]", + "12603": "[小矛动物园_生气]", + "12604": "[小矛动物园_送你]", + "12605": "[小矛动物园_贴贴]", + "12606": "[小矛动物园_晚安]", + "12607": "[小矛动物园_无辜]", + "12608": "[小矛动物园_下班]", + "12609": "[小矛动物园_心碎]", + "12610": "[小矛动物园_亿点点]", + "12611": "[小矛动物园_硬抗]", + "12612": "[小矛动物园_元气满满]", + "12616": "[萌妹_好难懂]", + "12617": "[萌妹_好朋友]", + "12618": "[萌妹_怎么会是]", + "12619": "[萌妹_嗯嗯好]", + "12620": "[萌妹_醉了]", + "12621": "[萌妹_好厉害]", + "12622": "[萌妹_抱抱]", + "12623": "[萌妹_好开心]", + "12624": "[萌妹_好烦恼]", + "12625": "[萌妹_赞同]", + "12626": "[萌妹_耶]", + "12627": "[萌妹_别骂我]", + "12628": "[萌妹_好喜欢]", + "12629": "[萌妹_别说话]", + "12630": "[萌妹_晴天霹雳]", + "12631": "[萌妹_怎么这样]", + "12632": "[萌妹_请给我钱]", + "12633": "[萌妹_你别惹我]", + "12634": "[萌妹_别难过]", + "12635": "[萌妹_偷笑]", + "12636": "[萌妹_啊啊啊啊]", + "12637": "[萌妹_好生气]", + "12638": "[萌妹_流汗]", + "12639": "[萌妹_请吃拳头]", + "12640": "[萌妹_好可爱]", + "12641": "[扇宝_圣诞卡祖笛]", + "12642": "[扇宝_心平气和]", + "12643": "[扇宝_对不起]", + "12644": "[扇宝_圣诞快乐]", + "12645": "[扇宝_律师函]", + "12646": "[扇宝_mua]", + "12647": "[扇宝_圣诞老人]", + "12648": "[扇宝_你才是扇宝]", + "12649": "[扇宝_看剑]", + "12650": "[扇宝_大小眼]", + "12651": "[扇宝_妈]", + "12652": "[扇宝_啧!唉~]", + "12653": "[扇宝_妙啊]", + "12654": "[扇宝_给你一拳]", + "12655": "[扇宝_疑问]", + "12656": "[扇宝_下班了]", + "12657": "[扇宝_爹]", + "12658": "[扇宝_流汗]", + "12659": "[扇宝_美女]", + "12660": "[扇宝_急急急]", + "12661": "[扇宝_姐有钱了]", + "12662": "[扇宝_我要闹了]", + "12663": "[扇宝_我是真的饿了]", + "12664": "[扇宝_怒]", + "12665": "[扇宝_天干物燥]", + "12674": "[穿越西元3000后_爱了]", + "12675": "[穿越西元3000后_我不听]", + "12676": "[穿越西元3000后_好困]", + "12677": "[穿越西元3000后_啊这]", + "12678": "[穿越西元3000后_给我滚]", + "12679": "[穿越西元3000后_震惊]", + "12680": "[穿越西元3000后_有吗]", + "12681": "[穿越西元3000后_闭嘴]", + "12682": "[穿越西元3000后_生气]", + "12683": "[穿越西元3000后_鬼脸]", + "12684": "[穿越西元3000后_憨笑]", + "12685": "[穿越西元3000后_啊]", + "12686": "[穿越西元3000后_没话说]", + "12687": "[穿越西元3000后_略略略]", + "12688": "[穿越西元3000后_哦]", + "12689": "[想见你_瞳孔地震]", + "12690": "[想见你_哭哭]", + "12691": "[想见你_惊恐]", + "12692": "[想见你_我很无语]", + "12693": "[想见你_自闭了]", + "12694": "[想见你_给我冲呀]", + "12695": "[想见你_闭眼]", + "12696": "[想见你_大口吃饭]", + "12697": "[想见你_大获诠胜]", + "12698": "[想见你_点赞]", + "12699": "[想见你_耶]", + "12700": "[想见你_想见你]", + "12701": "[想见你_比心]", + "12702": "[想见你_生日快乐]", + "12703": "[想见你_气死了]", + "12704": "[黄油小狗_禁止]", + "12705": "[黄油小狗_睡觉]", + "12706": "[黄油小狗_感动]", + "12707": "[黄油小狗_许愿]", + "12708": "[黄油小狗_盯着]", + "12709": "[黄油小狗_我吃]", + "12710": "[黄油小狗_问号]", + "12711": "[黄油小狗_爱了]", + "12712": "[黄油小狗_嗯嗯]", + "12713": "[黄油小狗_土豪]", + "12714": "[黄油小狗_整无语了]", + "12715": "[黄油小狗_额]", + "12716": "[黄油小狗_跳舞]", + "12717": "[黄油小狗_开心]", + "12718": "[黄油小狗_good]", + "12719": "[黄油小狗_狗叫?]", + "12720": "[黄油小狗_狗都不吃]", + "12721": "[黄油小狗_沮丧]", + "12722": "[黄油小狗_爱心]", + "12723": "[黄油小狗_酷]", + "12724": "[黄油小狗_狗狗祟祟]", + "12725": "[黄油小狗_狗来了]", + "12726": "[黄油小狗_干饭]", + "12727": "[黄油小狗_饿]", + "12728": "[黄油小狗_让我想想]", + "12729": "[天线宝宝_啊哦]", + "12730": "[天线宝宝_大扫除]", + "12731": "[天线宝宝_干饭]", + "12732": "[天线宝宝_害羞]", + "12733": "[天线宝宝_哼]", + "12734": "[天线宝宝_惊讶]", + "12735": "[天线宝宝_开门呀]", + "12736": "[天线宝宝_开心]", + "12737": "[天线宝宝_来玩呀]", + "12738": "[天线宝宝_买买买]", + "12739": "[天线宝宝_你好]", + "12740": "[天线宝宝_期待]", + "12741": "[天线宝宝_请]", + "12742": "[天线宝宝_让我看看]", + "12743": "[天线宝宝_圣诞快乐]", + "12744": "[天线宝宝_投币]", + "12745": "[天线宝宝_晚安]", + "12746": "[天线宝宝_我来啦]", + "12747": "[天线宝宝_演奏]", + "12748": "[天线宝宝_再见]", + "12749": "[线条小狗圣诞_安详]", + "12750": "[线条小狗圣诞_探头]", + "12751": "[线条小狗圣诞_浑身抗拒]", + "12752": "[线条小狗圣诞_给点礼物]", + "12753": "[线条小狗圣诞_好朋友]", + "12754": "[线条小狗圣诞_炸毛]", + "12755": "[线条小狗圣诞_放弃]", + "12756": "[线条小狗圣诞_贴贴]", + "12757": "[线条小狗圣诞_汪汪拳]", + "12758": "[线条小狗圣诞_带带我]", + "12759": "[线条小狗圣诞_达咩]", + "12760": "[线条小狗圣诞_加班狗]", + "12761": "[线条小狗圣诞_小狗来咯]", + "12762": "[线条小狗圣诞_加油]", + "12763": "[线条小狗圣诞_圣诞快乐]", + "12764": "[我的世界_白眼]", + "12765": "[我的世界_超生气]", + "12766": "[我的世界_点赞]", + "12767": "[我的世界_发呆]", + "12768": "[我的世界_嘿哈]", + "12769": "[我的世界_机智]", + "12770": "[我的世界_加油]", + "12771": "[我的世界_恐惧]", + "12772": "[我的世界_抠鼻]", + "12773": "[我的世界_酷]", + "12774": "[我的世界_流泪]", + "12775": "[我的世界_睡觉]", + "12776": "[我的世界_喜欢]", + "12777": "[我的世界_再见]", + "12778": "[我的世界_疑惑]", + "12779": "[我的世界_大骂]", + "12780": "[我的世界_欸]", + "12781": "[我的世界_呲牙咧嘴]", + "12782": "[我的世界_呲牙]", + "12783": "[我的世界_鼓掌]", + "12784": "[我的世界_害羞]", + "12785": "[我的世界_可怜]", + "12786": "[我的世界_困]", + "12787": "[我的世界_彩虹]", + "12788": "[我的世界_流汗]", + "12789": "[我的世界_生病]", + "12790": "[我的世界_笑]", + "12791": "[我的世界_嘘]", + "12792": "[我的世界_眨眼]", + "12793": "[我的世界_皱眉]", + "12795": "[喵来啦_害羞羞]", + "12796": "[喵来啦_达咩]", + "12797": "[喵来啦_哎]", + "12798": "[喵来啦_破防了]", + "12799": "[喵来啦_nice]", + "12800": "[喵来啦_i了i了]", + "12801": "[喵来啦_惊]", + "12802": "[喵来啦_拿捏了]", + "12803": "[喵来啦_冲鸭]", + "12804": "[喵来啦_泪目]", + "12805": "[喵来啦_就这?]", + "12806": "[喵来啦_emo]", + "12807": "[喵来啦_在吗]", + "12808": "[喵来啦_没眼看]", + "14489": "[喵来啦_我裂开了]", + "12809": "[UPOWER_80505_好耶]", + "12810": "[UPOWER_80505_呼呼呼]", + "12811": "[UPOWER_80505_哒咩]", + "12812": "[UPOWER_80505_开趴]", + "12813": "[UPOWER_80505_天空作美]", + "12814": "[UPOWER_80505_画不完]", + "12815": "[UPOWER_80505_爱了爱了]", + "12816": "[UPOWER_80505_看看你的]", + "12817": "[UPOWER_80505_小垃姬]", + "12818": "[UPOWER_80505_爬]", + "12819": "[初音未来圣诞快乐_OMG]", + "12820": "[初音未来圣诞快乐_啊??]", + "12821": "[初音未来圣诞快乐_哎]", + "12822": "[初音未来圣诞快乐_爱了]", + "12823": "[初音未来圣诞快乐_擦汗]", + "12824": "[初音未来圣诞快乐_打盹]", + "12825": "[初音未来圣诞快乐_掉坑]", + "12826": "[初音未来圣诞快乐_叮叮咚咚]", + "12827": "[初音未来圣诞快乐_发礼物]", + "12828": "[初音未来圣诞快乐_发射爱心]", + "12829": "[初音未来圣诞快乐_该工作了]", + "12830": "[初音未来圣诞快乐_好冷]", + "12831": "[初音未来圣诞快乐_喝茶]", + "12832": "[初音未来圣诞快乐_嘿嘿]", + "12833": "[初音未来圣诞快乐_火了]", + "12834": "[初音未来圣诞快乐_记下来]", + "12835": "[初音未来圣诞快乐_看看我]", + "12836": "[初音未来圣诞快乐_困惑]", + "12837": "[初音未来圣诞快乐_扭捏]", + "12838": "[初音未来圣诞快乐_求求]", + "12839": "[初音未来圣诞快乐_让我看看]", + "12840": "[初音未来圣诞快乐_圣诞快乐]", + "12841": "[初音未来圣诞快乐_圣诞驯鹿]", + "12842": "[初音未来圣诞快乐_思索]", + "12843": "[初音未来圣诞快乐_填坑]", + "12844": "[初音未来圣诞快乐_偷看]", + "12845": "[初音未来圣诞快乐_委屈]", + "12846": "[初音未来圣诞快乐_歇了]", + "12847": "[初音未来圣诞快乐_嘘]", + "12848": "[初音未来圣诞快乐_遗忘喷雾]", + "12849": "[折原露露-向日葵的约定_打call]", + "12850": "[折原露露-向日葵的约定_卑微]", + "12851": "[折原露露-向日葵的约定_好耶]", + "12852": "[折原露露-向日葵的约定_开炮]", + "12853": "[折原露露-向日葵的约定_比心]", + "12854": "[折原露露-向日葵的约定_装傻]", + "12855": "[折原露露-向日葵的约定_文化造纸]", + "12856": "[折原露露-向日葵的约定_乖巧]", + "12857": "[折原露露-向日葵的约定_市场调研]", + "12858": "[折原露露-向日葵的约定_生气]", + "12859": "[折原露露-向日葵的约定_蹲墙角]", + "12860": "[折原露露-向日葵的约定_晚安]", + "12861": "[折原露露-向日葵的约定_害羞]", + "12862": "[折原露露-向日葵的约定_可可爱爱]", + "12863": "[折原露露-向日葵的约定_晕]", + "12864": "[折原露露-向日葵的约定_好奇]", + "12865": "[折原露露-向日葵的约定_早安]", + "12866": "[折原露露-向日葵的约定_点赞]", + "12867": "[折原露露-向日葵的约定_震惊]", + "12868": "[折原露露-向日葵的约定_干杯]", + "12872": "[武炼巅峰_女王的蔑视]", + "12873": "[武炼巅峰_真棒鸭]", + "12874": "[武炼巅峰_嗯_]", + "12875": "[武炼巅峰_呆]", + "12876": "[武炼巅峰_嫌弃]", + "12877": "[武炼巅峰_欣喜]", + "12878": "[武炼巅峰_叹气]", + "12879": "[武炼巅峰_哭泣]", + "12880": "[武炼巅峰_加油鸭]", + "12881": "[武炼巅峰_生气]", + "12882": "[鬼刀海琴烟_就这?]", + "12883": "[鬼刀海琴烟_点赞]", + "12884": "[鬼刀海琴烟_big胆]", + "12885": "[鬼刀海琴烟_我酸了]", + "12886": "[鬼刀海琴烟_累了]", + "12887": "[鬼刀海琴烟_慌张]", + "12888": "[鬼刀海琴烟_不愧是你]", + "12889": "[鬼刀海琴烟_惊讶]", + "12890": "[鬼刀海琴烟_球球了]", + "12891": "[鬼刀海琴烟_哼]", + "12892": "[鬼刀海琴烟_伤脑筋]", + "12893": "[鬼刀海琴烟_暗中观察]", + "12894": "[鬼刀海琴烟_探头]", + "12895": "[鬼刀海琴烟_搬砖]", + "12896": "[鬼刀海琴烟_冲]", + "12897": "[高能手办团_加油]", + "12898": "[高能手办团_好]", + "12899": "[高能手办团_赞]", + "12900": "[高能手办团_摸头]", + "12901": "[高能手办团_无奈]", + "12902": "[高能手办团_可怜]", + "12903": "[高能手办团_头疼]", + "12904": "[高能手办团_元气满满]", + "12905": "[高能手办团_晚安]", + "12906": "[高能手办团_叹气]", + "12907": "[高能手办团_啊我死了]", + "12908": "[高能手办团_抱大腿]", + "12909": "[高能手办团_委屈]", + "12910": "[高能手办团_我可以]", + "12911": "[高能手办团_搞快点]", + "12912": "[高能手办团_最爱你了]", + "12913": "[Yommyko_好耶]", + "12914": "[Yommyko_暗中观察]", + "12915": "[Yommyko_乐]", + "12916": "[Yommyko_什么]", + "12917": "[Yommyko_阿巴阿巴]", + "12918": "[Yommyko_略略略]", + "12919": "[Yommyko_得意]", + "12920": "[Yommyko_坏笑]", + "12921": "[Yommyko_问号]", + "12922": "[Yommyko_流汗]", + "12923": "[Yommyko_EDD]", + "12924": "[Yommyko_重伤倒地]", + "12925": "[Yommyko_安详]", + "12926": "[Yommyko_大声]", + "12927": "[Yommyko_我投降]", + "12928": "[Yommyko_吃我一拳]", + "12929": "[Yommyko_小朋由]", + "12930": "[Yommyko_急了]", + "12931": "[Yommyko_苦瓜辣椒]", + "12932": "[Yommyko_害怕]", + "12933": "[Yommyko_吃面]", + "12934": "[Yommyko_那不行]", + "12935": "[Yommyko_不乐]", + "12936": "[Yommyko_两眼一黑]", + "12937": "[Yommyko_哇]", + "12938": "[一猫人_哭泣]", + "12939": "[一猫人_疑问]", + "12940": "[一猫人_吹唢呐]", + "12941": "[一猫人_收到]", + "12942": "[一猫人_注视]", + "12943": "[一猫人_上车]", + "12944": "[一猫人_火力全开]", + "12945": "[一猫人_放松]", + "12946": "[一猫人_飞向太空]", + "12947": "[一猫人_比心]", + "12948": "[一猫人_喝奶茶]", + "12949": "[一猫人_鼓掌]", + "12950": "[一猫人_撑伞]", + "12951": "[一猫人_沉思]", + "12952": "[一猫人_抱拳]", + "12953": "[一猫人_思考]", + "12954": "[一猫人_无语]", + "12955": "[一猫人_噗嗤]", + "12956": "[一猫人_拍拍]", + "12957": "[一猫人_拍桌子]", + "12958": "[一猫人_大佬]", + "12959": "[一猫人_睡了]", + "12960": "[一猫人_挠头]", + "12961": "[一猫人_你猫来了]", + "12962": "[一猫人_叹气]", + "12985": "[小铃久绘_啊对对对]", + "12986": "[小铃久绘_关我屁事]", + "12987": "[小铃久绘_哈哈哈哈]", + "12988": "[小铃久绘_未响应]", + "12989": "[小铃久绘_警告]", + "12990": "[小铃久绘_拿捏了]", + "12991": "[小铃久绘_叉出去]", + "12992": "[小铃久绘_别急]", + "12993": "[小铃久绘_mua]", + "12994": "[小铃久绘_思考]", + "12995": "[小铃久绘_摸摸]", + "12996": "[小铃久绘_坏了]", + "12997": "[小铃久绘_相信光]", + "12998": "[小铃久绘_呜呜呜]", + "12999": "[小铃久绘_坏死了]", + "13000": "[小铃久绘_牛哇]", + "13001": "[小铃久绘_爱你哟]", + "13002": "[小铃久绘_疑问]", + "13003": "[小铃久绘_给你一拳]", + "13004": "[小铃久绘_比心]", + "13005": "[小铃久绘_吃桃]", + "13006": "[小铃久绘_打call]", + "13007": "[小铃久绘_汗颜]", + "13008": "[小铃久绘_prprpr]", + "13009": "[小铃久绘_干杯]", + "13010": "[momo鸡_开始自闭-]", + "13011": "[momo鸡_闭嘴]", + "13012": "[momo鸡_好恶心]", + "13013": "[momo鸡_靓仔语塞]", + "13014": "[momo鸡_牛]", + "13015": "[momo鸡_心如止水]", + "13016": "[momo鸡_哈哈哈哈]", + "13017": "[momo鸡_快逃]", + "13018": "[momo鸡_你礼貌吗]", + "13019": "[momo鸡_四倍疑惑]", + "13020": "[momo鸡_鸡笼警告]", + "13021": "[momo鸡_哭]", + "13022": "[momo鸡_摸摸]", + "13023": "[momo鸡_偏要杆]", + "13024": "[momo鸡_真的会谢]", + "13025": "[momo鸡_当场去世]", + "13026": "[momo鸡_吃瓜]", + "13027": "[momo鸡_就这?]", + "13028": "[momo鸡_大可不必]", + "13029": "[momo鸡_绝对不鸽]", + "13030": "[momo鸡_报警]", + "13031": "[momo鸡_打call]", + "13032": "[momo鸡_不过如此]", + "13033": "[momo鸡_狗头]", + "13034": "[momo鸡_做个人吧]", + "13035": "[双生幻想_动次打次]", + "13036": "[双生幻想_不忍直视]", + "13037": "[双生幻想_最强探员]", + "13038": "[双生幻想_鄙视]", + "13039": "[双生幻想_馋]", + "13040": "[双生幻想_哼]", + "13041": "[双生幻想_吨吨吨]", + "13042": "[双生幻想_黑化]", + "13043": "[双生幻想_有道理]", + "13044": "[双生幻想_嘻嘻]", + "13045": "[双生幻想_不玩了]", + "13046": "[双生幻想_受伤]", + "13047": "[双生幻想_起了杀心]", + "13048": "[双生幻想_真香警告]", + "13049": "[双生幻想_圣诞节]", + "13050": "[面具古墓_大笑]", + "13051": "[面具古墓_嘟嘴]", + "13052": "[面具古墓_尴尬]", + "13053": "[面具古墓_害怕]", + "13054": "[面具古墓_害羞]", + "13055": "[面具古墓_哭]", + "13056": "[面具古墓_捏脸]", + "13057": "[面具古墓_生气]", + "13058": "[面具古墓_踢球]", + "13059": "[面具古墓_疑惑]", + "13060": "[决战平安京_NO]", + "13061": "[决战平安京_行吧]", + "13062": "[决战平安京_辣眼睛]", + "13063": "[决战平安京_贴贴]", + "13064": "[决战平安京_给我]", + "13065": "[决战平安京_泪目]", + "13066": "[决战平安京_无语]", + "13067": "[决战平安京_馋]", + "13068": "[决战平安京_喜欢]", + "13069": "[决战平安京_生气]", + "13070": "[决战平安京_比心]", + "13071": "[决战平安京_疑惑]", + "13072": "[决战平安京_欢呼]", + "13073": "[决战平安京_打call]", + "13074": "[决战平安京_机智]", + "13075": "[决战平安京_暗中观察]", + "13076": "[决战平安京_催促]", + "13077": "[决战平安京_谢谢]", + "13078": "[决战平安京_大哭]", + "13079": "[决战平安京_点赞]", + "13110": "[粉红兔子_安慰]", + "13111": "[粉红兔子_抓住]", + "13112": "[粉红兔子_心动]", + "13113": "[粉红兔子_鼓掌]", + "13114": "[粉红兔子_真诚]", + "13115": "[粉红兔子_注视]", + "13116": "[粉红兔子_嘿嘿嘿]", + "13117": "[粉红兔子_出神]", + "13118": "[粉红兔子_溜走]", + "13119": "[粉红兔子_逮捕]", + "13120": "[粉红兔子_你瞅啥呢]", + "13121": "[粉红兔子_点赞]", + "13122": "[粉红兔子_憧憬窗外]", + "13123": "[粉红兔子_欧耶]", + "13124": "[粉红兔子_大眼睛]", + "13125": "[粉红兔子_太棒了]", + "13126": "[粉红兔子_叫我干嘛]", + "13127": "[粉红兔子_哈哈哈哈]", + "13128": "[粉红兔子_好耶]", + "13129": "[粉红兔子_wink]", + "13130": "[粉红兔子_干杯]", + "13131": "[粉红兔子_哭了]", + "13132": "[粉红兔子_平和]", + "13133": "[粉红兔子_救命]", + "13134": "[粉红兔子_跳舞]", + "13160": "[饭粒猫·跨年特辑_思考]", + "13161": "[饭粒猫·跨年特辑_打你]", + "13162": "[饭粒猫·跨年特辑_哈哈哈]", + "13163": "[饭粒猫·跨年特辑_我太南了]", + "13164": "[饭粒猫·跨年特辑_期待]", + "13165": "[饭粒猫·跨年特辑_富有]", + "13166": "[饭粒猫·跨年特辑_贫穷]", + "13167": "[饭粒猫·跨年特辑_等红包]", + "13168": "[饭粒猫·跨年特辑_抱抱]", + "13169": "[饭粒猫·跨年特辑_新年快乐]", + "13170": "[饭粒猫·跨年特辑_抢到了]", + "13171": "[饭粒猫·跨年特辑_庆祝]", + "13172": "[饭粒猫·跨年特辑_疑问]", + "13173": "[饭粒猫·跨年特辑_闭嘴]", + "13174": "[饭粒猫·跨年特辑_就这?]", + "13175": "[饭粒猫·跨年特辑_YYDS]", + "13176": "[饭粒猫·跨年特辑_年年摸鱼]", + "13177": "[饭粒猫·跨年特辑_啥事]", + "13178": "[饭粒猫·跨年特辑_准备好了]", + "13179": "[饭粒猫·跨年特辑_打call]", + "13180": "[饭粒猫·跨年特辑_接好运]", + "13181": "[饭粒猫·跨年特辑_谢谢老板]", + "13182": "[饭粒猫·跨年特辑_真香]", + "13183": "[饭粒猫·跨年特辑_再发一个]", + "13184": "[饭粒猫·跨年特辑_大佬]", + "13185": "[adogsama秋_无语]", + "13186": "[adogsama秋_赞]", + "13187": "[adogsama秋_快逃]", + "13188": "[adogsama秋_生气]", + "13189": "[adogsama秋_笑]", + "13190": "[adogsama秋_呆]", + "13191": "[adogsama秋_累]", + "13192": "[adogsama秋_老婆]", + "13193": "[adogsama秋_喝茶]", + "13194": "[adogsama秋_嘿嘿]", + "13195": "[adogsama秋_抱]", + "13196": "[adogsama秋_达咩]", + "13197": "[adogsama秋_嗝]", + "13198": "[adogsama秋_哭]", + "13199": "[adogsama秋_玩明日方舟]", + "13200": "[adogsama秋_举高高]", + "13201": "[adogsama秋_气晕]", + "13202": "[adogsama秋_趴]", + "13203": "[adogsama秋_进化]", + "13204": "[adogsama秋_害怕]", + "13209": "[吾皇巴扎黑_学习]", + "13210": "[吾皇巴扎黑_失落]", + "13211": "[吾皇巴扎黑_傲娇]", + "13212": "[吾皇巴扎黑_谁说我]", + "13213": "[吾皇巴扎黑_打动我了]", + "13214": "[吾皇巴扎黑_疑惑]", + "13215": "[吾皇巴扎黑_八卦]", + "13216": "[吾皇巴扎黑_加油]", + "13217": "[吾皇巴扎黑_观望]", + "13218": "[吾皇巴扎黑_服了]", + "13219": "[吾皇巴扎黑_等消息]", + "13220": "[吾皇巴扎黑_不三不四]", + "13221": "[吾皇巴扎黑_老天爷]", + "13222": "[吾皇巴扎黑_在吗]", + "13223": "[吾皇巴扎黑_疲惫]", + "13224": "[吾皇巴扎黑_震惊]", + "13225": "[吾皇巴扎黑_晚安]", + "13226": "[吾皇巴扎黑_打脸]", + "13227": "[吾皇巴扎黑_别烦我]", + "13228": "[吾皇巴扎黑_出去]", + "13229": "[吾皇巴扎黑_报答]", + "13230": "[吾皇巴扎黑_哼]", + "13231": "[吾皇巴扎黑_记仇]", + "13232": "[吾皇巴扎黑_郁闷]", + "13233": "[吾皇巴扎黑_挑衅]", + "13234": "[入幕之臣_嗑死我了]", + "13235": "[入幕之臣_美人冷漠]", + "13236": "[入幕之臣_我裂开了]", + "13237": "[入幕之臣_该吃药了]", + "13238": "[入幕之臣_让我康康]", + "13239": "[入幕之臣_我就看戏]", + "13240": "[入幕之臣_拳头in了]", + "13241": "[入幕之臣_打爆狗头]", + "13242": "[入幕之臣_诶嘿嘿嘿]", + "13243": "[入幕之臣_直男疑惑]", + "13244": "[奇魔猪_你蜂啦]", + "13245": "[奇魔猪_谢谢]", + "13246": "[奇魔猪_格局小了]", + "13247": "[奇魔猪_熬夜冠军]", + "13248": "[奇魔猪_皮?]", + "13249": "[奇魔猪_看戏]", + "13250": "[奇魔猪_起床啦]", + "13251": "[奇魔猪_膨胀]", + "13252": "[奇魔猪_打不着]", + "13253": "[奇魔猪_优雅]", + "13254": "[奇魔猪_OK]", + "13255": "[奇魔猪_裂开]", + "13256": "[奇魔猪_吐魂]", + "13257": "[奇魔猪_生气]", + "13258": "[奇魔猪_报警了]", + "13259": "[奇魔猪_不开心]", + "13260": "[奇魔猪_不敢说话]", + "13261": "[奇魔猪_我没事]", + "13262": "[奇魔猪_在干嘛]", + "13263": "[奇魔猪_哭哭]", + "13264": "[奇魔猪_疑问]", + "13265": "[奇魔猪_困]", + "13266": "[奇魔猪_阴险]", + "13267": "[奇魔猪_达咩]", + "13268": "[奇魔猪_蹲墙角]", + "13269": "[两米兔和两斤兔_走了]", + "13270": "[两米兔和两斤兔恰柠檬]", + "13271": "[两米兔和两斤兔_发抖]", + "13272": "[两米兔和两斤兔_吐血]", + "13273": "[两米兔和两斤兔_点赞]", + "13274": "[两米兔和两斤兔_开心]", + "13275": "[两米兔和两斤兔_行吧]", + "13276": "[两米兔和两斤兔_爱你]", + "13277": "[两米兔和两斤兔_咕咕]", + "13278": "[两米兔和两斤兔_期待]", + "13279": "[两米兔和两斤兔_震惊]", + "13280": "[两米兔和两斤兔_送花]", + "13281": "[两米兔和两斤兔_我好菜啊]", + "13282": "[两米兔和两斤兔_疑问]", + "13283": "[两米兔和两斤兔_你说什么]", + "13284": "[两米兔和两斤兔_自闭]", + "13285": "[两米兔和两斤兔_无语]", + "13286": "[两米兔和两斤兔_可]", + "13287": "[两米兔和两斤兔_勾肩搭背]", + "13288": "[两米兔和两斤兔_会心一击]", + "13289": "[两米兔和两斤兔_吃惊]", + "13290": "[两米兔和两斤兔_流泪]", + "13291": "[两米兔和两斤兔_递茶]", + "13292": "[两米兔和两斤兔_吃瓜]", + "13293": "[两米兔和两斤兔_墨镜]", + "13294": "[暖暖狐_爱心发射]", + "13295": "[暖暖狐_抱抱]", + "13296": "[暖暖狐_不行]", + "13297": "[暖暖狐_聪明]", + "13298": "[暖暖狐_干杯]", + "13299": "[暖暖狐_给我看看]", + "13300": "[暖暖狐_记仇]", + "13301": "[暖暖狐_举手]", + "13302": "[暖暖狐_你好]", + "13303": "[暖暖狐_期待]", + "13304": "[暖暖狐_认真]", + "13305": "[暖暖狐_伤心]", + "13306": "[暖暖狐_生气]", + "13307": "[暖暖狐_投币]", + "13308": "[暖暖狐_晚安]", + "13309": "[暖暖狐_无语]", + "13310": "[暖暖狐_疑惑]", + "13311": "[暖暖狐_悠闲]", + "13312": "[暖暖狐_祝贺]", + "13313": "[暖暖狐_揍你]", + "13314": "[呆呆小可爱_抱抱]", + "13315": "[呆呆小可爱_不错]", + "13316": "[呆呆小可爱_不屑]", + "13317": "[呆呆小可爱_吃瓜]", + "13318": "[呆呆小可爱_痴笑]", + "13319": "[呆呆小可爱_得意]", + "13320": "[呆呆小可爱_感谢]", + "13321": "[呆呆小可爱_苦涩]", + "13322": "[呆呆小可爱_流汗]", + "13323": "[呆呆小可爱_冒头偷看]", + "13324": "[呆呆小可爱_跑路]", + "13325": "[呆呆小可爱_生气]", + "13326": "[呆呆小可爱_瘫倒]", + "13327": "[呆呆小可爱_哇哇大哭]", + "13328": "[呆呆小可爱_晚安]", + "13329": "[呆呆小可爱_委屈]", + "13330": "[呆呆小可爱_喜欢]", + "13331": "[呆呆小可爱_吓]", + "13332": "[呆呆小可爱_疑惑]", + "13333": "[呆呆小可爱_硬币]", + "13334": "[小熊奶黄包_休息]", + "13335": "[小熊奶黄包_抱抱]", + "13336": "[小熊奶黄包_沉思]", + "13337": "[小熊奶黄包_出来]", + "13338": "[小熊奶黄包_达咩]", + "13339": "[小熊奶黄包_呆]", + "13340": "[小熊奶黄包_多谢款待]", + "13341": "[小熊奶黄包_干饭咯]", + "13342": "[小熊奶黄包_嗨]", + "13343": "[小熊奶黄包_记仇]", + "13344": "[小熊奶黄包_惊]", + "13345": "[小熊奶黄包_累了]", + "13346": "[小熊奶黄包_妙啊]", + "13347": "[小熊奶黄包_破防了]", + "13348": "[小熊奶黄包_生气]", + "13349": "[小熊奶黄包_讨好]", + "13350": "[小熊奶黄包_偷看]", + "13351": "[小熊奶黄包_吐]", + "13352": "[小熊奶黄包_退退退]", + "13353": "[小熊奶黄包_晚安]", + "13354": "[小熊奶黄包_问号]", + "13355": "[小熊奶黄包_我爱你]", + "13356": "[小熊奶黄包_爷青回]", + "13357": "[小熊奶黄包_注入灵魂]", + "13358": "[柿柿如意_熬夜冠军]", + "13359": "[柿柿如意_蹭蹭]", + "13360": "[柿柿如意_大柿不妙]", + "13361": "[柿柿如意_放鞭炮]", + "13362": "[柿柿如意_福来啦]", + "13363": "[柿柿如意_恭喜发财]", + "13364": "[柿柿如意_好运来]", + "13365": "[柿柿如意_红包拿来]", + "13366": "[柿柿如意_饺子来咯]", + "13367": "[柿柿如意_看看兔兔]", + "13368": "[柿柿如意_可可爱爱]", + "13369": "[柿柿如意_困困]", + "13370": "[柿柿如意_你太瘦了]", + "13371": "[柿柿如意_年年有鱼]", + "13372": "[柿柿如意_乾坤一掷]", + "13373": "[柿柿如意_柿柿如意]", + "13374": "[柿柿如意_糖葫芦]", + "13375": "[柿柿如意_兔年吉祥]", + "13376": "[柿柿如意_兔兔疑惑]", + "13377": "[柿柿如意_嫌弃]", + "13378": "[柿柿如意_心碎]", + "13379": "[柿柿如意_新年快乐]", + "13380": "[柿柿如意_许愿]", + "13381": "[柿柿如意_炫饭]", + "13382": "[柿柿如意_一键三连]", + "13383": "[墩墩兔_AWSL]", + "13384": "[墩墩兔_啊对对对]", + "13385": "[墩墩兔_吃瓜]", + "13386": "[墩墩兔_吃我一拳]", + "13387": "[墩墩兔_达咩]", + "13388": "[墩墩兔_干杯]", + "13389": "[墩墩兔_惊了]", + "13390": "[墩墩兔_累了]", + "13391": "[墩墩兔_麻了]", + "13392": "[墩墩兔_摸鱼]", + "13393": "[墩墩兔_你币有了]", + "13394": "[墩墩兔_你不对劲]", + "13395": "[墩墩兔_生气]", + "13396": "[墩墩兔_栓Q]", + "13397": "[墩墩兔_一键三连]", + "13408": "[杜松子Gin_ 摇尾巴]", + "13409": "[杜松子Gin_ 千万幸福]", + "13410": "[杜松子Gin_ 忠心不二]", + "13411": "[杜松子Gin_ 正确的]", + "13412": "[杜松子Gin_妈妈]", + "13413": "[杜松子Gin_要闹了]", + "13414": "[杜松子Gin_ 投降]", + "13415": "[杜松子Gin_蛋蛋会动]", + "13416": "[杜松子Gin_吃米线]", + "13417": "[杜松子Gin_拿捏]", + "13418": "[杜松子Gin_乐]", + "13419": "[杜松子Gin_铸币]", + "13420": "[杜松子Gin_令人头大]", + "13421": "[杜松子Gin_急急急]", + "13422": "[杜松子Gin_ BYD]", + "13423": "[杜松子Gin_ 疑惑]", + "13424": "[杜松子Gin_牛]", + "13425": "[杜松子Gin_ 哭死]", + "13426": "[杜松子Gin_流汗]", + "13427": "[杜松子Gin_ 好快]", + "13428": "[杜松子Gin_看看你的]", + "13429": "[杜松子Gin_比心]", + "13430": "[杜松子Gin_ 千万开心]", + "13431": "[杜松子Gin_ 千万健康]", + "13432": "[杜松子Gin_ 许多米]", + "13433": "[美妆妖精_得意]", + "13434": "[美妆妖精_石化]", + "13435": "[美妆妖精_怒]", + "13436": "[美妆妖精_安慰]", + "13437": "[美妆妖精_我最美]", + "13438": "[美妆妖精_贫穷]", + "13439": "[美妆妖精_化妆]", + "13440": "[美妆妖精_嗯]", + "13441": "[美妆妖精_亲亲]", + "13442": "[美妆妖精_突然出现]", + "13443": "[美妆妖精_委屈]", + "13444": "[美妆妖精_期待]", + "13445": "[美妆妖精_OK]", + "13446": "[美妆妖精_盯]", + "13447": "[龙兔兔_冲鸭]", + "13448": "[龙兔兔_干杯]", + "13449": "[龙兔兔_恭喜发财]", + "13450": "[龙兔兔_好耶]", + "13451": "[龙兔兔_红包红包~]", + "13452": "[龙兔兔_惊]", + "13453": "[龙兔兔_可爱捏]", + "13454": "[龙兔兔_泪目了]", + "13455": "[龙兔兔_你币有了]", + "13456": "[龙兔兔_贴贴]", + "13457": "[龙兔兔_下次一定]", + "13458": "[龙兔兔_心想柿橙]", + "13459": "[龙兔兔_新年快乐]", + "13460": "[龙兔兔_优雅]", + "13461": "[龙兔兔_赞]", + "13462": "[qiqi_气急败坏]", + "13463": "[qiqi_老板大气]", + "13464": "[qiqi_哈哈哈]", + "13465": "[qiqi_晚安]", + "13466": "[qiqi_破防]", + "13467": "[qiqi_打卡]", + "13468": "[qiqi_星星眼]", + "13469": "[qiqi_来啦]", + "13470": "[qiqi_头大]", + "13471": "[qiqi_憨憨]", + "13472": "[qiqi_酸了]", + "13473": "[qiqi_神气]", + "13474": "[qiqi_害怕]", + "13475": "[qiqi_嘤嘤嘤]", + "13476": "[qiqi_赞]", + "13477": "[qiqi_问号]", + "13478": "[qiqi_加油]", + "13479": "[qiqi_流汗]", + "13480": "[qiqi_坏心眼子]", + "13481": "[qiqi_rua]", + "13482": "[qiqi_一键三连]", + "13483": "[qiqi_好耶]", + "13484": "[qiqi_尴尬]", + "13485": "[qiqi_吃瓜]", + "13486": "[qiqi_达咩]", + "13557": "[qiqi_对不qi]", + "13489": "[桂客盈门_无语]", + "13490": "[桂客盈门_哭哭]", + "13491": "[桂客盈门_生气]", + "13492": "[桂客盈门_贴贴]", + "13493": "[桂客盈门_挠头]", + "13494": "[桂客盈门_心平气和]", + "13495": "[桂客盈门_震惊]", + "13496": "[桂客盈门_安详]", + "13497": "[桂客盈门_一键三连]", + "13498": "[桂客盈门_收藏]", + "13499": "[桂客盈门_okk]", + "13500": "[桂客盈门_阿巴阿巴]", + "13501": "[桂客盈门_拜托了]", + "13502": "[桂客盈门_比心]", + "13503": "[桂客盈门_别管]", + "13504": "[桂客盈门_别惹我]", + "13505": "[桂客盈门_冲鸭]", + "13506": "[桂客盈门_打咩]", + "13507": "[桂客盈门_点赞]", + "13508": "[桂客盈门_喝茶]", + "13509": "[桂客盈门_慌张]", + "13510": "[桂客盈门_举手]", + "13511": "[桂客盈门_累死了]", + "13512": "[桂客盈门_麻了]", + "13513": "[桂客盈门_嗯哼]", + "13514": "[桂客盈门_请出去]", + "13515": "[桂客盈门_失落]", + "13516": "[桂客盈门_偷笑]", + "13517": "[桂客盈门_投币]", + "13518": "[桂客盈门_质疑]", + "13519": "[小丫丫_爱你]", + "13520": "[小丫丫_摸鱼]", + "13521": "[小丫丫_嫌弃]", + "13522": "[小丫丫_撒花]", + "13523": "[小丫丫_拜托]", + "13524": "[小丫丫_贪吃]", + "13525": "[小丫丫_没事吧]", + "13526": "[小丫丫_惊讶]", + "13527": "[小丫丫_失落]", + "13528": "[小丫丫_宇宙]", + "13529": "[小丫丫_什么鸟]", + "13530": "[小丫丫_吃瓜]", + "13531": "[小丫丫_探头]", + "13532": "[小丫丫_心疼]", + "13533": "[小丫丫_哭哭]", + "13534": "[小丫丫_灵光]", + "13535": "[小丫丫_确认]", + "13536": "[小丫丫_叹气]", + "13537": "[小丫丫_生气]", + "13538": "[UPOWER_1265680561_yes喵]", + "13539": "[UPOWER_1265680561_调皮]", + "13540": "[UPOWER_1265680561_谢谢喵]", + "13541": "[UPOWER_1265680561_关]", + "13542": "[UPOWER_1265680561_注]", + "13543": "[UPOWER_1265680561_永]", + "13544": "[UPOWER_1265680561_雏]", + "13545": "[UPOWER_1265680561_塔]", + "13546": "[UPOWER_1265680561_菲]", + "13547": "[nono狗_大佬]", + "13548": "[nono狗_举心心]", + "13549": "[nono狗_高冷]", + "13550": "[nono狗_OK]", + "13551": "[nono狗_生气]", + "13552": "[nono狗_晚安]", + "13553": "[nono狗_开心]", + "13554": "[nono狗_抱抱]", + "13555": "[nono狗_贴贴]", + "13556": "[nono狗_谢谢]", + "13596": "[nono狗_火大]", + "13598": "[nono狗_老婆嫁我]", + "13599": "[nono狗_吸溜吸溜]", + "13600": "[nono狗_早安]", + "13601": "[nono狗_震惊]", + "13558": "[鸽子球_思考人生]", + "13559": "[鸽子球_你好]", + "13560": "[鸽子球_好喜欢你]", + "13561": "[鸽子球_快乐]", + "13562": "[鸽子球_发大财了]", + "13563": "[鸽子球_我先睡了]", + "13564": "[鸽子球_愉悦]", + "13565": "[鸽子球_这有坏人]", + "13566": "[鸽子球_哭哭]", + "13567": "[鸽子球_交给我吧]", + "13578": "[哈利波特:魔法觉醒_驺吾生气]", + "13579": "[哈利波特:魔法觉醒_驺吾可爱]", + "13580": "[哈利波特:魔法觉醒_雷鸟贴贴]", + "13581": "[哈利波特:魔法觉醒_月痴兽惊讶]", + "13582": "[哈利波特:魔法觉醒_月痴兽疑惑]", + "13583": "[哈利波特:魔法觉醒_嗅嗅使劲]", + "13584": "[哈利波特:魔法觉醒_嗅嗅过节]", + "13585": "[哈利波特:魔法觉醒_嗅嗅搞怪]", + "13586": "[哈利波特:魔法觉醒_嗅嗅献宝]", + "13587": "[哈利波特:魔法觉醒_嗅嗅伤心]", + "13588": "[哈利波特:魔法觉醒_不了]", + "13589": "[哈利波特:魔法觉醒_鼓鼓掌]", + "13590": "[哈利波特:魔法觉醒_停下!]", + "13591": "[哈利波特:魔法觉醒_哈哈哈]", + "13592": "[哈利波特:魔法觉醒_狮院冲冲冲!]", + "13593": "[哈利波特:魔法觉醒_鹰院冲冲冲!]", + "13594": "[哈利波特:魔法觉醒_蛇院冲冲冲!]", + "13595": "[哈利波特:魔法觉醒_獾院冲冲冲!]", + "13602": "[可爱团子_面包鼠]", + "13603": "[可爱团子_拿捏]", + "13604": "[可爱团子_有柿么]", + "13605": "[可爱团子_溜了]", + "13606": "[可爱团子_爱你哦]", + "13607": "[可爱团子_好耶]", + "13608": "[可爱团子_那我呢]", + "13609": "[可爱团子_菜鼠]", + "13610": "[可爱团子_吃瓜]", + "13611": "[可爱团子_打call]", + "13612": "[可爱团子_emm]", + "13613": "[可爱团子_喜欢]", + "13614": "[可爱团子_呜呜呜]", + "13615": "[可爱团子_好运鼠]", + "13616": "[可爱团子_气气]", + "13617": "[可爱团子_自闭]", + "13618": "[可爱团子_鼠币]", + "13619": "[可爱团子_变傻魔棒]", + "13620": "[可爱团子_惊呆了]", + "13621": "[可爱团子_困困]", + "13622": "[甄嬛传系列_啊对对对]", + "13623": "[甄嬛传系列_闭嘴吧]", + "13624": "[甄嬛传系列_不会吧]", + "13625": "[甄嬛传系列_打工人]", + "13626": "[甄嬛传系列_独自美丽]", + "13627": "[甄嬛传系列_端庄]", + "13628": "[甄嬛传系列_哼老娘最美]", + "13629": "[甄嬛传系列_看戏]", + "13630": "[甄嬛传系列_妈我上岸啦]", + "13631": "[甄嬛传系列_让我看看]", + "13632": "[甄嬛传系列_我发誓]", + "13633": "[甄嬛传系列_我好爱]", + "13634": "[甄嬛传系列_乌拉拉拉式]", + "13635": "[甄嬛传系列_有被伤到]", + "13636": "[甄嬛传系列_给你要不要]", + "13637": "[花卷羊_让我听听]", + "13638": "[花卷羊_好困]", + "13639": "[花卷羊_呜呜]", + "13640": "[花卷羊_爱会消失]", + "13641": "[花卷羊_不理笨蛋]", + "13642": "[花卷羊_嗨嗨]", + "13643": "[花卷羊_爱你]", + "13644": "[花卷羊_快逃]", + "13645": "[花卷羊_好椰]", + "13646": "[花卷羊_打咩]", + "14044": "[花卷羊_干杯]", + "14045": "[花卷羊_看看]", + "14046": "[花卷羊_燃起来了]", + "14047": "[花卷羊_生气]", + "14048": "[花卷羊_问号]", + "14049": "[花卷羊_应得的]", + "13647": "[星尘的新年祝福_偷看]", + "13648": "[星尘的新年祝福_倦怠]", + "13649": "[星尘的新年祝福_我就说吧]", + "13650": "[星尘的新年祝福_安利]", + "13651": "[星尘的新年祝福_LA]", + "13652": "[星尘的新年祝福_早安]", + "13653": "[星尘的新年祝福_吉祥如意]", + "13654": "[星尘的新年祝福_不可以]", + "13655": "[星尘的新年祝福_嘿嘿]", + "13656": "[星尘的新年祝福_蟹蟹]", + "13657": "[星尘的新年祝福_噔噔]", + "13658": "[星尘的新年祝福_呜哇!]", + "13659": "[星尘的新年祝福_饮茶~]", + "13660": "[星尘的新年祝福_zZzZ]", + "13661": "[星尘的新年祝福_加油]", + "13662": "[星尘的新年祝福_思考]", + "13663": "[星尘的新年祝福_哦哦~]", + "13664": "[星尘的新年祝福_我懂了]", + "13665": "[星尘的新年祝福_得意]", + "13666": "[星尘的新年祝福_尊的吗]", + "13667": "[星尘的新年祝福_诶?]", + "13668": "[星尘的新年祝福_无语]", + "13669": "[星尘的新年祝福_叮当]", + "13670": "[星尘的新年祝福_别急]", + "13671": "[星尘的新年祝福_点赞]", + "13672": "[星律动_小心点]", + "13673": "[星律动_小纸来喽]", + "13674": "[星律动_莫急]", + "13675": "[星律动_踢]", + "13676": "[星律动_卡门]", + "13677": "[星律动_合理的]", + "13678": "[星律动_真离谱]", + "13679": "[星律动_贴贴]", + "13680": "[星律动_真棒]", + "13681": "[星律动_下头]", + "13682": "[星律动_MUA]", + "13683": "[星律动_很急]", + "13684": "[星律动_养马中勿扰]", + "13685": "[星律动_被气晕]", + "13686": "[星律动_流汗]", + "13687": "[星律动_比心]", + "13688": "[星律动_睡了]", + "13689": "[星律动_跪了]", + "13690": "[星律动_投降]", + "13691": "[星律动_哭唧唧]", + "13692": "[星律动_已标记]", + "13693": "[星律动_叶子来喽]", + "13694": "[星律动_鸭鸭来喽]", + "13695": "[星律动_好烧啊]", + "13696": "[星律动_啊哈哈哈]", + "13697": "[中国奇谭_呆]", + "13698": "[中国奇谭_第一]", + "13699": "[中国奇谭_感动]", + "13700": "[中国奇谭_害羞]", + "13701": "[中国奇谭_救命]", + "13702": "[中国奇谭_冷静]", + "13703": "[中国奇谭_瑟瑟发抖]", + "13704": "[中国奇谭_头大]", + "13705": "[中国奇谭_晚安]", + "13706": "[中国奇谭_微笑]", + "13707": "[中国奇谭_下班]", + "13708": "[中国奇谭_谢谢]", + "13709": "[中国奇谭_休息]", + "13710": "[中国奇谭_早安]", + "13711": "[中国奇谭_折磨]", + "14932": "[中国奇谭_教我做事?]", + "14933": "[中国奇谭_精神抖擞]", + "14934": "[中国奇谭_上班很快乐]", + "14935": "[中国奇谭_贴贴]", + "14936": "[中国奇谭_吃了么您]", + "14937": "[中国奇谭_靓女出现]", + "14938": "[中国奇谭_喜欢]", + "14939": "[中国奇谭_无语]", + "14940": "[中国奇谭_大为震惊]", + "14941": "[中国奇谭_恭喜发财]", + "13713": "[天气愈报_欢呼云]", + "13714": "[天气愈报_猫猫云]", + "13715": "[天气愈报_躺平云]", + "13716": "[天气愈报_汗如雨下]", + "13717": "[天气愈报_可怜云]", + "13718": "[天气愈报_狗狗云]", + "13719": "[天气愈报_突然出现]", + "13720": "[天气愈报_干杯云]", + "13721": "[天气愈报_贴贴云]", + "13722": "[天气愈报_我融化了]", + "13723": "[天气愈报_泪如雨下]", + "13724": "[天气愈报_睡觉云]", + "13725": "[天气愈报_冲呀云]", + "13726": "[天气愈报_吐彩虹]", + "13727": "[天气愈报_点赞云]", + "13728": "[天气愈报_自闭云]", + "13729": "[天气愈报_害羞云]", + "13730": "[天气愈报_电视云]", + "13731": "[天气愈报_腼腆云]", + "13732": "[天气愈报_猫云震惊]", + "13733": "[天气愈报_气球云]", + "13734": "[天气愈报_起飞云]", + "13735": "[天气愈报_大发雷霆]", + "13736": "[天气愈报_努力云]", + "13737": "[天气愈报_星星眼]", + "13738": "[天气愈报_冲浪云]", + "13739": "[天气愈报_颤抖云]", + "13740": "[天气愈报_问号云]", + "13741": "[天气愈报_秋千云]", + "13742": "[天气愈报_探头云]", + "13743": "[2023拜年纪_你很弱诶]", + "13744": "[2023拜年纪_两眼一黑]", + "13745": "[2023拜年纪_抱抱]", + "13746": "[2023拜年纪_好耶]", + "13747": "[2023拜年纪_nice]", + "13748": "[2023拜年纪_红包拿来]", + "13749": "[2023拜年纪_嘿嘿]", + "13750": "[2023拜年纪_瞄]", + "13751": "[2023拜年纪_大脑负载]", + "13752": "[2023拜年纪_2023]", + "13753": "[2023拜年纪_吐]", + "13754": "[2023拜年纪_盯_]", + "13755": "[2023拜年纪_震惊]", + "13756": "[2023拜年纪_就是你了]", + "13757": "[2023拜年纪_祈愿]", + "13758": "[2023拜年纪_吃草]", + "13759": "[2023拜年纪_贴贴]", + "13760": "[2023拜年纪_疑问]", + "13761": "[2023拜年纪_干杯!]", + "13762": "[2023拜年纪_落泪]", + "13763": "[2023拜年纪_烟花]", + "13764": "[2023拜年纪_什么?!]", + "13765": "[2023拜年纪_安详]", + "13766": "[2023拜年纪_新年快乐]", + "13767": "[2023拜年纪_呆滞]", + "13768": "[新春特辑·糖心小憩_拜托]", + "13769": "[新春特辑·糖心小憩_吃零食]", + "13770": "[新春特辑·糖心小憩_大桔大利]", + "13771": "[新春特辑·糖心小憩_大哭]", + "13772": "[新春特辑·糖心小憩_等红包]", + "13773": "[新春特辑·糖心小憩_给你小心心]", + "13774": "[新春特辑·糖心小憩_给您拜年~]", + "13775": "[新春特辑·糖心小憩_恭喜发财]", + "13776": "[新春特辑·糖心小憩_喝奶茶]", + "13777": "[新春特辑·糖心小憩_冷酷]", + "13778": "[新春特辑·糖心小憩_满足]", + "13779": "[新春特辑·糖心小憩_上线!]", + "13780": "[新春特辑·糖心小憩_生气]", + "13781": "[新春特辑·糖心小憩_收到]", + "13782": "[新春特辑·糖心小憩_万柿如意]", + "13783": "[新春特辑·糖心小憩_无语]", + "13784": "[新春特辑·糖心小憩_下线]", + "13785": "[新春特辑·糖心小憩_新年快乐]", + "13786": "[新春特辑·糖心小憩_再玩一会]", + "13787": "[新春特辑·糖心小憩_在干嘛?]", + "13788": "[新春特辑·心动约会_???]", + "13789": "[新春特辑·心动约会_NO]", + "13790": "[新春特辑·心动约会_爱你]", + "13791": "[新春特辑·心动约会_超赞]", + "13792": "[新春特辑·心动约会_腹黑]", + "13793": "[新春特辑·心动约会_给你红包]", + "13794": "[新春特辑·心动约会_恭喜发财]", + "13795": "[新春特辑·心动约会_害羞]", + "13796": "[新春特辑·心动约会_喝茶]", + "13797": "[新春特辑·心动约会_锦鲤附体]", + "13798": "[新春特辑·心动约会_哭晕]", + "13799": "[新春特辑·心动约会_摸鱼]", + "13800": "[新春特辑·心动约会_期待]", + "13801": "[新春特辑·心动约会_睡了]", + "13802": "[新春特辑·心动约会_叹气]", + "13803": "[新春特辑·心动约会_谢谢老板]", + "13804": "[新春特辑·心动约会_心想事成]", + "13805": "[新春特辑·心动约会_新年快乐]", + "13806": "[新春特辑·心动约会_震惊]", + "13807": "[新春特辑·心动约会_揍你]", + "13808": "[量子少年-慕宇_飞吻]", + "13809": "[量子少年-慕宇_永远爱你]", + "13810": "[量子少年-慕宇_就这]", + "13811": "[量子少年-慕宇_新年快乐]", + "13812": "[量子少年-慕宇_道歉]", + "13813": "[量子少年-慕宇_巡逻中]", + "13814": "[量子少年-慕宇_来了]", + "13815": "[量子少年-慕宇_DJ慕宇]", + "13816": "[量子少年-慕宇_都哥们]", + "13817": "[量子少年-慕宇_投降]", + "13818": "[量子少年-慕宇_疑惑]", + "13819": "[量子少年-慕宇_害怕]", + "13820": "[量子少年-慕宇_赞同]", + "13821": "[量子少年-慕宇_真笑了]", + "13822": "[量子少年-慕宇_玫瑰]", + "13823": "[量子少年-慕宇_刻骨铭心]", + "13824": "[量子少年-慕宇_摆烂]", + "13825": "[量子少年-慕宇_别急]", + "13826": "[量子少年-慕宇_惊讶]", + "13827": "[量子少年-慕宇_无语]", + "13828": "[量子少年-慕宇_喝水]", + "13829": "[量子少年-慕宇_使不得]", + "13830": "[量子少年-慕宇_?]", + "13831": "[量子少年-慕宇_大吉大利]", + "13832": "[量子少年-慕宇_两眼一黑]", + "13833": "[新春特辑·梦幻甜蜜限定套装_Hi]", + "13834": "[新春特辑·梦幻甜蜜限定套装_mua]", + "13835": "[新春特辑·梦幻甜蜜限定套装_啊?]", + "13836": "[新春特辑·梦幻甜蜜限定套装_爱你]", + "13837": "[新春特辑·梦幻甜蜜限定套装_哒咩]", + "13838": "[新春特辑·梦幻甜蜜限定套装_大桔大利]", + "13839": "[新春特辑·梦幻甜蜜限定套装_大哭]", + "13840": "[新春特辑·梦幻甜蜜限定套装_对不起]", + "13841": "[新春特辑·梦幻甜蜜限定套装_福兔贺岁]", + "13842": "[新春特辑·梦幻甜蜜限定套装_害羞]", + "13843": "[新春特辑·梦幻甜蜜限定套装_好吃!]", + "13844": "[新春特辑·梦幻甜蜜限定套装_哼]", + "13845": "[新春特辑·梦幻甜蜜限定套装_红包拿来]", + "13846": "[新春特辑·梦幻甜蜜限定套装_加油]", + "13847": "[新春特辑·梦幻甜蜜限定套装_就吃一勺]", + "13848": "[新春特辑·梦幻甜蜜限定套装_年年有鱼]", + "13849": "[新春特辑·梦幻甜蜜限定套装_平平安安]", + "13850": "[新春特辑·梦幻甜蜜限定套装_期待]", + "13851": "[新春特辑·梦幻甜蜜限定套装_晚安]", + "13852": "[新春特辑·梦幻甜蜜限定套装_无语]", + "13853": "[新春特辑·梦幻甜蜜限定套装_吓一跳]", + "13854": "[新春特辑·梦幻甜蜜限定套装_想要红包]", + "13855": "[新春特辑·梦幻甜蜜限定套装_新年快乐]", + "13856": "[新春特辑·梦幻甜蜜限定套装_休息中]", + "13857": "[新春特辑·梦幻甜蜜限定套装_养生]", + "13858": "[小黄人大眼萌_一起玩]", + "13859": "[小黄人大眼萌_好事花生]", + "13860": "[小黄人大眼萌_恭喜发财]", + "13861": "[小黄人大眼萌_震惊]", + "13862": "[小黄人大眼萌_干的漂亮]", + "13863": "[小黄人大眼萌_可爱]", + "13864": "[小黄人大眼萌_欢呼]", + "13865": "[小黄人大眼萌_开心]", + "13866": "[小黄人大眼萌_你好]", + "13867": "[小黄人大眼萌_想吃]", + "13868": "[UPOWER_1743593860_认可]", + "13869": "[UPOWER_1743593860_让我看看]", + "13870": "[UPOWER_1743593860_做不到]", + "13871": "[UPOWER_1743593860_有点意思]", + "13872": "[UPOWER_1743593860_麻了]", + "14557": "[UPOWER_1743593860_乐]", + "13874": "[UPOWER_1743593860_啊]", + "13875": "[UPOWER_1743593860_别急]", + "14558": "[UPOWER_1743593860_九折]", + "13877": "[UPOWER_1743593860_坏了]", + "13885": "[崩坏3·终焉归始_摸头]", + "13895": "[崩坏3·终焉归始_燃起来了]", + "13886": "[崩坏3·终焉归始_咀嚼]", + "13884": "[崩坏3·终焉归始_骑马]", + "13888": "[崩坏3·终焉归始_克利希娜]", + "13881": "[崩坏3·终焉归始_电力充足]", + "13892": "[崩坏3·终焉归始_自闭]", + "13889": "[崩坏3·终焉归始_指点]", + "13883": "[崩坏3·终焉归始_给你水晶]", + "13890": "[崩坏3·终焉归始_嘲笑]", + "13893": "[崩坏3·终焉归始_窒息]", + "13878": "[崩坏3·终焉归始_出击]", + "13880": "[崩坏3·终焉归始_?]", + "13882": "[崩坏3·终焉归始_宕机]", + "13894": "[崩坏3·终焉归始_有毛病吧]", + "13887": "[崩坏3·终焉归始_cool]", + "13897": "[崩坏3·终焉归始_臭美]", + "13891": "[崩坏3·终焉归始_渡人]", + "13879": "[崩坏3·终焉归始_咕蛹者]", + "13896": "[崩坏3·终焉归始_优雅]", + "13899": "[新春特辑·年兽套装_rua]", + "13900": "[新春特辑·年兽套装_拜大年]", + "13901": "[新春特辑·年兽套装_暴富]", + "13902": "[新春特辑·年兽套装_冲呀]", + "13903": "[新春特辑·年兽套装_打滚]", + "13904": "[新春特辑·年兽套装_大吉大利]", + "13905": "[新春特辑·年兽套装_放鞭炮]", + "13906": "[新春特辑·年兽套装_福到啦]", + "13907": "[新春特辑·年兽套装_恭喜发财]", + "13908": "[新春特辑·年兽套装_红包拿来]", + "13909": "[新春特辑·年兽套装_加油]", + "13910": "[新春特辑·年兽套装_年年有余]", + "13911": "[新春特辑·年兽套装_年兽来啦]", + "13912": "[新春特辑·年兽套装_前途似锦]", + "13913": "[新春特辑·年兽套装_探头]", + "13914": "[新春特辑·年兽套装_天天开心]", + "13915": "[新春特辑·年兽套装_贴贴]", + "13916": "[新春特辑·年兽套装_突飞猛进]", + "13917": "[新春特辑·年兽套装_谢谢老板]", + "13918": "[新春特辑·年兽套装_新春快乐]", + "13919": "[功夫熊猫·大展宏兔_吃我一招]", + "13921": "[功夫熊猫·大展宏兔_来吗?]", + "13922": "[功夫熊猫·大展宏兔_练功]", + "13923": "[功夫熊猫·大展宏兔_莫挨我]", + "13924": "[功夫熊猫·大展宏兔_受教了]", + "13925": "[功夫熊猫·大展宏兔_探头]", + "13926": "[功夫熊猫·大展宏兔_我厉害吗]", + "13927": "[功夫熊猫·大展宏兔_洗澡]", + "13928": "[功夫熊猫·大展宏兔_一起上]", + "13920": "[功夫熊猫·大展宏兔_哈!]", + "13929": "[V我_夸颂小猫]", + "13930": "[V我_晚安]", + "13931": "[V我_汗堡堡]", + "13932": "[V我_披萨心肠]", + "13933": "[V我_猪柳蛋太阳]", + "13934": "[V我_大脑宕机]", + "13935": "[V我_干杯]", + "13936": "[V我_你是我的宝]", + "13937": "[V我_V我50]", + "13938": "[V我_卷不动了]", + "14039": "[V我_强颜欢笑]", + "14040": "[V我_薯薯]", + "14041": "[V我_有点傻啦]", + "14042": "[V我_真的吗?]", + "14043": "[V我_整点薯条]", + "13939": "[神都夜行录_共工化神]", + "13940": "[神都夜行录_可爱]", + "13941": "[神都夜行录_牛]", + "13942": "[神都夜行录_喜欢]", + "13943": "[神都夜行录_星星眼]", + "13944": "[神都夜行录_退下]", + "13945": "[神都夜行录_不可思议]", + "13946": "[神都夜行录_吃瓜子]", + "13947": "[神都夜行录_思考]", + "13948": "[神都夜行录_浇花]", + "13949": "[神都夜行录_自抱自泣]", + "13950": "[神都夜行录_工作]", + "13951": "[神都夜行录_拔剑]", + "13952": "[神都夜行录_爱你]", + "13953": "[神都夜行录_脸红]", + "13954": "[章鱼小Q_大哭]", + "13955": "[章鱼小Q_和平]", + "13956": "[章鱼小Q_加油]", + "13957": "[章鱼小Q_震惊]", + "13958": "[章鱼小Q_占卜]", + "13959": "[章鱼小Q_洗澡]", + "13960": "[章鱼小Q_爱你]", + "13961": "[章鱼小Q_开心]", + "13962": "[章鱼小Q_晚安]", + "13963": "[章鱼小Q_晕]", + "13964": "[章鱼小Q_什么]", + "13965": "[章鱼小Q_我最美]", + "13966": "[章鱼小Q_吃饭喽]", + "13967": "[章鱼小Q_吹泡泡]", + "13968": "[章鱼小Q_魔法]", + "13969": "[章鱼小Q_逮到了]", + "13970": "[章鱼小Q_吐了]", + "13971": "[章鱼小Q_呆住]", + "13972": "[章鱼小Q_郁闷]", + "13973": "[章鱼小Q_贴贴]", + "13974": "[兔崽大长腿_心动]", + "13975": "[兔崽大长腿_emo]", + "13976": "[兔崽大长腿_溜了]", + "13977": "[兔崽大长腿_给我康康]", + "13978": "[兔崽大长腿_dance]", + "13979": "[兔崽大长腿_大爷来玩呀]", + "13980": "[兔崽大长腿_给你一jio]", + "13981": "[兔崽大长腿_静如处子]", + "13982": "[兔崽大长腿_深情]", + "13983": "[兔崽大长腿_完全理解了]", + "13984": "[兔崽大长腿_你是我的神]", + "13985": "[兔崽大长腿_动如脱兔]", + "13986": "[兔崽大长腿_想聊了没]", + "13987": "[兔崽大长腿_宇宙]", + "13988": "[兔崽大长腿_狂喜]", + "13989": "[希月萌奈_得加钱]", + "13990": "[希月萌奈_纠结]", + "13991": "[希月萌奈_好屑]", + "13992": "[希月萌奈_贴贴]", + "13993": "[希月萌奈_你说啥]", + "13994": "[希月萌奈_希望没事]", + "13995": "[希月萌奈_阿巴阿巴]", + "13996": "[希月萌奈_温柔]", + "13997": "[希月萌奈_过年咯]", + "13998": "[希月萌奈_傲娇]", + "13999": "[希月萌奈_呃呃]", + "14000": "[希月萌奈_吃鲸]", + "14001": "[希月萌奈_捶桌]", + "14002": "[希月萌奈_红包拿来]", + "14003": "[希月萌奈_晚安]", + "14004": "[希月萌奈_吃了没]", + "14005": "[希月萌奈_杂鱼体力]", + "14006": "[希月萌奈_打Call]", + "14007": "[希月萌奈_给你一拳]", + "14008": "[希月萌奈_恭喜发财]", + "14009": "[希月萌奈_蚕豆]", + "14010": "[希月萌奈_反弹无效]", + "14011": "[希月萌奈_不要过来]", + "14012": "[希月萌奈_问号]", + "14013": "[希月萌奈_呱!]", + "14014": "[洛少爷_失落]", + "14015": "[洛少爷_晚安哦]", + "14016": "[洛少爷_拜托]", + "14017": "[洛少爷_打call]", + "14018": "[洛少爷_挠头]", + "14019": "[洛少爷_不可以]", + "14020": "[洛少爷_哭哭]", + "14021": "[洛少爷_棒哦]", + "14022": "[洛少爷_来玩游戏]", + "14023": "[洛少爷_感觉良好]", + "14024": "[洛少爷_惊喜]", + "14025": "[洛少爷_呆滞]", + "14026": "[洛少爷_发红包]", + "14027": "[洛少爷_耍赖]", + "14028": "[洛少爷_委屈]", + "14029": "[洛少爷_酷]", + "14030": "[洛少爷_在干嘛]", + "14031": "[洛少爷_新年快乐]", + "14032": "[洛少爷_笑嘻嘻]", + "14033": "[洛少爷_吓]", + "14034": "[洛少爷_冲鸭]", + "14035": "[洛少爷_年年有鱼]", + "14036": "[洛少爷_喜欢]", + "14037": "[洛少爷_贴贴]", + "14038": "[洛少爷_握草]", + "14050": "[鹿鸣_疑问]", + "14051": "[鹿鸣_嗨]", + "14052": "[鹿鸣_害羞]", + "14053": "[鹿鸣_生气]", + "14054": "[鹿鸣_开心]", + "14055": "[鹿鸣_在吗]", + "14056": "[鹿鸣_比心]", + "14057": "[鹿鸣_哇]", + "14058": "[鹿鸣_难过]", + "14059": "[鹿鸣_不]", + "14060": "[鹿鸣_思考]", + "14061": "[鹿鸣_抓狂]", + "14062": "[鹿鸣_可怜]", + "14063": "[鹿鸣_惊讶]", + "14064": "[鹿鸣_赞]", + "14065": "[鹿鸣_吃]", + "14066": "[米奇欢笑多_冲呀]", + "14067": "[米奇欢笑多_NO SERVICE]", + "14068": "[米奇欢笑多_优雅]", + "14069": "[米奇欢笑多_一起唱]", + "14070": "[米奇欢笑多_严肃]", + "14071": "[米奇欢笑多_下次一定]", + "14072": "[米奇欢笑多_思考]", + "14073": "[米奇欢笑多_生气]", + "14074": "[米奇欢笑多_来了来了]", + "14075": "[米奇欢笑多_开心]", + "14076": "[米奇欢笑多_开车]", + "14077": "[米奇欢笑多_害羞]", + "14078": "[米奇欢笑多_就这?]", + "14079": "[米奇欢笑多_干杯]", + "14080": "[米奇欢笑多_吹走]", + "14081": "[2233兔年新春_烦躁]", + "14082": "[2233兔年新春_早上好]", + "14083": "[2233兔年新春_疯狂暗示]", + "14084": "[2233兔年新春_在吗]", + "14085": "[2233兔年新春_兔飞猛进]", + "14086": "[2233兔年新春_好事成兔]", + "14087": "[2233兔年新春_给你打气]", + "14088": "[2233兔年新春_拜拜]", + "14089": "[2233兔年新春_兔然暴富]", + "14090": "[2233兔年新春_庆祝]", + "14091": "[2233兔年新春_厄运走开]", + "14092": "[2233兔年新春_啊,这?]", + "14093": "[2233兔年新春_加班]", + "14094": "[2233兔年新春_新春快乐]", + "14095": "[2233兔年新春_兔个吉利]", + "14096": "[五橙喵 · 玉兔迎春_吃瓜中]", + "14097": "[五橙喵 · 玉兔迎春_贴贴]", + "14098": "[五橙喵 · 玉兔迎春_哒咩]", + "14099": "[五橙喵 · 玉兔迎春_开车]", + "14100": "[五橙喵 · 玉兔迎春_休息]", + "14101": "[五橙喵 · 玉兔迎春_吃瓜]", + "14102": "[五橙喵 · 玉兔迎春_在吗]", + "14103": "[五橙喵 · 玉兔迎春_emmm]", + "14104": "[五橙喵 · 玉兔迎春_好耶]", + "14105": "[五橙喵 · 玉兔迎春_哼]", + "14106": "[五橙喵 · 玉兔迎春_溜了溜了]", + "14107": "[五橙喵 · 玉兔迎春_拖走]", + "14108": "[五橙喵 · 玉兔迎春_欧皇]", + "14109": "[五橙喵 · 玉兔迎春_害怕]", + "14110": "[五橙喵 · 玉兔迎春_变黄]", + "14111": "[五橙喵 · 玉兔迎春_菜]", + "14112": "[五橙喵 · 玉兔迎春_666]", + "14113": "[五橙喵 · 玉兔迎春_爱你]", + "14114": "[五橙喵 · 玉兔迎春_牛哇]", + "14115": "[五橙喵 · 玉兔迎春_搞咩]", + "14116": "[五橙喵 · 玉兔迎春_冲鸭]", + "14117": "[五橙喵 · 玉兔迎春_no瑟瑟]", + "14118": "[五橙喵 · 玉兔迎春_哭哭]", + "14119": "[五橙喵 · 玉兔迎春_问号]", + "14120": "[五橙喵 · 玉兔迎春_下饭]", + "14121": "[五橙喵 · 玉兔迎春_无语]", + "14122": "[五橙喵 · 玉兔迎春_握草]", + "14123": "[五橙喵 · 玉兔迎春_吃瓜ing]", + "14172": "[Merry_???]", + "14173": "[Merry_爱你]", + "14174": "[Merry_巴适]", + "14175": "[Merry_打咩]", + "14176": "[Merry_打字]", + "14177": "[Merry_打call]", + "14178": "[Merry_干杯]", + "14179": "[Merry_嗨森]", + "14180": "[Merry_红包给你]", + "14181": "[Merry_惊了]", + "14182": "[Merry_啾咪]", + "14183": "[Merry_酷盖]", + "14184": "[Merry_摸头]", + "14185": "[Merry_泥嚎]", + "14186": "[Merry_牛哇]", + "14187": "[Merry_生气]", + "14188": "[Merry_探头]", + "14189": "[Merry_贴贴]", + "14190": "[Merry_兔飞猛进]", + "14191": "[Merry_兔年大吉]", + "14192": "[Merry_汪汪]", + "14193": "[Merry_嘤嘤]", + "14194": "[Merry_Inkdog]", + "14195": "[Merry_OHOHO]", + "14196": "[Merry_TSKR]", + "14197": "[兔年吉祥東雪蓮_哈哈]", + "14198": "[兔年吉祥東雪蓮_抚摸兔头]", + "14199": "[兔年吉祥東雪蓮_润]", + "14200": "[兔年吉祥東雪蓮_已黑化]", + "14201": "[兔年吉祥東雪蓮_就这?]", + "14202": "[兔年吉祥東雪蓮_猫猫可爱]", + "14203": "[兔年吉祥東雪蓮_别急]", + "14204": "[兔年吉祥東雪蓮_看看你的]", + "14205": "[兔年吉祥東雪蓮_下头]", + "14206": "[兔年吉祥東雪蓮_测测你的]", + "14207": "[兔年吉祥東雪蓮_兔年快乐]", + "14208": "[兔年吉祥東雪蓮_你没事吧]", + "14209": "[兔年吉祥東雪蓮_宝]", + "14210": "[兔年吉祥東雪蓮_w]", + "14211": "[兔年吉祥東雪蓮_管人痴]", + "14212": "[兔年吉祥東雪蓮_赢!]", + "14213": "[兔年吉祥東雪蓮_年年有余]", + "14214": "[兔年吉祥東雪蓮_切割]", + "14215": "[兔年吉祥東雪蓮_哭哭]", + "14216": "[兔年吉祥東雪蓮_哈喽哈喽]", + "14217": "[兔年吉祥東雪蓮_打call]", + "14218": "[兔年吉祥東雪蓮_gachi]", + "14219": "[兔年吉祥東雪蓮_结婚]", + "14220": "[兔年吉祥東雪蓮_待机]", + "14221": "[兔年吉祥東雪蓮_妈!]", + "14227": "[新春抽赏-慕宇_给您拜年了]", + "14228": "[新春抽赏-慕宇_恭喜发财]", + "14229": "[新春抽赏-慕宇_夸奖]", + "14230": "[新春抽赏-慕宇_难过]", + "14231": "[新春抽赏-慕宇_谢谢老板]", + "14232": "[新春抽赏-希月_不愧是我]", + "14233": "[新春抽赏-希月_吃药啦]", + "14234": "[新春抽赏-希月_身体健康]", + "14235": "[新春抽赏-希月_团圆]", + "14236": "[新春抽赏-希月_新年好]", + "14237": "[新春抽赏-奈奈莉娅_拜年啦]", + "14238": "[新春抽赏-奈奈莉娅_给点]", + "14239": "[新春抽赏-奈奈莉娅_红包拿来]", + "14240": "[新春抽赏-奈奈莉娅_亲亲]", + "14241": "[新春抽赏-奈奈莉娅_新春快乐]", + "14242": "[新春抽赏-Bao_大吃大喝]", + "14243": "[新春抽赏-Bao_好耶]", + "14244": "[新春抽赏-Bao_潜水]", + "14245": "[新春抽赏-Bao_兔年吉祥]", + "14246": "[新春抽赏-Bao_新年祈愿]", + "14247": "[爱宠大机密·喜迎兔兔_美滋滋]", + "14248": "[爱宠大机密·喜迎兔兔_哼]", + "14249": "[爱宠大机密·喜迎兔兔_蛤?]", + "14250": "[爱宠大机密·喜迎兔兔_期待]", + "14251": "[爱宠大机密·喜迎兔兔_恭喜发财]", + "14252": "[爱宠大机密·喜迎兔兔_出去玩]", + "14253": "[爱宠大机密·喜迎兔兔_无聊]", + "14254": "[爱宠大机密·喜迎兔兔_怀疑]", + "14255": "[爱宠大机密·喜迎兔兔_你说呢]", + "14256": "[爱宠大机密·喜迎兔兔_可爱捏]", + "14257": "[爱宠大机密·喜迎兔兔_歇一歇]", + "14258": "[爱宠大机密·喜迎兔兔_高冷]", + "14259": "[爱宠大机密·喜迎兔兔_偷瞄]", + "14260": "[爱宠大机密·喜迎兔兔_看招]", + "14261": "[爱宠大机密·喜迎兔兔_牛]", + "14262": "[疯狂兔子宇宙博物馆_快跑]", + "14263": "[疯狂兔子宇宙博物馆_合照]", + "14264": "[疯狂兔子宇宙博物馆_开心]", + "14265": "[疯狂兔子宇宙博物馆_惊喜]", + "14266": "[疯狂兔子宇宙博物馆_哼]", + "14267": "[疯狂兔子宇宙博物馆_飞升]", + "14268": "[疯狂兔子宇宙博物馆_爱你]", + "14269": "[疯狂兔子宇宙博物馆_飞喽]", + "14270": "[疯狂兔子宇宙博物馆_倒立]", + "14271": "[疯狂兔子宇宙博物馆_嘲笑]", + "14272": "[疯狂兔子宇宙博物馆_拥抱]", + "14273": "[疯狂兔子宇宙博物馆_喜欢]", + "14274": "[疯狂兔子宇宙博物馆_Hi]", + "14275": "[疯狂兔子宇宙博物馆_震惊]", + "14276": "[疯狂兔子宇宙博物馆_再见]", + "14277": "[疯狂兔子宇宙博物馆_贴贴]", + "14278": "[疯狂兔子宇宙博物馆_让我看看]", + "14279": "[疯狂兔子宇宙博物馆_讨厌]", + "14280": "[疯狂兔子宇宙博物馆_生气]", + "14281": "[疯狂兔子宇宙博物馆_谁呀]", + "14282": "[Bao新春装扮_流汗]", + "14283": "[Bao新春装扮_吐泡泡]", + "14284": "[Bao新春装扮_兴奋]", + "14285": "[Bao新春装扮_晕]", + "14286": "[Bao新春装扮_疑惑]", + "14287": "[Bao新春装扮_生气]", + "14288": "[Bao新春装扮_拿福]", + "14289": "[Bao新春装扮_达咩]", + "14290": "[Bao新春装扮_啾啾Bao]", + "14291": "[Bao新春装扮_半死]", + "14292": "[Bao新春装扮_打Call]", + "14293": "[Bao新春装扮_给红包]", + "14294": "[Bao新春装扮_狗狗]", + "14295": "[Bao新春装扮_贴贴]", + "14296": "[Bao新春装扮_拜年]", + "14297": "[Bao新春装扮_比心]", + "14298": "[Bao新春装扮_晚安]", + "14299": "[Bao新春装扮_AWSL]", + "14300": "[Bao新春装扮_喷水]", + "14301": "[Bao新春装扮_要红包]", + "14302": "[熊出没_团聚]", + "14303": "[熊出没_发呆]", + "14304": "[熊出没_别急]", + "14305": "[熊出没_吃瓜]", + "14306": "[熊出没_不懂]", + "14307": "[熊出没_等一等]", + "14308": "[熊出没_收]", + "14309": "[熊出没_帅]", + "14310": "[熊出没_看电影]", + "14311": "[熊出没_晚安]", + "14312": "[熊出没_给你拜回去]", + "14313": "[熊出没_出来玩]", + "14314": "[熊出没_上工]", + "14315": "[熊出没_红包]", + "14316": "[熊出没_在忙]", + "14317": "[熊出没_委屈]", + "14343": "[熊出没_强]", + "14344": "[熊出没_给你拜年啦]", + "14345": "[熊出没_哼]", + "14346": "[熊出没_嫌弃]", + "14339": "[可爱联盟新年_好]", + "14333": "[可爱联盟新年_新年快乐]", + "14323": "[可爱联盟新年_祈福]", + "14324": "[可爱联盟新年_恭喜发财]", + "14328": "[可爱联盟新年_害羞]", + "14318": "[可爱联盟新年_开心]", + "14321": "[可爱联盟新年_击掌]", + "14340": "[可爱联盟新年_嘿嘿]", + "14338": "[可爱联盟新年_福]", + "14325": "[可爱联盟新年_贫穷]", + "14341": "[可爱联盟新年_发红包]", + "14332": "[可爱联盟新年_蹲]", + "14342": "[可爱联盟新年_等待]", + "14337": "[可爱联盟新年_流汗]", + "14330": "[可爱联盟新年_打你]", + "14327": "[可爱联盟新年_吃饭]", + "14335": "[可爱联盟新年_不开心]", + "14320": "[可爱联盟新年_晕]", + "14336": "[可爱联盟新年_不]", + "14319": "[可爱联盟新年_谢谢老板]", + "14326": "[可爱联盟新年_仙女棒]", + "14329": "[可爱联盟新年_嘻]", + "14322": "[可爱联盟新年_睡觉]", + "14331": "[可爱联盟新年_上厕所]", + "14334": "[可爱联盟新年_呆]", + "14347": "[奈奈莉娅_晚安]", + "14348": "[奈奈莉娅_嗨]", + "14349": "[奈奈莉娅_比心]", + "14350": "[奈奈莉娅_是娅是娅]", + "14351": "[奈奈莉娅_可爱捏]", + "14352": "[奈奈莉娅_疑问]", + "14353": "[奈奈莉娅_吃桃]", + "14354": "[奈奈莉娅_恭喜]", + "14355": "[奈奈莉娅_好耶]", + "14356": "[奈奈莉娅_摸摸]", + "14357": "[奈奈莉娅_润了]", + "14358": "[奈奈莉娅_啊对对]", + "14359": "[奈奈莉娅_让我康康]", + "14360": "[奈奈莉娅_小笨蛋]", + "14361": "[奈奈莉娅_屑]", + "14362": "[奈奈莉娅_生气]", + "14363": "[奈奈莉娅_发红包]", + "14364": "[奈奈莉娅_沾沾喜气]", + "14365": "[奈奈莉娅_不值]", + "14366": "[奈奈莉娅_打call]", + "14367": "[奈奈莉娅_指指点点]", + "14368": "[奈奈莉娅_流汗]", + "14369": "[奈奈莉娅_哭哭]", + "14370": "[奈奈莉娅_摸了]", + "14371": "[奈奈莉娅_细说]", + "14419": "[唐人街探案3_可爱]", + "14420": "[唐人街探案3_鼓掌]", + "14421": "[唐人街探案3_OK]", + "14422": "[唐人街探案3_探头]", + "14423": "[唐人街探案3_哈哈哈]", + "14424": "[唐人街探案3_麻了]", + "14425": "[唐人街探案3_大佬出场]", + "14426": "[唐人街探案3_奶思]", + "14427": "[唐人街探案3_装酷]", + "14428": "[唐人街探案3_震惊]", + "14429": "[唐人街探案3_冷漠]", + "14430": "[唐人街探案3_耶]", + "14431": "[唐人街探案3_冲鸭]", + "14432": "[唐人街探案3_恭喜发财]", + "14433": "[唐人街探案3_红包拿来]", + "14438": "[要来点米米子么_比心]", + "14439": "[要来点米米子么_吃瓜]", + "14440": "[要来点米米子么_催更]", + "14441": "[要来点米米子么_哒咩]", + "14442": "[要来点米米子么_打call]", + "14443": "[要来点米米子么_弟!]", + "14444": "[要来点米米子么_对不起]", + "14445": "[要来点米米子么_饿饿]", + "14446": "[要来点米米子么_干杯]", + "14447": "[要来点米米子么_擦擦汗]", + "14448": "[要来点米米子么_不可以涩涩 ]", + "14449": "[要来点米米子么_给你来一拳]", + "14450": "[要来点米米子么_害怕]", + "14451": "[要来点米米子么_好寄]", + "14452": "[要来点米米子么_好耶]", + "14453": "[要来点米米子么_急]", + "14455": "[要来点米米子么_惊讶]", + "14456": "[要来点米米子么_钻]", + "14457": "[要来点米米子么_石]", + "14458": "[要来点米米子么_剑!!]", + "14459": "[要来点米米子么_就这?]", + "14460": "[要来点米米子么_开派对咯]", + "14461": "[要来点米米子么_开心]", + "14462": "[要来点米米子么_可怜]", + "14463": "[要来点米米子么_哭哭]", + "14464": "[要来点米米子么_你先别急]", + "14465": "[要来点米米子么_弱爆]", + "14466": "[要来点米米子么_生气]", + "14467": "[要来点米米子么_生日快乐]", + "14468": "[要来点米米子么_实力]", + "14469": "[要来点米米子么_投币]", + "14470": "[要来点米米子么_问号]", + "14471": "[要来点米米子么_下次一定]", + "14472": "[要来点米米子么_一键三连]", + "14473": "[要来点米米子么_永远爱你]", + "14474": "[要来点米米子么_贴贴]", + "14475": "[要来点米米子么_累]", + "14483": "[UPOWER_23630128_告辞]", + "14484": "[UPOWER_23630128_口亨]", + "14485": "[UPOWER_23630128_警告]", + "14486": "[UPOWER_23630128_略略]", + "14487": "[UPOWER_23630128_嚣张]", + "14488": "[UPOWER_23630128_疑惑]", + "14490": "[肥肥鲨打工人_阿巴]", + "14491": "[肥肥鲨打工人_真的狗]", + "14492": "[肥肥鲨打工人_我爱工作]", + "14493": "[肥肥鲨打工人_吃饭]", + "14494": "[肥肥鲨打工人_不想打工]", + "14495": "[肥肥鲨打工人_为什么上班]", + "14496": "[肥肥鲨打工人_请说]", + "14497": "[肥肥鲨打工人_时间暂停]", + "14498": "[肥肥鲨打工人_别走]", + "14499": "[肥肥鲨打工人_不上班]", + "14500": "[肥肥鲨打工人_加班]", + "14501": "[肥肥鲨打工人_yes]", + "14502": "[肥肥鲨打工人_走了]", + "14503": "[肥肥鲨打工人_不想上班]", + "14504": "[肥肥鲨打工人_不想努力]", + "14505": "[肥肥鲨打工人_延长假期]", + "14506": "[肥肥鲨打工人_打工人上班了]", + "14507": "[肥肥鲨打工人_回家种地]", + "14508": "[肥肥鲨打工人_请假]", + "14509": "[肥肥鲨打工人_烦]", + "14510": "[肥肥鲨打工人_谢谢]", + "14511": "[肥肥鲨打工人_要工作吗]", + "14512": "[肥肥鲨打工人_郁闷]", + "14513": "[肥肥鲨打工人_迷之自信]", + "14514": "[肥肥鲨打工人_搬砖]", + "14530": "[黑泽诺亚NOIR_摸鱼]", + "14531": "[黑泽诺亚NOIR_看看你的]", + "14532": "[黑泽诺亚NOIR_委屈巴巴]", + "14533": "[黑泽诺亚NOIR_你没事吧?]", + "14534": "[黑泽诺亚NOIR_kksk]", + "14535": "[黑泽诺亚NOIR_绷不住了]", + "14536": "[黑泽诺亚NOIR_我哭死]", + "14537": "[黑泽诺亚NOIR_问号]", + "14538": "[黑泽诺亚NOIR_祈祷nia]", + "14539": "[黑泽诺亚NOIR_吃瓜]", + "14540": "[黑泽诺亚NOIR_真下头]", + "14541": "[黑泽诺亚NOIR_哇]", + "14542": "[黑泽诺亚NOIR_笑死]", + "14543": "[黑泽诺亚NOIR_打call]", + "14544": "[黑泽诺亚NOIR_啊对对对]", + "14545": "[黑泽诺亚NOIR_啾咪]", + "14546": "[黑泽诺亚NOIR_害羞]", + "14547": "[黑泽诺亚NOIR_赫赫]", + "14548": "[黑泽诺亚NOIR_awsl]", + "14549": "[黑泽诺亚NOIR_好好休息]", + "14550": "[黑泽诺亚NOIR_无语]", + "14551": "[黑泽诺亚NOIR_老婆]", + "14552": "[黑泽诺亚NOIR_令人头大]", + "14553": "[黑泽诺亚NOIR_牛哇]", + "14554": "[黑泽诺亚NOIR_收收味]", + "14560": "[蜡笔小新_起飞]", + "14561": "[蜡笔小新_爱你]", + "14562": "[蜡笔小新_我摔倒了]", + "14563": "[蜡笔小新_弹吉他]", + "14564": "[蜡笔小新_吹喇叭]", + "14565": "[蜡笔小新_嘀嘀]", + "14566": "[蜡笔小新_动感超人]", + "14567": "[蜡笔小新_休息]", + "14568": "[蜡笔小新_给我看看]", + "14569": "[蜡笔小新_好耶]", + "14570": "[蜡笔小新_来了来了]", + "14571": "[蜡笔小新_啦啦啦]", + "14572": "[蜡笔小新_开心]", + "14573": "[蜡笔小新_加油]", + "14574": "[蜡笔小新_急急急]", + "14575": "[蜡笔小新_溜了]", + "14576": "[蜡笔小新_芜湖]", + "14577": "[蜡笔小新_耍帅]", + "14578": "[蜡笔小新_哇哦]", + "14579": "[蜡笔小新_跳舞]", + "14580": "[蜡笔小新_阿呆]", + "14581": "[蜡笔小新_妮妮]", + "14582": "[蜡笔小新_小爱]", + "14583": "[蜡笔小新_风间]", + "14584": "[蜡笔小新_正南]", + "14585": "[Tingus_Victoria]", + "14586": "[Tingus_Frank]", + "14587": "[Tingus_Armstrong]", + "14588": "[Tingus_MrsA]", + "14589": "[Tingus_Edward]", + "14590": "[Tingus_Clement]", + "14591": "[Tingus_Jasmine]", + "14592": "[Tingus_Samantha]", + "14593": "[Tingus_Cynthia]", + "14594": "[Tingus_Alan]", + "14595": "[Tingus_Paul]", + "14596": "[Tingus_Barbara]", + "14597": "[Tingus_Julia]", + "14598": "[Tingus_Timmy]", + "14599": "[Tingus_Steven]", + "14600": "[Tingus_Walter]", + "14601": "[Tingus_Mimi]", + "14602": "[Tingus_Jenny]", + "14603": "[Tingus_Patricia]", + "14604": "[Tingus_MrA]", + "14605": "[Tingus_Sandra]", + "14606": "[Tingus_Alexander]", + "14607": "[Tingus_Charles]", + "14608": "[Tingus_Howard]", + "14609": "[pop子和pipi美_暗中观察]", + "14610": "[pop子和pipi美_比心]", + "14611": "[pop子和pipi美_冲呀]", + "14612": "[pop子和pipi美_呆住]", + "14613": "[pop子和pipi美_等等我]", + "14614": "[pop子和pipi美_点赞]", + "14615": "[pop子和pipi美_害怕]", + "14616": "[pop子和pipi美_害羞]", + "14617": "[pop子和pipi美_救命]", + "14618": "[pop子和pipi美_哭哭]", + "14619": "[pop子和pipi美_摸摸头]", + "14620": "[pop子和pipi美_闪亮登场]", + "14621": "[pop子和pipi美_上车]", + "14622": "[pop子和pipi美_算了算了]", + "14623": "[pop子和pipi美_我发誓]", + "14624": "[pop子和pipi美_向往]", + "14625": "[pop子和pipi美_辛酸]", + "14626": "[pop子和pipi美_赢了赢了]", + "14627": "[pop子和pipi美_震惊]", + "14628": "[pop子和pipi美_自拍]", + "14629": "[咖喱饭_?]", + "14630": "[咖喱饭_冲鸭]", + "14631": "[咖喱饭_达咩]", + "14632": "[咖喱饭_打棒球]", + "14633": "[咖喱饭_打碟]", + "14634": "[咖喱饭_点赞]", + "14635": "[咖喱饭_顶住]", + "14636": "[咖喱饭_躲起来]", + "14637": "[咖喱饭_放过寄几]", + "14638": "[咖喱饭_害羞了]", + "14639": "[咖喱饭_喝茶]", + "14640": "[咖喱饭_火箭炮]", + "14641": "[咖喱饭_加油]", + "14642": "[咖喱饭_哭哭]", + "14643": "[咖喱饭_狂吃咖喱饭]", + "14644": "[咖喱饭_来就补]", + "14645": "[咖喱饭_你干嘛!]", + "14646": "[咖喱饭_捏脸]", + "14647": "[咖喱饭_跑路]", + "14648": "[咖喱饭_洗澡]", + "14649": "[咖喱饭_上头了]", + "14650": "[咖喱饭_时光机]", + "14651": "[咖喱饭_帅气]", + "14652": "[咖喱饭_睡觉]", + "14653": "[咖喱饭_玩大蒜]", + "14654": "[咖喱饭_我汗]", + "14655": "[咖喱饭_喜欢]", + "14656": "[咖喱饭_震惊]", + "14657": "[咖喱饭_自闭了]", + "14658": "[咖喱饭_走个形式]", + "14659": "[C酱兔兔纪念装扮_鹅鹅鹅]", + "14660": "[C酱兔兔纪念装扮_搞笑吧]", + "14661": "[C酱兔兔纪念装扮_搞笑吧你]", + "14662": "[C酱兔兔纪念装扮_给你一拳]", + "14663": "[C酱兔兔纪念装扮_害怕]", + "14664": "[C酱兔兔纪念装扮_很急]", + "14665": "[C酱兔兔纪念装扮_很气]", + "14666": "[C酱兔兔纪念装扮_激动]", + "14667": "[C酱兔兔纪念装扮_寄]", + "14668": "[C酱兔兔纪念装扮_看戏]", + "14669": "[C酱兔兔纪念装扮_快看]", + "14670": "[C酱兔兔纪念装扮_乐]", + "14671": "[C酱兔兔纪念装扮_救救]", + "14672": "[C酱兔兔纪念装扮_流汗]", + "14673": "[C酱兔兔纪念装扮_纳尼]", + "14674": "[C酱兔兔纪念装扮_拳头嗯了]", + "14675": "[C酱兔兔纪念装扮_生日快乐]", + "14676": "[C酱兔兔纪念装扮_哇啊]", + "14677": "[C酱兔兔纪念装扮_biu]", + "14678": "[C酱兔兔纪念装扮_哇嗷]", + "14679": "[C酱兔兔纪念装扮_委屈]", + "14680": "[C酱兔兔纪念装扮_呜]", + "14681": "[C酱兔兔纪念装扮_折磨]", + "14682": "[C酱兔兔纪念装扮_啊对对]", + "14683": "[C酱兔兔纪念装扮_Rua]", + "14684": "[C酱兔兔纪念装扮_啊啊啊]", + "14685": "[C酱兔兔纪念装扮_啊啊啊啊]", + "14686": "[C酱兔兔纪念装扮_不好意思]", + "14687": "[C酱兔兔纪念装扮_啊哈]", + "14688": "[C酱兔兔纪念装扮_倒地]", + "14689": "[C酱兔兔纪念装扮_爱肉包]", + "14691": "[童话系列·豌豆公主_睡不着]", + "14692": "[童话系列·豌豆公主_暗中观察]", + "14693": "[童话系列·豌豆公主_困了]", + "14694": "[童话系列·豌豆公主_坏笑]", + "14695": "[童话系列·豌豆公主_晚安]", + "14696": "[童话系列·豌豆公主_尊贵]", + "14697": "[童话系列·豌豆公主_好冷]", + "14698": "[童话系列·豌豆公主_不愧是我]", + "14699": "[童话系列·豌豆公主_疲惫]", + "14700": "[童话系列·豌豆公主_拿来]", + "14701": "[童话系列·豌豆公主_生气]", + "14702": "[童话系列·豌豆公主_丢掉]", + "14703": "[童话系列·豌豆公主_谢谢]", + "14704": "[童话系列·豌豆公主_收到]", + "14705": "[童话系列·豌豆公主_超喜欢]", + "14706": "[童话系列·豌豆公主_早安]", + "14707": "[童话系列·豌豆公主_痛,太痛了]", + "14708": "[童话系列·豌豆公主_转圈]", + "14709": "[童话系列·豌豆公主_发呆]", + "14710": "[童话系列·豌豆公主_傲娇]", + "14711": "[童话系列·豌豆公主_sorry]", + "14712": "[童话系列·豌豆公主_嘻嘻]", + "14713": "[童话系列·豌豆公主_洗洗睡吧]", + "14714": "[童话系列·豌豆公主_期待]", + "14715": "[童话系列·豌豆公主_不听不听]", + "14719": "[UPOWER_75547577_必歪]", + "14720": "[UPOWER_75547577_必中]", + "14727": "[装扮小姐姐樱花_高产]", + "14728": "[装扮小姐姐樱花_集合啦]", + "14729": "[装扮小姐姐樱花_勾引]", + "14730": "[装扮小姐姐樱花_赏花]", + "14731": "[装扮小姐姐樱花_蹦蹦跳跳]", + "14732": "[装扮小姐姐樱花_可爱捏]", + "14733": "[装扮小姐姐樱花_赞]", + "14734": "[装扮小姐姐樱花_头脑风暴]", + "14735": "[装扮小姐姐樱花_买它!]", + "14736": "[装扮小姐姐樱花_睡醒啦]", + "14737": "[装扮小姐姐樱花_好创意!]", + "14738": "[装扮小姐姐樱花_画画]", + "14739": "[装扮小姐姐樱花_变美]", + "14740": "[装扮小姐姐樱花_显摆]", + "14741": "[装扮小姐姐樱花_放着我来]", + "14742": "[艺术猫咪_嘿嘿]", + "14743": "[艺术猫咪_什么]", + "14744": "[艺术猫咪_又被伤到]", + "14745": "[艺术猫咪_兴奋]", + "14746": "[艺术猫咪_委屈]", + "14747": "[艺术猫咪_摊手]", + "14748": "[艺术猫咪_生气]", + "14749": "[艺术猫咪_思考]", + "14750": "[艺术猫咪_一起摸鱼]", + "14751": "[艺术猫咪_流泪]", + "14752": "[艺术猫咪_盯]", + "14753": "[艺术猫咪_太空]", + "14754": "[艺术猫咪_震惊]", + "14755": "[艺术猫咪_爱了]", + "14756": "[艺术猫咪_闭嘴]", + "14758": "[迪士尼经典·全站热恋_丘比特]", + "14765": "[迪士尼经典·全站热恋_扭捏]", + "14759": "[迪士尼经典·全站热恋_亲亲]", + "14760": "[迪士尼经典·全站热恋_凹造型]", + "14761": "[迪士尼经典·全站热恋_喜欢]", + "14762": "[迪士尼经典·全站热恋_嗨]", + "14757": "[迪士尼经典·全站热恋_What?]", + "14763": "[迪士尼经典·全站热恋_害羞]", + "14764": "[迪士尼经典·全站热恋_想你]", + "14766": "[迪士尼经典·全站热恋_抱抱]", + "14767": "[迪士尼经典·全站热恋_爱你]", + "14768": "[迪士尼经典·全站热恋_礼物]", + "14769": "[迪士尼经典·全站热恋_秀恩爱]", + "14770": "[迪士尼经典·全站热恋_约会去]", + "14771": "[迪士尼经典·全站热恋_走咯]", + "14772": "[糖粽兔兔表情包_fighting]", + "14773": "[糖粽兔兔表情包_偷偷哭泣]", + "14774": "[糖粽兔兔表情包_震惊兔兔]", + "14775": "[糖粽兔兔表情包_偷看一眼]", + "14776": "[糖粽兔兔表情包_兔兔凝视]", + "14777": "[糖粽兔兔表情包_兔兔嘎了]", + "14778": "[糖粽兔兔表情包_兔兔无语]", + "14779": "[糖粽兔兔表情包_快逃]", + "14780": "[糖粽兔兔表情包_快乐吃瓜]", + "14781": "[糖粽兔兔表情包_好耶好耶]", + "14782": "[糖粽兔兔表情包_气炸毛了]", + "14783": "[糖粽兔兔表情包_爱了爱了]", + "14784": "[糖粽兔兔表情包_耍赖]", + "14785": "[糖粽兔兔表情包_那我呢]", + "14786": "[糖粽兔兔表情包_试探]", + "14787": "[糖粽兔兔表情包_兔兔疑惑]", + "14788": "[糖粽兔兔表情包_逃避现实]", + "14789": "[糖粽兔兔表情包_送你花花]", + "14790": "[糖粽兔兔表情包_抱抱]", + "14791": "[糖粽兔兔表情包_裹紧被被]", + "14792": "[彩虹宇航员_献花]", + "14793": "[彩虹宇航员_疑惑]", + "14794": "[彩虹宇航员_看戏]", + "14795": "[彩虹宇航员_额]", + "14796": "[彩虹宇航员_我来了]", + "14797": "[彩虹宇航员_喜欢]", + "14798": "[彩虹宇航员_我是垃圾]", + "14799": "[彩虹宇航员_晚安]", + "14800": "[彩虹宇航员_哭]", + "14801": "[彩虹宇航员_加一]", + "14802": "[彩虹宇航员_再见]", + "14803": "[彩虹宇航员_震惊]", + "14804": "[彩虹宇航员_爱心]", + "14805": "[彩虹宇航员_抱抱]", + "14806": "[彩虹宇航员_伤了]", + "14807": "[西湖印_挑灯照夜]", + "14808": "[西湖印_万事定胜]", + "14809": "[西湖印_小荷]", + "14810": "[西湖印_药草飘香]", + "14811": "[西湖印_雷峰夕照]", + "14812": "[西湖印_西泠余韵]", + "14813": "[西湖印_山茶映雪]", + "14814": "[西湖印_月]", + "14815": "[西湖印_满陇桂雨]", + "14816": "[西湖印_柳浪闻莺]", + "14817": "[西湖印_龙井飘香]", + "14818": "[西湖印_清嘉]", + "14819": "[西湖印_旧戏新唱]", + "14820": "[西湖印_三潭映月]", + "14821": "[西湖印_一键三连]", + "14822": "[mikko_爱你]", + "14823": "[mikko_真好]", + "14824": "[mikko_再见哦]", + "14825": "[mikko_不好意思]", + "14826": "[mikko_支持]", + "14827": "[mikko_抱抱]", + "14828": "[mikko_好累啊]", + "14829": "[mikko_哦哦]", + "14830": "[mikko_好开心]", + "14831": "[mikko_无语]", + "14832": "[mikko_是吗]", + "14833": "[mikko_早安]", + "14834": "[mikko_加油]", + "14835": "[mikko_我回来了]", + "14836": "[mikko_流泪]", + "14837": "[mikko_明白了]", + "14838": "[mikko_幸福]", + "14839": "[mikko_棒棒]", + "14840": "[mikko_ok]", + "14841": "[mikko_晚安]", + "14842": "[郁金香星球_灵魂出窍]", + "14843": "[郁金香星球_莓问题]", + "14844": "[郁金香星球_震惊]", + "14845": "[郁金香星球_6]", + "14846": "[郁金香星球_害羞]", + "14847": "[郁金香星球_胡说八道]", + "14848": "[郁金香星球_wink]", + "14849": "[郁金香星球_让我看看]", + "14850": "[郁金香星球_送花]", + "14851": "[郁金香星球_贴贴]", + "14852": "[郁金香星球_慌张]", + "14853": "[郁金香星球_记仇]", + "14854": "[郁金香星球_哭哭]", + "14855": "[郁金香星球_开心]", + "14856": "[郁金香星球_点赞]", + "14876": "[美月もも_爱你]", + "14863": "[美月もも_抱抱]", + "14880": "[美月もも_叉出去]", + "14869": "[美月もも_吃了吗]", + "14877": "[美月もも_吃桃]", + "14881": "[美月もも_出勤]", + "14873": "[美月もも_耷拉耳]", + "14871": "[美月もも_盯]", + "14870": "[美月もも_嗨老婆]", + "14875": "[美月もも_好耶]", + "14878": "[美月もも_加班]", + "14857": "[美月もも_哭了]", + "14862": "[美月もも_来了]", + "14867": "[美月もも_摸鱼]", + "14858": "[美月もも_请笑我]", + "14864": "[美月もも_收到]", + "14860": "[美月もも_贴贴]", + "14861": "[美月もも_偷窥]", + "14879": "[美月もも_晚安]", + "14859": "[美月もも_想桃]", + "14866": "[美月もも_赞]", + "14865": "[美月もも_早呀]", + "14874": "[美月もも_震撼]", + "14872": "[美月もも_mua]", + "14868": "[美月もも_OK]", + "14882": "[烛灵儿Hikari_快睡觉]", + "14883": "[烛灵儿Hikari_好饿]", + "14884": "[烛灵儿Hikari_哼]", + "14885": "[烛灵儿Hikari_生气]", + "14886": "[烛灵儿Hikari_加油]", + "14887": "[烛灵儿Hikari_贴贴]", + "14888": "[烛灵儿Hikari_呜呜]", + "14889": "[烛灵儿Hikari_爱你]", + "14890": "[烛灵儿Hikari_流汗]", + "14891": "[烛灵儿Hikari_晚安]", + "14892": "[烛灵儿Hikari_暗中观察]", + "14893": "[烛灵儿Hikari_病娇]", + "14894": "[烛灵儿Hikari_汪]", + "14895": "[烛灵儿Hikari_嫌弃]", + "14896": "[烛灵儿Hikari_抱抱]", + "14897": "[烛灵儿Hikari_哒咩]", + "14898": "[烛灵儿Hikari_叉出去]", + "14899": "[烛灵儿Hikari_问号]", + "14900": "[烛灵儿Hikari_害怕]", + "14901": "[烛灵儿Hikari_烛脑过载]", + "14902": "[烛灵儿Hikari_打call]", + "14903": "[烛灵儿Hikari_妈妈饭饭]", + "14904": "[烛灵儿Hikari_亲亲]", + "14905": "[烛灵儿Hikari_兴奋]", + "14906": "[烛灵儿Hikari_害羞]", + "14907": "[艾露露装扮表情包_你干嘛呀]", + "14908": "[艾露露装扮表情包_比心]", + "14909": "[艾露露装扮表情包_嘻嘻]", + "14910": "[艾露露装扮表情包_吃点好的]", + "14911": "[艾露露装扮表情包_亲亲]", + "14912": "[艾露露装扮表情包_好听]", + "14913": "[艾露露装扮表情包_问号]", + "14914": "[艾露露装扮表情包_吃我一拳]", + "14915": "[艾露露装扮表情包_爱你哦]", + "14916": "[艾露露装扮表情包_害羞]", + "14917": "[艾露露装扮表情包_好耶]", + "14918": "[艾露露装扮表情包_打call]", + "14919": "[艾露露装扮表情包_急急急]", + "14920": "[艾露露装扮表情包_不急不急]", + "14921": "[艾露露装扮表情包_养熊人]", + "14922": "[艾露露装扮表情包_打住]", + "14923": "[艾露露装扮表情包_就这]", + "14924": "[艾露露装扮表情包_吃块披萨吧]", + "14925": "[艾露露装扮表情包_不愧是我]", + "14926": "[艾露露装扮表情包_差不多得了]", + "14927": "[艾露露装扮表情包_抱抱]", + "14928": "[艾露露装扮表情包_看看你的]", + "14929": "[艾露露装扮表情包_摸摸]", + "14930": "[艾露露装扮表情包_摸鱼]", + "14931": "[艾露露装扮表情包_无语]", + "14942": "[笙歌_惊讶]", + "14943": "[笙歌_小丑]", + "14944": "[笙歌_不可以]", + "14945": "[笙歌_傲娇]", + "14946": "[笙歌_晚安]", + "14947": "[笙歌_喝奶茶]", + "14948": "[笙歌_困了]", + "14949": "[笙歌_喂喂喂]", + "14950": "[笙歌_不屑]", + "14951": "[笙歌_幸灾乐祸]", + "14952": "[笙歌_叫爸爸]", + "14953": "[笙歌_赞叹]", + "14954": "[笙歌_v我50]", + "14955": "[笙歌_安慰]", + "14956": "[笙歌_大哭]", + "14957": "[笙歌_老婆]", + "14958": "[笙歌_查岗]", + "14959": "[笙歌_磕CP]", + "14960": "[笙歌_mua]", + "14961": "[笙歌_委屈]", + "14962": "[笙歌_得意]", + "14963": "[笙歌_无语]", + "14964": "[笙歌_偷笑]", + "14965": "[笙歌_附和]", + "14966": "[笙歌_我生气了]", + "14967": "[甜点师奶茶熊_给你心心]", + "14968": "[甜点师奶茶熊_哈喽]", + "14969": "[甜点师奶茶熊_emmm]", + "14970": "[甜点师奶茶熊_好气]", + "14971": "[甜点师奶茶熊_惊]", + "14972": "[甜点师奶茶熊_记仇]", + "14973": "[甜点师奶茶熊_吃瓜]", + "14974": "[甜点师奶茶熊_下饭]", + "14975": "[甜点师奶茶熊_嘿嘿]", + "14976": "[甜点师奶茶熊_叹气]", + "14977": "[甜点师奶茶熊_贫穷]", + "14978": "[甜点师奶茶熊_哇哦]", + "14979": "[甜点师奶茶熊_躺平]", + "14980": "[甜点师奶茶熊_问号]", + "14981": "[甜点师奶茶熊_报警了]", + "14982": "[牧牧白-万有引力_快逃]", + "14983": "[牧牧白-万有引力_哈哈]", + "14984": "[牧牧白-万有引力_好那个哦]", + "14985": "[牧牧白-万有引力_急了]", + "14986": "[牧牧白-万有引力_kisskiss]", + "14987": "[牧牧白-万有引力_还能说话吗]", + "14988": "[牧牧白-万有引力_寄!]", + "14989": "[牧牧白-万有引力_投降啦]", + "14990": "[牧牧白-万有引力_已取证]", + "14991": "[牧牧白-万有引力_我来背锅]", + "14992": "[牧牧白-万有引力_私密马三]", + "14993": "[牧牧白-万有引力_妈!]", + "14994": "[牧牧白-万有引力_老婆]", + "14995": "[牧牧白-万有引力_我回来了]", + "14996": "[牧牧白-万有引力_大小姐咋了]", + "14997": "[牧牧白-万有引力_?]", + "14998": "[牧牧白-万有引力_赢!]", + "14999": "[牧牧白-万有引力_好吧]", + "15000": "[牧牧白-万有引力_啊?]", + "15001": "[牧牧白-万有引力_不吃这套]", + "15002": "[牧牧白-万有引力_给我白嫖]", + "15003": "[牧牧白-万有引力_鬼!]", + "15004": "[牧牧白-万有引力_V50]", + "15005": "[牧牧白-万有引力_嘘]", + "15006": "[牧牧白-万有引力_走开!]", + "15012": "[王蓝莓表情包_呵]", + "15013": "[王蓝莓表情包_抠鼻]", + "15014": "[王蓝莓表情包_女明星]", + "15015": "[王蓝莓表情包_就是你]", + "15016": "[王蓝莓表情包_妈妈生气]", + "15017": "[王蓝莓表情包_裂开]", + "15018": "[王蓝莓表情包_有趣]", + "15019": "[王蓝莓表情包_咋整]", + "15020": "[王蓝莓表情包_嘻嘻]", + "15021": "[王蓝莓表情包_让我听听]", + "15022": "[王蓝莓表情包_行]", + "15023": "[王蓝莓表情包_王蓝莓发抖]", + "15024": "[王蓝莓表情包_脸红]", + "15025": "[王蓝莓表情包_哼]", + "15026": "[王蓝莓表情包_告辞]", + "15027": "[王蓝莓表情包_嗯嗯]", + "15028": "[王蓝莓表情包_no]", + "15029": "[王蓝莓表情包_唱歌]", + "15030": "[王蓝莓表情包_惨]", + "15031": "[王蓝莓表情包_被打]", + "15032": "[王蓝莓表情包_爸爸发抖]", + "15033": "[王蓝莓表情包_就打你]", + "15034": "[王蓝莓表情包_啊啊啊]", + "15035": "[王蓝莓表情包_就这]", + "15036": "[王蓝莓表情包_撅嘴]", + "15037": "[黑白猫_暗中观察]", + "15038": "[黑白猫_饿饿]", + "15039": "[黑白猫_好耶]", + "15040": "[黑白猫_你币有了]", + "15041": "[黑白猫_喜欢]", + "15042": "[黑白猫_伤心]", + "15043": "[黑白猫_想开了]", + "15044": "[黑白猫_疑惑]", + "15045": "[黑白猫_开心]", + "15046": "[黑白猫_晚安]", + "15047": "[黑白猫_摸摸头]", + "15048": "[黑白猫_乐了]", + "15049": "[黑白猫_贴贴]", + "15050": "[黑白猫_乖巧]", + "15051": "[黑白猫_嗨]", + "15052": "[悲伤蛙表情包2_你不对劲]", + "15053": "[悲伤蛙表情包2_摸鱼]", + "15054": "[悲伤蛙表情包2_夺笋啊]", + "15055": "[悲伤蛙表情包2_???]", + "15056": "[悲伤蛙表情包2_你挺会啊]", + "15057": "[悲伤蛙表情包2_不值钱]", + "15058": "[悲伤蛙表情包2_不会吧]", + "15059": "[悲伤蛙表情包2_下班]", + "15060": "[悲伤蛙表情包2_敬礼]", + "15061": "[悲伤蛙表情包2_关机]", + "15062": "[悲伤蛙表情包2_心平气和]", + "15063": "[悲伤蛙表情包2_快逃]", + "15064": "[悲伤蛙表情包2_握草]", + "15065": "[悲伤蛙表情包2_蒜了]", + "15066": "[悲伤蛙表情包2_针棒啊]", + "15067": "[悲伤蛙表情包2_球球了]", + "15068": "[悲伤蛙表情包2_裂开]", + "15069": "[悲伤蛙表情包2_笑YUE了]", + "15070": "[悲伤蛙表情包2_RUA他]", + "15071": "[悲伤蛙表情包2_就这]", + "15072": "[悲伤蛙表情包2_YYDS]", + "15073": "[悲伤蛙表情包2_嘻嘻嘻]", + "15074": "[悲伤蛙表情包2_拒绝内卷]", + "15075": "[悲伤蛙表情包2_我爱工作]", + "15076": "[悲伤蛙表情包2_告辞]", + "15077": "[肥肥鲨情侣篇_桃花]", + "15078": "[肥肥鲨情侣篇_送花]", + "15079": "[肥肥鲨情侣篇_约会]", + "15080": "[肥肥鲨情侣篇_哇]", + "15081": "[肥肥鲨情侣篇_惊喜]", + "15082": "[肥肥鲨情侣篇_给你爱心]", + "15083": "[肥肥鲨情侣篇_接受爱心]", + "15084": "[肥肥鲨情侣篇_聊天]", + "15085": "[肥肥鲨情侣篇_巧克力]", + "15086": "[肥肥鲨情侣篇_撒爱心]", + "15087": "[肥肥鲨情侣篇_我的心]", + "15088": "[肥肥鲨情侣篇_丘比特]", + "15089": "[肥肥鲨情侣篇_抱抱]", + "15090": "[肥肥鲨情侣篇_收情书]", + "15091": "[肥肥鲨情侣篇_亲亲]", + "15092": "[肥肥鲨情侣篇_送给你]", + "15093": "[肥肥鲨情侣篇_举高高]", + "15094": "[肥肥鲨情侣篇_拆礼物]", + "15095": "[肥肥鲨情侣篇_把爱送给你]", + "15096": "[肥肥鲨情侣篇_捏脸]", + "15097": "[游乐园表情包_看戏]", + "15098": "[游乐园表情包_小丑竟是我]", + "15099": "[游乐园表情包_散发魅力]", + "15100": "[游乐园表情包_打鼓]", + "15101": "[游乐园表情包_困]", + "15102": "[游乐园表情包_期待]", + "15103": "[游乐园表情包_祝贺]", + "15104": "[游乐园表情包_啊对对对]", + "15105": "[游乐园表情包_power]", + "15106": "[游乐园表情包_吃惊]", + "15107": "[游乐园表情包_委屈]", + "15108": "[游乐园表情包_急急急]", + "15109": "[游乐园表情包_?]", + "15110": "[游乐园表情包_yes]", + "15111": "[游乐园表情包_行]", + "15112": "[缺德猫_嗐嗨嗐]", + "15113": "[缺德猫_嗨]", + "15114": "[缺德猫_两副面孔]", + "15115": "[缺德猫_坏笑]", + "15116": "[缺德猫_嘿嘿小鱼干]", + "15117": "[缺德猫_无语]", + "15118": "[缺德猫_乐]", + "15119": "[缺德猫_查岗]", + "15120": "[缺德猫_闭嘴]", + "15121": "[缺德猫_发现目标]", + "15122": "[缺德猫_强者的肯定]", + "15123": "[缺德猫_亮爪]", + "15124": "[缺德猫_安详]", + "15125": "[缺德猫_打没打内]", + "15126": "[缺德猫_不通过]", + "15127": "[缺德猫_面露难色]", + "15128": "[缺德猫_哈?]", + "15129": "[缺德猫_爱我怕了吗]", + "15130": "[缺德猫_摸鱼]", + "15131": "[缺德猫_困了]", + "15132": "[周末小狼_啊哈]", + "15133": "[周末小狼_突击检查]", + "15134": "[周末小狼_酷酷]", + "15135": "[周末小狼_委屈巴巴]", + "15136": "[周末小狼_服了呀]", + "15137": "[周末小狼_打call]", + "15138": "[周末小狼_救命]", + "15139": "[周末小狼_肯定]", + "15140": "[周末小狼_不想起]", + "15141": "[周末小狼_呆住]", + "15142": "[周末小狼_期待]", + "15143": "[周末小狼_害怕]", + "15144": "[周末小狼_喜欢]", + "15145": "[周末小狼_吔?]", + "15146": "[周末小狼_火大]", + "15147": "[周末小狼_大哭]", + "15148": "[周末小狼_看手机]", + "15149": "[周末小狼_流泪]", + "15150": "[周末小狼_快乐加班]", + "15151": "[周末小狼_强颜欢笑]", + "15152": "[赛博纪元_比心]", + "15153": "[赛博纪元_难过]", + "15154": "[赛博纪元_害羞]", + "15155": "[赛博纪元_呆住]", + "15156": "[赛博纪元_可爱]", + "15157": "[赛博纪元_探头]", + "15158": "[赛博纪元_生气]", + "15159": "[赛博纪元_疑问]", + "15160": "[赛博纪元_拒绝]", + "15161": "[赛博纪元_点头]", + "15162": "[赛博纪元_无语]", + "15163": "[赛博纪元_嘬一口]", + "15164": "[赛博纪元_思考]", + "15165": "[赛博纪元_震惊]", + "15166": "[赛博纪元_嘲笑]", + "15167": "[赛博纪元_抱歉]", + "15168": "[赛博纪元_666]", + "15169": "[赛博纪元_满意]", + "15170": "[赛博纪元_你被捕了]", + "15171": "[赛博纪元_坏笑]", + "15172": "[恋恋海蓝色_不屑]", + "15173": "[恋恋海蓝色_哒咩]", + "15174": "[恋恋海蓝色_大哭]", + "15175": "[恋恋海蓝色_打call]", + "15176": "[恋恋海蓝色_嘿嘿]", + "15177": "[恋恋海蓝色_喵呜]", + "15178": "[恋恋海蓝色_委屈]", + "15179": "[恋恋海蓝色_困困]", + "15180": "[恋恋海蓝色_略略略]", + "15181": "[恋恋海蓝色_晚安]", + "15182": "[恋恋海蓝色_cpu爆炸]", + "15183": "[恋恋海蓝色_开心]", + "15184": "[恋恋海蓝色_比心]", + "15185": "[恋恋海蓝色_期待]", + "15186": "[恋恋海蓝色_投币]", + "15187": "[宅狐小藏的日子表情_吸]", + "15188": "[宅狐小藏的日子表情_倒立鼓掌]", + "15189": "[宅狐小藏的日子表情_无语子]", + "15190": "[宅狐小藏的日子表情_优雅]", + "15191": "[宅狐小藏的日子表情_dd]", + "15192": "[宅狐小藏的日子表情_想要]", + "15193": "[宅狐小藏的日子表情_麻了]", + "15194": "[宅狐小藏的日子表情_酷啊]", + "15195": "[宅狐小藏的日子表情_送fafa]", + "15196": "[宅狐小藏的日子表情_我酸了]", + "15197": "[宅狐小藏的日子表情_留个耳朵]", + "15198": "[宅狐小藏的日子表情_盯]", + "15199": "[宅狐小藏的日子表情_佛系]", + "15200": "[宅狐小藏的日子表情_啥]", + "15201": "[宅狐小藏的日子表情_难过]", + "15202": "[萌二崽崽的春天图_爱你]", + "15203": "[萌二崽崽的春天图_呜呜呜]", + "15204": "[萌二崽崽的春天图_?]", + "15205": "[萌二崽崽的春天图_委屈]", + "15206": "[萌二崽崽的春天图_害羞]", + "15207": "[萌二崽崽的春天图_啊啊啊]", + "15208": "[萌二崽崽的春天图_无语]", + "15209": "[萌二崽崽的春天图_哎]", + "15210": "[萌二崽崽的春天图_火了]", + "15211": "[萌二崽崽的春天图_哼]", + "15212": "[萌二崽崽的春天图_收到]", + "15213": "[萌二崽崽的春天图_疲惫]", + "15214": "[萌二崽崽的春天图_赞]", + "15215": "[萌二崽崽的春天图_吃惊]", + "15216": "[萌二崽崽的春天图_扮鬼脸]", + "15217": "[奇魔猪猪仔表情包_爱你]", + "15218": "[奇魔猪猪仔表情包_菜猪]", + "15219": "[奇魔猪猪仔表情包_送花]", + "15220": "[奇魔猪猪仔表情包_焦绿]", + "15221": "[奇魔猪猪仔表情包_真无雨]", + "15222": "[奇魔猪猪仔表情包_滋你]", + "15223": "[奇魔猪猪仔表情包_kiss]", + "15224": "[奇魔猪猪仔表情包_小草]", + "15225": "[奇魔猪猪仔表情包_一切随缘]", + "15226": "[奇魔猪猪仔表情包_举个栗子]", + "15227": "[奇魔猪猪仔表情包_你算哪根葱]", + "15228": "[奇魔猪猪仔表情包_急了]", + "15229": "[奇魔猪猪仔表情包_叉出去]", + "15230": "[奇魔猪猪仔表情包_紫腚行]", + "15231": "[奇魔猪猪仔表情包_彩虹屁]", + "15252": "[发射小人表情包_反了]", + "15253": "[发射小人表情包_大震惊]", + "15254": "[发射小人表情包_发射彩虹]", + "15255": "[发射小人表情包_假装乐观]", + "15256": "[发射小人表情包_令人头大]", + "15257": "[发射小人表情包_大佬喝茶]", + "15258": "[发射小人表情包_比个心]", + "15259": "[发射小人表情包_okk]", + "15260": "[发射小人表情包_无语]", + "15261": "[发射小人表情包_毫无头绪]", + "15262": "[发射小人表情包_怒了]", + "15263": "[发射小人表情包_摇花手]", + "15264": "[发射小人表情包_变傻光波]", + "15265": "[发射小人表情包_记得吃药]", + "15266": "[发射小人表情包_emo]", + "15267": "[敷衍熊_求求了]", + "15268": "[敷衍熊_迷惑]", + "15269": "[敷衍熊_开饭]", + "15270": "[敷衍熊_勾引]", + "15271": "[敷衍熊_出现]", + "15272": "[敷衍熊_阴暗爬行]", + "15273": "[敷衍熊_拉屎]", + "15274": "[敷衍熊_纠结]", + "15275": "[敷衍熊_看戏]", + "15276": "[敷衍熊_功德]", + "15277": "[敷衍熊_睡觉]", + "15278": "[敷衍熊_呆]", + "15279": "[敷衍熊_发呆]", + "15280": "[敷衍熊_路过]", + "15281": "[敷衍熊_感动]", + "15282": "[敷衍熊_赞]", + "15283": "[敷衍熊_思考]", + "15284": "[敷衍熊_转运]", + "15285": "[敷衍熊_流鼻血]", + "15286": "[敷衍熊_帅气]", + "15287": "[UPOWER_410588425_打卡]", + "15288": "[UPOWER_410588425_打call]", + "15289": "[UPOWER_410588425_老规矩]", + "15290": "[UPOWER_410588425_鸽了]", + "15291": "[UPOWER_410588425_白嫖]", + "15292": "[UPOWER_410588425_都在这]", + "15293": "[UPOWER_410588425_狠狠投币]", + "15294": "[UPOWER_410588425_一键三连]", + "15295": "[UPOWER_410588425_开啵啦]", + "15296": "[UPOWER_410588425_注意身体]", + "15297": "[无语叽仔_靓叽无语]", + "15298": "[无语叽仔_收嘤叽]", + "15299": "[无语叽仔_忘叽]", + "15300": "[无语叽仔_太乐啦]", + "15301": "[无语叽仔_滑叽]", + "15302": "[无语叽仔_鄙叽]", + "15303": "[无语叽仔_宕叽]", + "15304": "[无语叽仔_飞叽]", + "15305": "[无语叽仔_封叽]", + "15306": "[无语叽仔_秃叽]", + "15307": "[无语叽仔_委屈巴巴]", + "15308": "[无语叽仔_喜欢]", + "15309": "[无语叽仔_我来啦]", + "15310": "[无语叽仔_重伤倒地]", + "15311": "[无语叽仔_火叽]", + "15312": "[缺德猫炫彩版_瞳孔地震]", + "15313": "[缺德猫炫彩版_帅]", + "15314": "[缺德猫炫彩版_remake]", + "15315": "[缺德猫炫彩版_满头问号]", + "15316": "[缺德猫炫彩版_惊醒]", + "15317": "[缺德猫炫彩版_拍手]", + "15318": "[缺德猫炫彩版_已自闭]", + "15319": "[缺德猫炫彩版_你醒啦]", + "15320": "[缺德猫炫彩版_耶比]", + "15321": "[缺德猫炫彩版_酸了]", + "15322": "[缺德猫炫彩版_夸颂]", + "15323": "[缺德猫炫彩版_怎么回事呢]", + "15324": "[缺德猫炫彩版_开始贩剑]", + "15325": "[缺德猫炫彩版_半夜偷吃]", + "15326": "[缺德猫炫彩版_打咩]", + "15327": "[缺德猫炫彩版_无语]", + "15328": "[缺德猫炫彩版_乐]", + "15329": "[缺德猫炫彩版_隐忍]", + "15330": "[缺德猫炫彩版_危]", + "15331": "[缺德猫炫彩版_恰个v]", + "15332": "[The Boba Family 奶茶一家_星星眼]", + "15333": "[The Boba Family 奶茶一家_抱抱]", + "15334": "[The Boba Family 奶茶一家_爱]", + "15335": "[The Boba Family 奶茶一家_哭哭]", + "15336": "[The Boba Family 奶茶一家_晚安]", + "15337": "[The Boba Family 奶茶一家_哈哈哈]", + "15338": "[The Boba Family 奶茶一家_惊]", + "15339": "[The Boba Family 奶茶一家_摸摸]", + "15340": "[The Boba Family 奶茶一家_加油]", + "15341": "[The Boba Family 奶茶一家_打你]", + "15342": "[The Boba Family 奶茶一家_呜呜]", + "15343": "[The Boba Family 奶茶一家_乖巧]", + "15344": "[The Boba Family 奶茶一家_好耶]", + "15345": "[The Boba Family 奶茶一家_耶]", + "15346": "[The Boba Family 奶茶一家_哦]", + "15347": "[鬼刀风曳表情包_ 震惊]", + "15348": "[鬼刀风曳表情包_冲]", + "15349": "[鬼刀风曳表情包_哇哦]", + "15350": "[鬼刀风曳表情包_打call]", + "15351": "[鬼刀风曳表情包_窒息]", + "15352": "[鬼刀风曳表情包_让我看看]", + "15353": "[鬼刀风曳表情包_哼]", + "15354": "[鬼刀风曳表情包_不愧是我]", + "15355": "[鬼刀风曳表情包_一键三连]", + "15356": "[鬼刀风曳表情包_润了]", + "15357": "[鬼刀风曳表情包_慌张]", + "15358": "[鬼刀风曳表情包_干饭]", + "15359": "[鬼刀风曳表情包_流口水]", + "15360": "[鬼刀风曳表情包_无语]", + "15361": "[鬼刀风曳表情包_?]", + "15362": "[鬼刀风曳表情包_听我解释]", + "15363": "[鬼刀风曳表情包_睡了]", + "15364": "[鬼刀风曳表情包_啊这]", + "15365": "[鬼刀风曳表情包_生气]", + "15366": "[鬼刀风曳表情包_呜呜呜]", + "15367": "[小恐龙吧唧_别来杠]", + "15368": "[小恐龙吧唧_什么]", + "15369": "[小恐龙吧唧_爱你]", + "15370": "[小恐龙吧唧_有点东西]", + "15371": "[小恐龙吧唧_不]", + "15372": "[小恐龙吧唧_好气]", + "15373": "[小恐龙吧唧_哦 是吗]", + "15374": "[小恐龙吧唧_扣1]", + "15375": "[小恐龙吧唧_那我走]", + "15376": "[小恐龙吧唧_委屈]", + "15377": "[小恐龙吧唧_哭哭]", + "15378": "[小恐龙吧唧_达咩]", + "15379": "[小恐龙吧唧_鲨了我吧]", + "15380": "[小恐龙吧唧_不听不听]", + "15381": "[小恐龙吧唧_离谱]", + "15382": "[微雨临安_品茶]", + "15383": "[微雨临安_春困]", + "15384": "[微雨临安_你有事吗]", + "15385": "[微雨临安_别太荒谬]", + "15386": "[微雨临安_为你撑伞]", + "15387": "[微雨临安_努力加餐]", + "15388": "[微雨临安_6眼泪了]", + "15389": "[微雨临安_放纸鸢]", + "15390": "[微雨临安_来吃青团]", + "15391": "[微雨临安_你起来啦]", + "15392": "[微雨临安_簪花]", + "15393": "[微雨临安_吃青团]", + "15394": "[微雨临安_惊雷]", + "15395": "[微雨临安_本少来也]", + "15396": "[微雨临安_一键三连]", + "15397": "[花月小熊座_加一]", + "15398": "[花月小熊座_点点点]", + "15399": "[花月小熊座_哭哭]", + "15400": "[花月小熊座_饿饿饭饭]", + "15401": "[花月小熊座_搬砖中]", + "15402": "[花月小熊座_真香]", + "15403": "[花月小熊座_紧张]", + "15404": "[花月小熊座_疑问]", + "15405": "[花月小熊座_OK]", + "15406": "[花月小熊座_摸鱼]", + "15407": "[花月小熊座_生气]", + "15408": "[花月小熊座_害羞]", + "15409": "[花月小熊座_可爱]", + "15410": "[花月小熊座_谢谢]", + "15411": "[花月小熊座_投币]", + "15412": "[山海异兽-白泽_三连]", + "15413": "[山海异兽-白泽_惊吓]", + "15414": "[山海异兽-白泽_吃瓜]", + "15415": "[山海异兽-白泽_偷看]", + "15416": "[山海异兽-白泽_前排]", + "15417": "[山海异兽-白泽_害羞]", + "15418": "[山海异兽-白泽_哭哭]", + "15419": "[山海异兽-白泽_投币]", + "15420": "[山海异兽-白泽_吐魂]", + "15421": "[山海异兽-白泽_点赞]", + "15422": "[山海异兽-白泽_问号]", + "15423": "[山海异兽-白泽_比心]", + "15424": "[山海异兽-白泽_生气]", + "15425": "[山海异兽-白泽_达咩]", + "15426": "[山海异兽-白泽_无语]", + "15427": "[黑粉告白漫_吓]", + "15428": "[黑粉告白漫_心态爆炸]", + "15429": "[黑粉告白漫_比心]", + "15430": "[黑粉告白漫_害羞]", + "15431": "[黑粉告白漫_就这]", + "15432": "[黑粉告白漫_开心]", + "15433": "[黑粉告白漫_哭哭]", + "15434": "[黑粉告白漫_魔法师]", + "15435": "[黑粉告白漫_嘻嘻]", + "15436": "[黑粉告白漫_投币]", + "15437": "[黑粉告白漫_生气]", + "15438": "[黑粉告白漫_晕]", + "15439": "[黑粉告白漫_装傻]", + "15440": "[黑粉告白漫_超喜欢]", + "15441": "[黑粉告白漫_暗中观察]", + "15442": "[搞怪白阿鹅2_鹅滴天]", + "15443": "[搞怪白阿鹅2_酸]", + "15444": "[搞怪白阿鹅2_自闭鹅]", + "15445": "[搞怪白阿鹅2_谢了鹅]", + "15446": "[搞怪白阿鹅2_no]", + "15447": "[搞怪白阿鹅2_开开腿]", + "15448": "[搞怪白阿鹅2_hi]", + "15449": "[搞怪白阿鹅2_鹅之凝视]", + "15450": "[搞怪白阿鹅2_无语鹅]", + "15451": "[搞怪白阿鹅2_扬长鹅去]", + "15452": "[搞怪白阿鹅2_送心心]", + "15453": "[搞怪白阿鹅2_鼓掌]", + "15454": "[搞怪白阿鹅2_好嗨鹅]", + "15455": "[搞怪白阿鹅2_yes]", + "15456": "[搞怪白阿鹅2_?]", + "15457": "[搞怪白阿鹅2_划水鹅]", + "15458": "[搞怪白阿鹅2_友好]", + "15459": "[搞怪白阿鹅2_鹅来啦]", + "15460": "[搞怪白阿鹅2_好无聊鹅]", + "15461": "[搞怪白阿鹅2_啾咪]", + "15462": "[猫右-表情包_ 阿巴阿巴]", + "15463": "[猫右-表情包_消消火]", + "15464": "[猫右-表情包_我不听]", + "15465": "[猫右-表情包_哦,是吼?]", + "15466": "[猫右-表情包_在?]", + "15467": "[猫右-表情包_点赞]", + "15468": "[猫右-表情包_让我看看]", + "15469": "[猫右-表情包_呕了]", + "15470": "[猫右-表情包_上车]", + "15471": "[猫右-表情包_牛]", + "15472": "[猫右-表情包_给你花花]", + "15473": "[猫右-表情包_你币右了]", + "15474": "[猫右-表情包_多喝水]", + "15475": "[猫右-表情包_生气]", + "15476": "[猫右-表情包_睡了]", + "15477": "[猫右-表情包_啊?]", + "15478": "[猫右-表情包_干饭]", + "15479": "[猫右-表情包_哭哭]", + "15480": "[猫右-表情包_哼]", + "15481": "[猫右-表情包_走你]", + "15482": "[普通小狗表情包_随缘吧]", + "15483": "[普通小狗表情包_无语子]", + "15484": "[普通小狗表情包_行吧]", + "15485": "[普通小狗表情包_坐等]", + "15486": "[普通小狗表情包_我来了]", + "15487": "[普通小狗表情包_比心心]", + "15488": "[普通小狗表情包_满脸疑惑]", + "15489": "[普通小狗表情包_冲鸭]", + "15490": "[普通小狗表情包_我超爱]", + "15491": "[普通小狗表情包_让我看看]", + "15492": "[普通小狗表情包_仙女生气]", + "15493": "[普通小狗表情包_前排看戏]", + "15494": "[普通小狗表情包_该吃药了]", + "15495": "[普通小狗表情包_cool]", + "15496": "[普通小狗表情包_哄不好了]", + "15497": "[暴富糖粽兔兔表情_买买买]", + "15498": "[暴富糖粽兔兔表情_兔兔许愿]", + "15499": "[暴富糖粽兔兔表情_来啦来啦]", + "15500": "[暴富糖粽兔兔表情_红包来啦]", + "15501": "[暴富糖粽兔兔表情_吃吃吃]", + "15502": "[暴富糖粽兔兔表情_发大财]", + "15503": "[暴富糖粽兔兔表情_谢谢老板]", + "15504": "[暴富糖粽兔兔表情_乖巧]", + "15505": "[暴富糖粽兔兔表情_好运连连]", + "15506": "[暴富糖粽兔兔表情_大吉大利]", + "15507": "[暴富糖粽兔兔表情_好运来]", + "15508": "[暴富糖粽兔兔表情_福气满满]", + "15509": "[暴富糖粽兔兔表情_心想事成]", + "15510": "[暴富糖粽兔兔表情_苹安喜乐]", + "15511": "[暴富糖粽兔兔表情_兔圆家润]", + "15512": "[九阙风华_糟糕]", + "15513": "[九阙风华_质疑眼神]", + "15514": "[九阙风华_礼貌微笑]", + "15515": "[九阙风华_伤心]", + "15516": "[九阙风华_偷笑]", + "15517": "[九阙风华_不愧是我]", + "15518": "[九阙风华_尴尬]", + "15519": "[九阙风华_星星眼]", + "15520": "[九阙风华_呵呵]", + "15521": "[九阙风华_开心]", + "15522": "[九阙风华_呆]", + "15523": "[九阙风华_哼]", + "15524": "[九阙风华_得意]", + "15525": "[九阙风华_闭嘴]", + "15526": "[九阙风华_忍]", + "15527": "[偏爱星河梦_彩虹]", + "15528": "[偏爱星河梦_闪亮]", + "15529": "[偏爱星河梦_心碎]", + "15530": "[偏爱星河梦_下次一定]", + "15531": "[偏爱星河梦_吐彩虹]", + "15532": "[偏爱星河梦_吃鱼]", + "15533": "[偏爱星河梦_敬礼]", + "15534": "[偏爱星河梦_?]", + "15535": "[偏爱星河梦_着急]", + "15536": "[偏爱星河梦_啧]", + "15537": "[偏爱星河梦_送我]", + "15538": "[偏爱星河梦_555]", + "15539": "[偏爱星河梦_炫我嘴里]", + "15540": "[偏爱星河梦_睡了]", + "15541": "[偏爱星河梦_贴贴]", + "15542": "[女仆兔耳漫_投币]", + "15543": "[女仆兔耳漫_发火]", + "15544": "[女仆兔耳漫_困困]", + "15545": "[女仆兔耳漫_流泪]", + "15546": "[女仆兔耳漫_无语]", + "15547": "[女仆兔耳漫_小心]", + "15548": "[女仆兔耳漫_害羞]", + "15549": "[女仆兔耳漫_委屈]", + "15550": "[女仆兔耳漫_大哭]", + "15551": "[女仆兔耳漫_点赞]", + "15552": "[女仆兔耳漫_疑惑]", + "15553": "[女仆兔耳漫_抱抱]", + "15554": "[女仆兔耳漫_大喊]", + "15555": "[女仆兔耳漫_晕]", + "15556": "[女仆兔耳漫_爱你]", + "15557": "[名剑冢表情包_吐血]", + "15558": "[名剑冢表情包_中箭]", + "15559": "[名剑冢表情包_不要]", + "15560": "[名剑冢表情包_冲了]", + "15561": "[名剑冢表情包_一脸懵逼]", + "15562": "[名剑冢表情包_哦]", + "15563": "[名剑冢表情包_好啊好啊]", + "15564": "[名剑冢表情包_好吃]", + "15565": "[名剑冢表情包_期待]", + "15566": "[名剑冢表情包_呆]", + "15567": "[名剑冢表情包_我饱了]", + "15568": "[名剑冢表情包_尴尬]", + "15569": "[名剑冢表情包_就是你了]", + "15570": "[名剑冢表情包_这个嘛]", + "15571": "[名剑冢表情包_婉拒了哈]", + "15572": "[名剑冢表情包_好困]", + "15573": "[名剑冢表情包_疯狂记仇]", + "15574": "[名剑冢表情包_开心]", + "15575": "[名剑冢表情包_找打]", + "15576": "[名剑冢表情包_认真思考]", + "15577": "[油画星球_亲亲]", + "15578": "[油画星球_伤心]", + "15579": "[油画星球_生气]", + "15580": "[油画星球_投币]", + "15581": "[油画星球_贴贴]", + "15582": "[油画星球_酷]", + "15583": "[油画星球_哼]", + "15584": "[油画星球_无语]", + "15585": "[油画星球_赞]", + "15586": "[油画星球_出现]", + "15587": "[油画星球_开心]", + "15588": "[油画星球_起飞]", + "15589": "[油画星球_害羞]", + "15590": "[油画星球_晕]", + "15591": "[油画星球_惊]", + "15592": "[对你百无禁忌_愛心]", + "15593": "[对你百无禁忌_怎么肥事]", + "15594": "[对你百无禁忌_亲亲]", + "15595": "[对你百无禁忌_抱抱]", + "15596": "[对你百无禁忌_生气]", + "15597": "[对你百无禁忌_不行]", + "15598": "[对你百无禁忌_盯]", + "15599": "[对你百无禁忌_疑惑]", + "15600": "[对你百无禁忌_睡了]", + "15601": "[对你百无禁忌_我要闹了]", + "15602": "[对你百无禁忌_大吃一惊]", + "15603": "[对你百无禁忌_委屈]", + "15604": "[对你百无禁忌_可怜]", + "15605": "[对你百无禁忌_受伤]", + "15606": "[对你百无禁忌_来喽]", + "15617": "[寄山色_簪花带酒]", + "15618": "[寄山色_展信安]", + "15619": "[寄山色_别走]", + "15620": "[寄山色_且慢]", + "15621": "[寄山色_三月折柳]", + "15622": "[寄山色_燕来]", + "15623": "[寄山色_你说得对]", + "15624": "[寄山色_挠头]", + "15625": "[寄山色_春困]", + "15626": "[寄山色_春水煎茶]", + "15627": "[寄山色_多读点书]", + "15628": "[寄山色_风流倜傥]", + "15629": "[寄山色_与你赏灯]", + "15630": "[寄山色_貌美如花]", + "15631": "[寄山色_一键三连]", + "15632": "[前兔无量_寒冷]", + "15633": "[前兔无量_吃瓜]", + "15634": "[前兔无量_打call]", + "15635": "[前兔无量_生气]", + "15636": "[前兔无量_爱心]", + "15637": "[前兔无量_钱兔似锦]", + "15638": "[前兔无量_生草]", + "15639": "[前兔无量_贴贴]", + "15640": "[前兔无量_点赞]", + "15641": "[前兔无量_学习]", + "15642": "[前兔无量_好耶]", + "15643": "[前兔无量_睡觉]", + "15644": "[前兔无量_欧皇]", + "15645": "[前兔无量_震惊]", + "15646": "[前兔无量_可怜]", + "15647": "[棠间月_傲娇]", + "15648": "[棠间月_比心]", + "15649": "[棠间月_大哭]", + "15650": "[棠间月_可怜]", + "15651": "[棠间月_灵光]", + "15652": "[棠间月_思索]", + "15653": "[棠间月_疑问]", + "15654": "[棠间月_叹气]", + "15655": "[棠间月_阴险]", + "15656": "[棠间月_吃瓜]", + "15657": "[棠间月_发呆]", + "15658": "[棠间月_撑伞]", + "15659": "[棠间月_吃惊]", + "15660": "[棠间月_害怕]", + "15661": "[棠间月_呵呵]", + "15662": "[悲伤蛙表情包_就这?]", + "15663": "[悲伤蛙表情包_不会吧]", + "15664": "[悲伤蛙表情包_???]", + "15665": "[悲伤蛙表情包_球球了~]", + "15666": "[悲伤蛙表情包_告辞]", + "15667": "[悲伤蛙表情包_嘻嘻嘻]", + "15668": "[悲伤蛙表情包_蒜了~]", + "15669": "[悲伤蛙表情包_脚趾抠地]", + "15670": "[悲伤蛙表情包_对你敬礼]", + "15671": "[悲伤蛙表情包_针棒啊~]", + "15672": "[悲伤蛙表情包_不值钱]", + "15673": "[悲伤蛙表情包_夺笋啊]", + "15674": "[悲伤蛙表情包_你不对劲]", + "15675": "[悲伤蛙表情包_裂开]", + "15676": "[悲伤蛙表情包_RUA他]", + "15677": "[悲伤蛙表情包_别骂了]", + "15678": "[悲伤蛙表情包_你挺会啊]", + "15679": "[悲伤蛙表情包_笑YUE]", + "15680": "[悲伤蛙表情包_YYDS]", + "15681": "[悲伤蛙表情包_心平气和]" +} \ No newline at end of file diff --git a/bilibili_api/data/geetest/captcha.html b/bilibili_api/data/geetest/captcha.html new file mode 100644 index 0000000000000000000000000000000000000000..e019fced05a3ec55c4cd07c73e3c743324936879 --- /dev/null +++ b/bilibili_api/data/geetest/captcha.html @@ -0,0 +1,587 @@ + + + + + + + 验证 + + + + +

+ 请完成验证码 +

+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/bilibili_api/data/geetest/captcha.js b/bilibili_api/data/geetest/captcha.js new file mode 100644 index 0000000000000000000000000000000000000000..c2eadc88b87c069e2e2b0ab6882e2557be9466e8 --- /dev/null +++ b/bilibili_api/data/geetest/captcha.js @@ -0,0 +1,35 @@ +var handler = function (captchaObj) { + captchaObj.appendTo('#captcha'); + captchaObj.onReady(function () { + $("#wait").hide(); + }); + $("#done").click(function () { + var result = captchaObj.getValidate(); + if (!result) { + alert("请完成验证"); + } + else { + var validate = result.geetest_validate; + var seccode = result.geetest_seccode; + window.location.href = window.location.href + "result/validate=" + validate + "&seccode=" + seccode; + } + }); + // 更多前端接口说明请参见:http://docs.geetest.com/install/client/web-front/ +}; +$('#wait').show(); +// 调用 initGeetest 进行初始化 +// 参数1:配置参数 +// 参数2:回调,回调的第一个参数验证码对象,之后可以使用它调用相应的接口 +initGeetest({ + // 以下 4 个配置参数为必须,不能缺少 + gt: { Python_Interface: GT }, // 这里需要替换成 python 获取的 gt + challenge: { Python_Interface: CHALLENGE }, // 这里需要替换成 python 获取的 challenge + offline: false, // 表示用户后台检测极验服务器是否宕机 + new_captcha: true, // 用于宕机时表示是新验证码的宕机 + + product: "popup", // 产品形式,包括:float,popup + width: "300px", + https: true + + // 更多前端配置参数说明请参见:http://docs.geetest.com/install/client/web-front/ +}, handler); diff --git a/bilibili_api/data/geetest/done.html b/bilibili_api/data/geetest/done.html new file mode 100644 index 0000000000000000000000000000000000000000..b1f26e61201fe7677211cb441cf2e245e54d25ce --- /dev/null +++ b/bilibili_api/data/geetest/done.html @@ -0,0 +1,16 @@ + + + + + + + 完成验证 + + + +
+

您已完成验证!可以关闭页面了!

+
+ + + \ No newline at end of file diff --git a/bilibili_api/data/geetest/gt.js b/bilibili_api/data/geetest/gt.js new file mode 100644 index 0000000000000000000000000000000000000000..ef0399f44a0f778cfecbd14907e725295cac9d02 --- /dev/null +++ b/bilibili_api/data/geetest/gt.js @@ -0,0 +1,353 @@ +"v0.4.8 Geetest Inc."; + +(function (window) { + "use strict"; + if (typeof window === 'undefined') { + throw new Error('Geetest requires browser environment'); + } + + var document = window.document; + var Math = window.Math; + var head = document.getElementsByTagName("head")[0]; + + function _Object(obj) { + this._obj = obj; + } + + _Object.prototype = { + _each: function (process) { + var _obj = this._obj; + for (var k in _obj) { + if (_obj.hasOwnProperty(k)) { + process(k, _obj[k]); + } + } + return this; + } + }; + + function Config(config) { + var self = this; + new _Object(config)._each(function (key, value) { + self[key] = value; + }); + } + + Config.prototype = { + api_server: 'api.geetest.com', + protocol: 'http://', + typePath: '/gettype.php', + fallback_config: { + slide: { + static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"], + type: 'slide', + slide: '/static/js/geetest.0.0.0.js' + }, + fullpage: { + static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"], + type: 'fullpage', + fullpage: '/static/js/fullpage.0.0.0.js' + } + }, + _get_fallback_config: function () { + var self = this; + if (isString(self.type)) { + return self.fallback_config[self.type]; + } else if (self.new_captcha) { + return self.fallback_config.fullpage; + } else { + return self.fallback_config.slide; + } + }, + _extend: function (obj) { + var self = this; + new _Object(obj)._each(function (key, value) { + self[key] = value; + }) + } + }; + var isNumber = function (value) { + return (typeof value === 'number'); + }; + var isString = function (value) { + return (typeof value === 'string'); + }; + var isBoolean = function (value) { + return (typeof value === 'boolean'); + }; + var isObject = function (value) { + return (typeof value === 'object' && value !== null); + }; + var isFunction = function (value) { + return (typeof value === 'function'); + }; + var MOBILE = /Mobi/i.test(navigator.userAgent); + var pt = MOBILE ? 3 : 0; + + var callbacks = {}; + var status = {}; + + var nowDate = function () { + var date = new Date(); + var year = date.getFullYear(); + var month = date.getMonth() + 1; + var day = date.getDate(); + var hours = date.getHours(); + var minutes = date.getMinutes(); + var seconds = date.getSeconds(); + + if (month >= 1 && month <= 9) { + month = '0' + month; + } + if (day >= 0 && day <= 9) { + day = '0' + day; + } + if (hours >= 0 && hours <= 9) { + hours = '0' + hours; + } + if (minutes >= 0 && minutes <= 9) { + minutes = '0' + minutes; + } + if (seconds >= 0 && seconds <= 9) { + seconds = '0' + seconds; + } + var currentdate = year + '-' + month + '-' + day + " " + hours + ":" + minutes + ":" + seconds; + return currentdate; + } + + var random = function () { + return parseInt(Math.random() * 10000) + (new Date()).valueOf(); + }; + + var loadScript = function (url, cb) { + var script = document.createElement("script"); + script.charset = "UTF-8"; + script.async = true; + + // 对geetest的静态资源添加 crossOrigin + if (/static\.geetest\.com/g.test(url)) { + script.crossOrigin = "anonymous"; + } + + script.onerror = function () { + cb(true); + }; + var loaded = false; + script.onload = script.onreadystatechange = function () { + if (!loaded && + (!script.readyState || + "loaded" === script.readyState || + "complete" === script.readyState)) { + + loaded = true; + setTimeout(function () { + cb(false); + }, 0); + } + }; + script.src = url; + head.appendChild(script); + }; + + var normalizeDomain = function (domain) { + // special domain: uems.sysu.edu.cn/jwxt/geetest/ + // return domain.replace(/^https?:\/\/|\/.*$/g, ''); uems.sysu.edu.cn + return domain.replace(/^https?:\/\/|\/$/g, ''); // uems.sysu.edu.cn/jwxt/geetest + }; + var normalizePath = function (path) { + path = path.replace(/\/+/g, '/'); + if (path.indexOf('/') !== 0) { + path = '/' + path; + } + return path; + }; + var normalizeQuery = function (query) { + if (!query) { + return ''; + } + var q = '?'; + new _Object(query)._each(function (key, value) { + if (isString(value) || isNumber(value) || isBoolean(value)) { + q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&'; + } + }); + if (q === '?') { + q = ''; + } + return q.replace(/&$/, ''); + }; + var makeURL = function (protocol, domain, path, query) { + domain = normalizeDomain(domain); + + var url = normalizePath(path) + normalizeQuery(query); + if (domain) { + url = protocol + domain + url; + } + + return url; + }; + + var load = function (config, send, protocol, domains, path, query, cb) { + var tryRequest = function (at) { + + var url = makeURL(protocol, domains[at], path, query); + loadScript(url, function (err) { + if (err) { + if (at >= domains.length - 1) { + cb(true); + // report gettype error + if (send) { + config.error_code = 508; + var url = protocol + domains[at] + path; + reportError(config, url); + } + } else { + tryRequest(at + 1); + } + } else { + cb(false); + } + }); + }; + tryRequest(0); + }; + + + var jsonp = function (domains, path, config, callback) { + if (isObject(config.getLib)) { + config._extend(config.getLib); + callback(config); + return; + } + if (config.offline) { + callback(config._get_fallback_config()); + return; + } + + var cb = "geetest_" + random(); + window[cb] = function (data) { + if (data.status == 'success') { + callback(data.data); + } else if (!data.status) { + callback(data); + } else { + callback(config._get_fallback_config()); + } + window[cb] = undefined; + try { + delete window[cb]; + } catch (e) { + } + }; + load(config, true, config.protocol, domains, path, { + gt: config.gt, + callback: cb + }, function (err) { + if (err) { + callback(config._get_fallback_config()); + } + }); + }; + + var reportError = function (config, url) { + load(config, false, config.protocol, ['monitor.geetest.com'], '/monitor/send', { + time: nowDate(), + captcha_id: config.gt, + challenge: config.challenge, + pt: pt, + exception_url: url, + error_code: config.error_code + }, function (err) { }) + } + + var throwError = function (errorType, config) { + var errors = { + networkError: '网络错误', + gtTypeError: 'gt字段不是字符串类型' + }; + if (typeof config.onError === 'function') { + config.onError(errors[errorType]); + } else { + throw new Error(errors[errorType]); + } + }; + + var detect = function () { + return window.Geetest || document.getElementById("gt_lib"); + }; + + if (detect()) { + status.slide = "loaded"; + } + + window.initGeetest = function (userConfig, callback) { + + var config = new Config(userConfig); + + if (userConfig.https) { + config.protocol = 'https://'; + } else if (!userConfig.protocol) { + config.protocol = window.location.protocol + '//'; + } + + // for KFC + if (userConfig.gt === '050cffef4ae57b5d5e529fea9540b0d1' || + userConfig.gt === '3bd38408ae4af923ed36e13819b14d42') { + config.apiserver = 'yumchina.geetest.com/'; // for old js + config.api_server = 'yumchina.geetest.com'; + } + + if (userConfig.gt) { + window.GeeGT = userConfig.gt + } + + if (userConfig.challenge) { + window.GeeChallenge = userConfig.challenge + } + + if (isObject(userConfig.getType)) { + config._extend(userConfig.getType); + } + jsonp([config.api_server || config.apiserver], config.typePath, config, function (newConfig) { + var type = newConfig.type; + var init = function () { + config._extend(newConfig); + callback(new window.Geetest(config)); + }; + + callbacks[type] = callbacks[type] || []; + var s = status[type] || 'init'; + if (s === 'init') { + status[type] = 'loading'; + + callbacks[type].push(init); + + load(config, true, config.protocol, newConfig.static_servers || newConfig.domains, newConfig[type] || newConfig.path, null, function (err) { + if (err) { + status[type] = 'fail'; + throwError('networkError', config); + } else { + status[type] = 'loaded'; + var cbs = callbacks[type]; + for (var i = 0, len = cbs.length; i < len; i = i + 1) { + var cb = cbs[i]; + if (isFunction(cb)) { + cb(); + } + } + callbacks[type] = []; + } + }); + } else if (s === "loaded") { + init(); + } else if (s === "fail") { + throwError('networkError', config); + } else if (s === "loading") { + callbacks[type].push(init); + } + }); + + }; + + +})(window); \ No newline at end of file diff --git a/bilibili_api/data/geetest/readme.md b/bilibili_api/data/geetest/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..6b25400f8f3028200f8be4bf61656e334c78b711 --- /dev/null +++ b/bilibili_api/data/geetest/readme.md @@ -0,0 +1 @@ +极验验证页面基于 编写。 diff --git a/bilibili_api/data/geetest/theme.css b/bilibili_api/data/geetest/theme.css new file mode 100644 index 0000000000000000000000000000000000000000..86928f2b303b0db5bb085e3eedc45c27eb8f2110 --- /dev/null +++ b/bilibili_api/data/geetest/theme.css @@ -0,0 +1,161 @@ +body { + margin: 50px 0; + text-align: center; + font-family: "PingFangSC-Regular", "Open Sans", Arial, "Hiragino Sans GB", "Microsoft YaHei", "STHeiti", "WenQuanYi Micro Hei", SimSun, sans-serif; +} + +.inp { + border: 1px solid #cccccc; + border-radius: 2px; + padding: 0 10px; + width: 320px; + height: 40px; + font-size: 18px; +} + +.btn { + display: inline-block; + box-sizing: border-box; + border: 1px solid #cccccc; + border-radius: 2px; + width: 100px; + height: 40px; + line-height: 40px; + font-size: 16px; + color: #666; + cursor: pointer; + background: white linear-gradient(180deg, #ffffff 0%, #f3f3f3 100%); +} + +.btn:hover { + background: white linear-gradient(0deg, #ffffff 0%, #f3f3f3 100%) +} + +#captcha { + width: 300px; + display: inline-block; +} + +label { + vertical-align: top; + display: inline-block; + width: 120px; + text-align: right; +} + +#text { + height: 42px; + width: 298px; + text-align: center; + border-radius: 2px; + background-color: #F3F3F3; + color: #BBBBBB; + font-size: 14px; + letter-spacing: 0.1px; + line-height: 42px; +} + +#wait { + display: none; + height: 42px; + width: 298px; + text-align: center; + border-radius: 2px; + background-color: #F3F3F3; +} + +.loading { + margin: auto; + width: 70px; + height: 20px; +} + +.loading-dot { + float: left; + width: 8px; + height: 8px; + margin: 18px 4px; + background: #ccc; + + -webkit-border-radius: 50%; + -moz-border-radius: 50%; + border-radius: 50%; + + opacity: 0; + + -webkit-box-shadow: 0 0 2px black; + -moz-box-shadow: 0 0 2px black; + -ms-box-shadow: 0 0 2px black; + -o-box-shadow: 0 0 2px black; + box-shadow: 0 0 2px black; + + -webkit-animation: loadingFade 1s infinite; + -moz-animation: loadingFade 1s infinite; + animation: loadingFade 1s infinite; +} + +.loading-dot:nth-child(1) { + -webkit-animation-delay: 0s; + -moz-animation-delay: 0s; + animation-delay: 0s; +} + +.loading-dot:nth-child(2) { + -webkit-animation-delay: 0.1s; + -moz-animation-delay: 0.1s; + animation-delay: 0.1s; +} + +.loading-dot:nth-child(3) { + -webkit-animation-delay: 0.2s; + -moz-animation-delay: 0.2s; + animation-delay: 0.2s; +} + +.loading-dot:nth-child(4) { + -webkit-animation-delay: 0.3s; + -moz-animation-delay: 0.3s; + animation-delay: 0.3s; +} + +@-webkit-keyframes loadingFade { + 0% { + opacity: 0; + } + + 50% { + opacity: 0.8; + } + + 100% { + opacity: 0; + } +} + +@-moz-keyframes loadingFade { + 0% { + opacity: 0; + } + + 50% { + opacity: 0.8; + } + + 100% { + opacity: 0; + } +} + +@keyframes loadingFade { + 0% { + opacity: 0; + } + + 50% { + opacity: 0.8; + } + + 100% { + opacity: 0; + } +} \ No newline at end of file diff --git a/bilibili_api/data/live_area.json b/bilibili_api/data/live_area.json new file mode 100644 index 0000000000000000000000000000000000000000..dd541e09f18d88b804b7d1e284fb7ad2aeddb1bf --- /dev/null +++ b/bilibili_api/data/live_area.json @@ -0,0 +1,4329 @@ +[ + { + "id": 2, + "name": "网游", + "list": [ + { + "id": "86", + "parent_id": "2", + "old_area_id": "4", + "name": "英雄联盟", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/0e808167886ad2299971ea49aade69b3663db9b9.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "329", + "parent_id": "2", + "old_area_id": "3", + "name": "无畏契约", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/78d7a625ebcb88f0d1143cf4ba0426ebb7a4a853.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "89", + "parent_id": "2", + "old_area_id": "4", + "name": "CS:GO", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/63bd489cc644c89be0f2069606a52d7802dfa412.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "240", + "parent_id": "2", + "old_area_id": "3", + "name": "APEX英雄", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/64383fd997f6b2ea79c729042edd1231dc5eb49c.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "666", + "parent_id": "2", + "old_area_id": "3", + "name": "永劫无间", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/3f11252c85ef404a42cd8f0bae2f8547b5f57c7c.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "88", + "parent_id": "2", + "old_area_id": "4", + "name": "穿越火线", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/0630e6087384683f6d920e2739f896e7312763d3.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "590", + "parent_id": "2", + "old_area_id": "1", + "name": "命运方舟", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/ca93e596942d9c6d556b510a691830671d5387d3.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "92", + "parent_id": "2", + "old_area_id": "4", + "name": "DOTA2", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/dd830c1fd58dcf536f4b6099546d213a5781c59b.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "80", + "parent_id": "2", + "old_area_id": "1", + "name": "吃鸡行动", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/5d6671d6cef3d1e3f1544ae82d69259a622c7e98.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "252", + "parent_id": "2", + "old_area_id": "3", + "name": "逃离塔科夫", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/b103d9eced994160031073445dfb91c90fb3dccc.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "695", + "parent_id": "2", + "old_area_id": "3", + "name": "传奇", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/3fd352015d960380e8406f7fe044f5dde6bc30b0.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "78", + "parent_id": "2", + "old_area_id": "3", + "name": "DNF", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/17c75c131445406ba52b3cf82724bb850e760c24.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "782", + "parent_id": "2", + "old_area_id": "3", + "name": "卡拉彼丘", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/539a5a55dd9852b8378f2866e9c5cb71c11c0542.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "774", + "parent_id": "2", + "old_area_id": "3", + "name": "幕后高手", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/bdb4765e85f329c50a59376dcc8393005c6f4b86.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "575", + "parent_id": "2", + "old_area_id": "3", + "name": "生死狙击2", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/02bb75e92d38f2fa9c63a9a5c5bf1d1497cced25.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "599", + "parent_id": "2", + "old_area_id": "3", + "name": "洛奇英雄传", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/ae7808442b33a0925ef892ef42db9fbca267883b.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "102", + "parent_id": "2", + "old_area_id": "3", + "name": "最终幻想14", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/32a5b7da3e79feb394f538c9d95a858fea97b113.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "809", + "parent_id": "2", + "old_area_id": "3", + "name": "重生边缘", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/c08a2240e51a1f97c730bf009ae0d57364fb5594.jpg", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "249", + "parent_id": "2", + "old_area_id": "3", + "name": "星际战甲", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/915f580c0cd68e8303ef5f8f140da4f7ddfc55fa.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "710", + "parent_id": "2", + "old_area_id": "3", + "name": "梦三国", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/61ba98a2a7ae11f400c483b95cb827bcb2795cf2.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "690", + "parent_id": "2", + "old_area_id": "3", + "name": "英魂之刃", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/e409490d10b1a7c09e6a09a033dfe6d8d24fc298.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "82", + "parent_id": "2", + "old_area_id": "3", + "name": "剑网3", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/52ced75fd47d5c9d5820046226628d9e9a25385e.png", + "complex_area_name": "", + "cate_id": "8", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "691", + "parent_id": "2", + "old_area_id": "3", + "name": "铁甲雄兵", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/8a585b08c6bfd7978fd515ffbb6cd5ab91b04a78.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "300", + "parent_id": "2", + "old_area_id": "3", + "name": "封印者", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/fa4f62af998c82d80446e5972bd0d37cc9a2231c.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "653", + "parent_id": "2", + "old_area_id": "3", + "name": "新天龙八部", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/06db6ba8eb18e8b6e69cdc15ec1eb1d0bcc72943.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "667", + "parent_id": "2", + "old_area_id": "3", + "name": "赛尔号", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/6bdd3499a3a66f5e7e9c7753b87575b00594bdae.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "668", + "parent_id": "2", + "old_area_id": "3", + "name": "造梦西游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/e0cb84b0270ad8a082af7a5615ec7787d0d00207.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "669", + "parent_id": "2", + "old_area_id": "3", + "name": "洛克王国", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/d0bdc2347681140a8fe8c2e3a0498e532c55500a.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "670", + "parent_id": "2", + "old_area_id": "3", + "name": "问道", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/005cb4e6ae56b718c5f3dd721d9488dd39b437c1.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "654", + "parent_id": "2", + "old_area_id": "3", + "name": "诛仙世界", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/37ed122a8262299e343f9c6890ae320bd24e5fae.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "652", + "parent_id": "2", + "old_area_id": "3", + "name": "大话西游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/2bd0c6fc3922b2c5724ee3504a56f9fec49ebdd8.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "683", + "parent_id": "2", + "old_area_id": "3", + "name": "奇迹MU", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/b5f521677aa51993c96712b0757d4de55dd5ef43.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "684", + "parent_id": "2", + "old_area_id": "3", + "name": "永恒之塔", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/1b79fe706d14cb1122a840ad5daea56547ffc0d6.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "685", + "parent_id": "2", + "old_area_id": "3", + "name": "QQ三国", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/b0cabcb3dcc2dc4c5d5767dc51d27ebec0aaa5a9.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "677", + "parent_id": "2", + "old_area_id": "3", + "name": "人间地狱", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/f14da86691e3f142df9cc4ac049be5481c1353c8.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "686", + "parent_id": "2", + "old_area_id": "3", + "name": "彩虹岛", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/d46ce8aa981ad9ca3e191e104d587175dc11c329.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "663", + "parent_id": "2", + "old_area_id": "3", + "name": "洛奇", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/539080aa5587af80b05b33ee140fc81d51543251.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "664", + "parent_id": "2", + "old_area_id": "3", + "name": "跑跑卡丁车", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/afb6caa7ee8f67f801d77a6d72a1dd0027ad8187.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "658", + "parent_id": "2", + "old_area_id": "3", + "name": "星际公民", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/b92b907d98e977ce281b8dfa4a2082aec0bd0691.jpg", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "659", + "parent_id": "2", + "old_area_id": "3", + "name": "Squad战术小队", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/4b883d20e6aedaecbf20b0b5e17a8849c45f9616.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "629", + "parent_id": "2", + "old_area_id": "3", + "name": "反恐精英Online", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/5d5843fda51acdfb6f0855d315fb6386ef2ad0f3.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "648", + "parent_id": "2", + "old_area_id": "3", + "name": "风暴奇侠", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/6a79b35e49632b8f62cc97641b24b213388b442f.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "705", + "parent_id": "2", + "old_area_id": "3", + "name": "创世战车", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/5b4c187bd34a681baf0703a207409848893acd8b.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "642", + "parent_id": "2", + "old_area_id": "3", + "name": "装甲战争", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/a6c81d52729e8c5e851e64703356a64f54b6fa81.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "87", + "parent_id": "2", + "old_area_id": "3", + "name": "守望先锋", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/78de2398b0bb48a60828b2db206a4423e3c15124.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "639", + "parent_id": "2", + "old_area_id": "3", + "name": "阿尔比恩", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/8ebda9b3241890f5abbff0db23fb4e6562033e21.jpg", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "600", + "parent_id": "2", + "old_area_id": "3", + "name": "猎杀对决", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/c61c1de35caeba893f6f4427f7b2fc6702fad669.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "472", + "parent_id": "2", + "old_area_id": "3", + "name": "CFHD", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/5da13cd88a5574287eacdcb668f35e970f3bf083.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "650", + "parent_id": "2", + "old_area_id": "3", + "name": "骑士精神2", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/8a0a53a408aedc7f8f35f25f268d9940a680a5ec.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "680", + "parent_id": "2", + "old_area_id": "3", + "name": "超击突破", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/408c48c93a8478fa8935b3ce56f0f2d70af403a8.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "634", + "parent_id": "2", + "old_area_id": "3", + "name": "武装突袭", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/0e8c782aa4f5c1858e3ea3e6a9b2398000543cc6.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "773", + "parent_id": "2", + "old_area_id": "3", + "name": "Wayfinder寻路者", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/fd000584820e654ff57c8505329a465f0d9f66ee.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "84", + "parent_id": "2", + "old_area_id": "3", + "name": "300英雄", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/4f3a845e71610760a13f45b427383589677d252d.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "91", + "parent_id": "2", + "old_area_id": "3", + "name": "炉石传说", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/ce83319ab4ebc2f0c357fc101f20c785c655f9e6.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "499", + "parent_id": "2", + "old_area_id": "3", + "name": "剑网3缘起", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/im/5f38a9c0e483d880892cdd7e50c8cf8d1d268a79.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "649", + "parent_id": "2", + "old_area_id": "3", + "name": "街头篮球", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/d0620ce38a059284cdd3c9b7427636d15db67838.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "601", + "parent_id": "2", + "old_area_id": "3", + "name": "综合射击", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/be25ec368b700e8800205c5093fb0c863656cc22.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "505", + "parent_id": "2", + "old_area_id": "3", + "name": "剑灵", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/e31c6cf6efcadf66e87cddb6283a16559dde32f4.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "651", + "parent_id": "2", + "old_area_id": "3", + "name": "艾尔之光", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/1a40b6b4fae40be18f2b6092744aa3e8912e1c47.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "632", + "parent_id": "2", + "old_area_id": "3", + "name": "黑色沙漠", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/11705ec098eeeaf2b00086fa374f9fddf454307a.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "596", + "parent_id": "2", + "old_area_id": "3", + "name": "天涯明月刀", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/7fafd30eed975b13788bcc4ca83ca1abf285123f.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "519", + "parent_id": "2", + "old_area_id": "3", + "name": "超激斗梦境", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/93c5db4c56db17a4994ea41bf84f03f384ef8f1a.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "574", + "parent_id": "2", + "old_area_id": "3", + "name": "冒险岛", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/ab239c8785b1f26909cfdb24b479f7f705846c69.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "487", + "parent_id": "2", + "old_area_id": "3", + "name": "逆战", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/im/47387aad5a40c060fddf7e8df6e3facf9e06449f.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "181", + "parent_id": "2", + "old_area_id": "1", + "name": "魔兽争霸3", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/22ddd91ed6915209b92c77880a27edda79905878.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "610", + "parent_id": "2", + "old_area_id": "3", + "name": "QQ飞车", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/8ed3460df72e404616d0f351203c908407da2123.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "83", + "parent_id": "2", + "old_area_id": "3", + "name": "魔兽世界", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/1e34e9b58e639d6dc37f093d814fd431f5d6a436.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "388", + "parent_id": "2", + "old_area_id": "3", + "name": "FIFA ONLINE 4", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/9ae9378572fd513a9c67d966d6b457d15cf5aaf8.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "581", + "parent_id": "2", + "old_area_id": "1", + "name": "NBA2KOL2", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/6bddd9dc640d804429c2f8759847062d40f8ce40.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "318", + "parent_id": "2", + "old_area_id": "3", + "name": "使命召唤:战区", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/a45d767961882fab7f0ee58bab211a6c9d58d85b.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "656", + "parent_id": "2", + "old_area_id": "3", + "name": "VRChat", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/20b48a1e81da9043f8af5badfa7064fc531f9111.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "115", + "parent_id": "2", + "old_area_id": "3", + "name": "坦克世界", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/49d933463353da1b872563d666765041dbbbbf6b.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "248", + "parent_id": "2", + "old_area_id": "3", + "name": "战舰世界", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/3ad588762a9d8ac4d51df1712f29144631359b7a.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "729", + "parent_id": "2", + "old_area_id": "3", + "name": "战争与抉择", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/6928fd3877bb1b2ad3c0a44851e673eefeb0daab.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "316", + "parent_id": "2", + "old_area_id": "3", + "name": "战争雷霆", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/0492b111de7e427adda8326c90adff9f93ffb6e8.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "383", + "parent_id": "2", + "old_area_id": "3", + "name": "战意", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/b5a88419f917c8e6ba57d27712fc2d82aae094e8.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "114", + "parent_id": "2", + "old_area_id": "4", + "name": "风暴英雄", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/94c6517e4af725fb9ba8794b045ff98ec303de6e.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "93", + "parent_id": "2", + "old_area_id": "4", + "name": "星际争霸2", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/7217538bea375fd81ec908723fbae0ab455ec693.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "239", + "parent_id": "2", + "old_area_id": "3", + "name": "刀塔自走棋", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/fd87a2b60643f55ec5e2f7c4721b4b2650ca8f0c.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "164", + "parent_id": "2", + "old_area_id": "1", + "name": "堡垒之夜", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/7e7bac7279717814c8044de820f3926b679d1b08.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "251", + "parent_id": "2", + "old_area_id": "3", + "name": "枪神纪", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/52701c80055ad0718b145d360b7624b4b318e81a.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "81", + "parent_id": "2", + "old_area_id": "3", + "name": "三国杀", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/2b0b4e2dead74194c108210003bc8e9788d2ae00.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "112", + "parent_id": "2", + "old_area_id": "3", + "name": "龙之谷", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/db5908c6fb8afb4df9b5a671afdf90c26efd6f57.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "173", + "parent_id": "2", + "old_area_id": "3", + "name": "古剑奇谭OL", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/8727f065411951ed3150c2a686de49446876511b.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "176", + "parent_id": "2", + "old_area_id": "3", + "name": "幻想全明星", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/21e8dfea68619b1dafdff88462c80b25f7c4df20.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "288", + "parent_id": "2", + "old_area_id": "3", + "name": "怀旧网游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/44860e1ef2fe1fddc931d11c5c18d6c9b2c09cac.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "298", + "parent_id": "2", + "old_area_id": "3", + "name": "新游前瞻", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/23f91e69e042ee2f2ed333b5738f74cb30cde837.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "331", + "parent_id": "2", + "old_area_id": "3", + "name": "星战前夜:晨曦", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/beeb86edc8863f34ad2f08495fb6f3a7229c4298.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "551", + "parent_id": "2", + "old_area_id": "3", + "name": "流放之路", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/641dc7563aa8e557f7e99239c4ef8250c75a198f.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "633", + "parent_id": "2", + "old_area_id": "3", + "name": "FPS沙盒", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/27083cc692ec3e7d33fe633d205484aace034a20.jpg", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "459", + "parent_id": "2", + "old_area_id": "3", + "name": "永恒轮回", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/im/ed830eb1738796c2503f3590a3cb27cd05ee6ac4.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "607", + "parent_id": "2", + "old_area_id": "3", + "name": "激战2", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/b09afa81402646c0d53b921acb4ac120affba9c8.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "107", + "parent_id": "2", + "old_area_id": "1", + "name": "其他网游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/336fdcc2afc0d8ed0b98809802fafb4e6d41db71.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + }, + { + "id": "760", + "parent_id": "2", + "old_area_id": "3", + "name": "蓝色协议", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/e7ab7df8c4910b661c20d6141f66df119c2c116f.png", + "complex_area_name": "", + "parent_name": "网游", + "area_type": 0 + } + ] + }, + { + "id": 3, + "name": "手游", + "list": [ + { + "id": "35", + "parent_id": "3", + "old_area_id": "12", + "name": "王者荣耀", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/0fefa924760b2dd492a12dddafe179bfa1216918.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "256", + "parent_id": "3", + "old_area_id": "12", + "name": "和平精英", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/im/00050c896d29c5ae85ecadb541302f41ff35ea48.jpg", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "321", + "parent_id": "3", + "old_area_id": "12", + "name": "原神", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/a7c0b5834c9861a4e5cd58f84e8b125fa9760876.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "549", + "parent_id": "3", + "old_area_id": "12", + "name": "崩坏:星穹铁道", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/526b0b887b27786a19c7ad7283c6afa3fd92f348.jpg", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "163", + "parent_id": "3", + "old_area_id": "12", + "name": "第五人格", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/76dba96e0766ac390d49b4aee5458a20e6f61226.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "395", + "parent_id": "3", + "old_area_id": "12", + "name": "LOL手游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/305848ff1b644b820c0c6c18c83c4808e4cde0c7.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "255", + "parent_id": "3", + "old_area_id": "12", + "name": "明日方舟", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/5dbd486433f33efabde53be38a7e2eff3ce09883.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "479", + "parent_id": "3", + "old_area_id": "12", + "name": "黎明觉醒:生机", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/c7a440696ea9e357b360aa706bba234da644cdd4.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "571", + "parent_id": "3", + "old_area_id": "12", + "name": "蛋仔派对", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/856ae2720586e80b939a062a0b5afcee349ee783.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "796", + "parent_id": "3", + "old_area_id": "12", + "name": "冒险岛手游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/65522fcd40917d481738a6b2c915c18520f228a3.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "805", + "parent_id": "3", + "old_area_id": "12", + "name": "闪耀!优俊少女", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/b1adcde4e223e3a4846bcc92b23c52f2d5ec38ed.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "797", + "parent_id": "3", + "old_area_id": "12", + "name": "斯露德", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/e6f13027d569f9bd037cf4585fa272e4b52cfa8d.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "806", + "parent_id": "3", + "old_area_id": "12", + "name": "千年之旅", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/91fd0422f0d354af67c97445d23fa2aa645ae716.jpg", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "781", + "parent_id": "3", + "old_area_id": "12", + "name": "白夜极光", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/a73785f8bceed89db98139804512eb3100c6d9a1.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "771", + "parent_id": "3", + "old_area_id": "12", + "name": "逆水寒手游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/f176df2fb306c166c31c483616ba899bda789dc8.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "807", + "parent_id": "3", + "old_area_id": "12", + "name": "率土之滨", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/8ef2049985fbdc717e5ad7025b434ea28beb7472.jpg", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "215", + "parent_id": "3", + "old_area_id": "12", + "name": "月圆之夜", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/3802030a4ebc11f1ec15f24bfd6a647e6d587e0e.jpg", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "474", + "parent_id": "3", + "old_area_id": "12", + "name": "哈利波特:魔法觉醒", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/d38e792754f0afb851f844e3e973d17345e7dc24.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "550", + "parent_id": "3", + "old_area_id": "12", + "name": "幻塔", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/e6840f7dbb6548f3f23a24d62603c77f606c4390.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "799", + "parent_id": "3", + "old_area_id": "12", + "name": "星之破晓", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/c01313b64497231b96c43fcdc6ec5dcf62dfb9af.jpg", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "514", + "parent_id": "3", + "old_area_id": "12", + "name": "金铲铲之战", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/4c3e9a1b5663ce75f9418e1458a564e591a6d770.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "598", + "parent_id": "3", + "old_area_id": "12", + "name": "深空之眼", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/f621d5294b16c0440ed731a47fb3ad552c3a1fae.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "675", + "parent_id": "3", + "old_area_id": "12", + "name": "无期迷途", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/341db928251b2471ea9a411739b88d8f67646c01.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "777", + "parent_id": "3", + "old_area_id": "12", + "name": "晶核", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/8b934c849ebc053866f3526232d902ac200a24af.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "687", + "parent_id": "3", + "old_area_id": "12", + "name": "光遇", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/7a87708bd374a12377afbb8813bbbac65123ae91.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "792", + "parent_id": "3", + "old_area_id": "12", + "name": "桃源深处有人家", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/6fa1bfa095fa5b1a84b2047886e9f3e116dd6a32.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "756", + "parent_id": "3", + "old_area_id": "12", + "name": "三国志战棋版", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/a018e8921b50335751a23971f11173d03a29fd2f.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "765", + "parent_id": "3", + "old_area_id": "12", + "name": "战火勋章", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/283aac879c9d2a18c028868eca0ec4ecaf603c5d.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "755", + "parent_id": "3", + "old_area_id": "12", + "name": "以闪亮之名", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/af9a1f7100d177cb6139e712c7ba1a56482cd72f.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "778", + "parent_id": "3", + "old_area_id": "12", + "name": "尘白禁区", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/277a326708a16ae2d370de9aef8ed0d64df805fb.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "759", + "parent_id": "3", + "old_area_id": "12", + "name": "古魂", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/c2e94f8d2d68d887bd181be822d784ac9a4f671f.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "470", + "parent_id": "3", + "old_area_id": "12", + "name": "鬼泣-巅峰之战", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/cce74f7d111a8ed972b2a7742d9b7cdfddeeeaa0.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "762", + "parent_id": "3", + "old_area_id": "12", + "name": "奇点时代", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/7d135fd3516f1ce1e8099dc51f7c52bfaa007f61.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "689", + "parent_id": "3", + "old_area_id": "12", + "name": "香肠派对", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/a2a60d6b5635f86c24c4033811ac38a7ace13fc6.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "645", + "parent_id": "3", + "old_area_id": "12", + "name": "猫之城", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/f843404c1b1e505272d9070a7c0a4017ce846b03.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "783", + "parent_id": "3", + "old_area_id": "12", + "name": "高能英雄", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/8e0ec60364e5013e4564ab06556b46e6682b3332.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "386", + "parent_id": "3", + "old_area_id": "12", + "name": "使命召唤手游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/a0037e4934fc33fce8cb9fe046d67247cb1c244f.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "40", + "parent_id": "3", + "old_area_id": "12", + "name": "崩坏3", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/242c92ee54d374e379820521353d9ae6199be7fc.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "793", + "parent_id": "3", + "old_area_id": "12", + "name": "银河境界线", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/164c19a67a6cadcf8106b10af06e31f70da8dc00.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "787", + "parent_id": "3", + "old_area_id": "12", + "name": "蔚蓝档案", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/d20380b9b6d66725aba33f2d67fb0cb78bfc98b4.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "784", + "parent_id": "3", + "old_area_id": "12", + "name": "第七史诗", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/013c93e5ba446e21d540f8e050f6f947911f7039.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "407", + "parent_id": "3", + "old_area_id": "12", + "name": "游戏王:决斗链接", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/32b8a3d8aa377754b90dda33d9e50b008121319c.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "303", + "parent_id": "3", + "old_area_id": "12", + "name": "游戏王", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/d2437f26b4617b90b38f1db4a7e622a17875eb04.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "724", + "parent_id": "3", + "old_area_id": "12", + "name": "JJ斗地主", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/faf5ef16a0b6508fa9b67970a3ea29e14c95185e.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "36", + "parent_id": "3", + "old_area_id": "12", + "name": "阴阳师", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/4326e09f1bbc98aa9a6f06c11bd24f428ac21c9b.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "719", + "parent_id": "3", + "old_area_id": "12", + "name": "欢乐斗地主", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/704e9116bacc7b6eb4d821db3ad6605672c82dbc.jpg", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "791", + "parent_id": "3", + "old_area_id": "12", + "name": "太空行动", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/d60f73b982e65afd94fd1803d88c36999bce6fc5.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "718", + "parent_id": "3", + "old_area_id": "12", + "name": "空之要塞:启航", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/ea5131d8a349bb8ad24a5325644f78179f3d5c0d.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "738", + "parent_id": "3", + "old_area_id": "12", + "name": "长安幻想", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/e227f9a6f33935af4c82da706c0d469f14abd40c.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "292", + "parent_id": "3", + "old_area_id": "12", + "name": "火影忍者手游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/f2f30d97710a2f017efe8a0ad9a8b55aac82ca1f.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "37", + "parent_id": "3", + "old_area_id": "12", + "name": "Fate/GO", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/d2fb77d1c95bbaf089535ea35af686bed4a901c4.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "354", + "parent_id": "3", + "old_area_id": "12", + "name": "综合棋牌", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/db377b05357e0ba56a0e1cd233e8f6a3769e1014.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "154", + "parent_id": "3", + "old_area_id": "12", + "name": "QQ飞车手游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/44e7d226ee3547da7662f0b33516b21fe833146a.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "140", + "parent_id": "3", + "old_area_id": "12", + "name": "决战!平安京", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/3ea2ad8569e0508f5de1a176b20e4c313b119e8a.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "41", + "parent_id": "3", + "old_area_id": "12", + "name": "狼人杀", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/c544e3c5ce0fe6b31ddd079784450f518e16b643.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "352", + "parent_id": "3", + "old_area_id": "12", + "name": "三国杀移动版", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/4be870cc667373af0899fbe55e40f273a905fad5.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "113", + "parent_id": "3", + "old_area_id": "12", + "name": "碧蓝航线", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/dae8e374d6542f231b10c75d77df977d884f83f3.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "156", + "parent_id": "3", + "old_area_id": "12", + "name": "影之诗", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/d9896cdaf349e6862ec13b464ecb1d8919bdf6f4.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "189", + "parent_id": "3", + "old_area_id": "12", + "name": "明日之后", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/1af368a5e619a48dd265becb94c349d56ad666c0.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "761", + "parent_id": "3", + "old_area_id": "12", + "name": "重返未来:1999", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/0ed147f627a6117206ff14f23349c86b0eb30fc0.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "786", + "parent_id": "3", + "old_area_id": "12", + "name": "环行旅舍", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/ceacb6007de1e2299ca5eda1e40fd740c57835ee.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "50", + "parent_id": "3", + "old_area_id": "12", + "name": "部落冲突:皇室战争", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/72579725074ab8687cd5f6af97eaf1c2d880b9ff.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "661", + "parent_id": "3", + "old_area_id": "12", + "name": "奥比岛手游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/750ed66b9c626a149edbc6a44adc6a5316298b3a.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "734", + "parent_id": "3", + "old_area_id": "12", + "name": "弹弹堂", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/1626a5f665af50f21780d038ac6f1ace40979dd6.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "214", + "parent_id": "3", + "old_area_id": "12", + "name": "雀姬", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/5077330ea76a5f89b0e21af4541bcecff4d5964c.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "330", + "parent_id": "3", + "old_area_id": "12", + "name": "公主连结Re:Dive", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/48a89d7de3bdbcc3b7cf345e83df389cfa5829ea.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "343", + "parent_id": "3", + "old_area_id": "12", + "name": "DNF手游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/35e64556c4c6e75cd67396dfafde5c6ad475d12a.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "641", + "parent_id": "3", + "old_area_id": "12", + "name": "FIFA足球世界", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/9f1c0e604bfce8a4393235a14af1eed336429107.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "258", + "parent_id": "3", + "old_area_id": "12", + "name": "BanG Dream", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/e470bf425032d9e2f46ed446a07321855e638a48.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "469", + "parent_id": "3", + "old_area_id": "12", + "name": "荒野乱斗", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/im/ace7831f82003e06af4d2ffd86f43e9b44959c91.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "333", + "parent_id": "3", + "old_area_id": "12", + "name": "CF手游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/ea379df638d043df26d61f91ebf84e2b0b19746e.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "293", + "parent_id": "3", + "old_area_id": "12", + "name": "战双帕弥什", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/b1c6fe3845b274df732d8f3a582e4113c6ef1036.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "389", + "parent_id": "3", + "old_area_id": "12", + "name": "天涯明月刀手游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/a8d84300b25a006c6989aba123e29fd3fb5b4484.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "779", + "parent_id": "3", + "old_area_id": "12", + "name": "一拳超人:世界", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/2594c3968c5cc74a8ef07f73efe6f166ab773d09.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "42", + "parent_id": "3", + "old_area_id": "12", + "name": "解密游戏", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/7d1ff3d2a4d752d6eadd69d39bcd6d5fc1672218.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "576", + "parent_id": "3", + "old_area_id": "12", + "name": "恋爱养成游戏", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/dc4beb0b5a3e1b45d616fc846fc2b32b0d8cd447.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "492", + "parent_id": "3", + "old_area_id": "12", + "name": "暗黑破坏神:不朽", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/im/fe00f4c949fd938aafeb586588b1b335ed7f4f40.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "502", + "parent_id": "3", + "old_area_id": "12", + "name": "暗区突围", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/6d61c36eb480189ae1db35976e0989ea85b7fab2.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "265", + "parent_id": "3", + "old_area_id": "12", + "name": "跑跑卡丁车手游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/2355482b284f2805ce222cf8e30d6e890fd403dc.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "212", + "parent_id": "3", + "old_area_id": "12", + "name": "非人学园", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/cebf58d684d2c63825a2129b062f31b6236abdb6.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "286", + "parent_id": "3", + "old_area_id": "12", + "name": "百闻牌", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/1164d0d1d2fd3cfd57bc3fc3d02fdc845a457c42.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "269", + "parent_id": "3", + "old_area_id": "12", + "name": "猫和老鼠手游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/1608107c14864384b9f569185ad5c804433e04a3.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "442", + "parent_id": "3", + "old_area_id": "12", + "name": "坎公骑冠剑", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/ab60f4b0442c67f06e29dd84dc7d32889352b30b.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "203", + "parent_id": "3", + "old_area_id": "12", + "name": "忍者必须死3", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/c5e9a31158282765b017e5166dff5460f73be992.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "342", + "parent_id": "3", + "old_area_id": "12", + "name": "梦幻西游手游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/91d5e60dbd60ee8f84b0fc62baa23550c012fdf1.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "504", + "parent_id": "3", + "old_area_id": "12", + "name": "航海王热血航线", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/im/0a4d0191a1715f95696bd7ae890dff9021884cd6.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "39", + "parent_id": "3", + "old_area_id": "12", + "name": "少女前线", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/1ebf500f1875f62c3d2b0c4bd077c3ef2614fc67.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "688", + "parent_id": "3", + "old_area_id": "12", + "name": "300大作战", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/67be562957ba11421ca87102396bb3cd255e1999.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "525", + "parent_id": "3", + "old_area_id": "12", + "name": "少女前线:云图计划", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/5d7be109eaaf6e8ddc0ad5b694ef7b75b5032562.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "478", + "parent_id": "3", + "old_area_id": "12", + "name": "漫威超级战争", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/im/90478b016d41f6cf4fb6c00298d2f1cef3eb6591.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "464", + "parent_id": "3", + "old_area_id": "12", + "name": "摩尔庄园手游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/3cdfa8741823c82a355c6a2a8c2b788eb2e1e058.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "493", + "parent_id": "3", + "old_area_id": "12", + "name": "宝可梦大集结", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/im/b15cf67865399226ac699b15fb7de38c1509c7ee.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "473", + "parent_id": "3", + "old_area_id": "12", + "name": "小动物之星", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/3721bbddab86bc55e6a7bcf29a81ca67d2ab9b8a.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "448", + "parent_id": "3", + "old_area_id": "12", + "name": "天地劫:幽城再临", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/80da3d72711039446e500d581d01b233eb341805.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "511", + "parent_id": "3", + "old_area_id": "12", + "name": "漫威对决", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/09c63a927ad2c3b1ce506b2acc56a8b5f60039c5.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "538", + "parent_id": "3", + "old_area_id": "12", + "name": "东方归言录", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/73f2a51015fbdea62ca81241309eeaacc502c0b1.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "178", + "parent_id": "3", + "old_area_id": "12", + "name": "梦幻模拟战", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/a080f3afa82bfcebc6a2d0db9b805c77ef232964.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "643", + "parent_id": "3", + "old_area_id": "12", + "name": "时空猎人3", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/e492b3b83ee37efa41a12bd12fae44ca759e118b.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "613", + "parent_id": "3", + "old_area_id": "12", + "name": "重返帝国", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/4beda618eb4d354b6e95b77867f97fdbc46c2bdc.jpg", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "679", + "parent_id": "3", + "old_area_id": "12", + "name": "休闲小游戏", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/ecd77d15cac6cfd38035dc2fce4c35bba9daef86.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "98", + "parent_id": "3", + "old_area_id": "12", + "name": "其他手游", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/e31422cdef5b54f8bfbab3fb53c718ba7964229e.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + }, + { + "id": "274", + "parent_id": "3", + "old_area_id": "12", + "name": "新游评测", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/e8dcb0dd8de7cd5c320ee4ce8a7c352e9d32a97a.png", + "complex_area_name": "", + "parent_name": "手游", + "area_type": 0 + } + ] + }, + { + "id": 6, + "name": "单机游戏", + "list": [ + { + "id": "236", + "parent_id": "6", + "old_area_id": "1", + "name": "主机游戏", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/edb636ee59f902e3134a2790545045bddd70978e.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "216", + "parent_id": "6", + "old_area_id": "1", + "name": "我的世界", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/a5a2abb10cd6e06e41c9447ffb10fbd7bdf6b2d6.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "283", + "parent_id": "6", + "old_area_id": "1", + "name": "独立游戏", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/5b4e700a4f91c55c91e791134cd46559fd903120.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "237", + "parent_id": "6", + "old_area_id": "1", + "name": "怀旧游戏", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/58ec5c5a909958ebef68b1e8762f63f502a68558.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "460", + "parent_id": "6", + "old_area_id": "1", + "name": "互动玩法", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/im/321fe8138b85f38a19f9c18d7be2c7383ee8f8cb.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "808", + "parent_id": "6", + "old_area_id": "1", + "name": "星空", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/5a264f0cdc487f47c270b267b3da4ec98ddeb402.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "308", + "parent_id": "6", + "old_area_id": "1", + "name": "塞尔达传说", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/ce88445ce681501f9c38c6e046a98b4512180ca4.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "384", + "parent_id": "6", + "old_area_id": "1", + "name": "猛兽派对", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/17137532386ab63c6a00e3da954c071c6b3720c3.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "798", + "parent_id": "6", + "old_area_id": "1", + "name": "苍翼:混沌效应", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/8f74bb08f44ca2304f4600e448aac2c19a670d4b.jpg", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "277", + "parent_id": "6", + "old_area_id": "1", + "name": "命运2", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/54c8751b55d6abc4f504b18538373f2716ff5585.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "800", + "parent_id": "6", + "old_area_id": "12", + "name": "机战佣兵VI 境界天火", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/674a734ce5a5aac206afad2a40f2b5c4f6c0ac95.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "780", + "parent_id": "6", + "old_area_id": "1", + "name": "暗黑破坏神Ⅳ", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/ecc1df46c6961f695966c317e83c7d14740264ec.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "385", + "parent_id": "6", + "old_area_id": "1", + "name": "博德之门3", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/6d56e618566f6140cdd0fabe9d398027da77450b.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "801", + "parent_id": "6", + "old_area_id": "1", + "name": "绝世好武功", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/6d9d021425c4afea900874b6e859cc42400d4a3b.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "276", + "parent_id": "6", + "old_area_id": "1", + "name": "恐怖游戏", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/ae6d71bb82cf28e721352132c7e862b53dd8af01.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "795", + "parent_id": "6", + "old_area_id": "1", + "name": "Dark and Darker", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/f1323529559feaa75f138d451f5022bb8b746474.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "785", + "parent_id": "6", + "old_area_id": "1", + "name": "Warlander", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/beeade71ac7b2646e10b40dc43321ceae231f33c.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "302", + "parent_id": "6", + "old_area_id": "1", + "name": "FORZA 极限竞速", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/baf1fb935016db883e32bc7e9be2d49e769ca35f.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "794", + "parent_id": "6", + "old_area_id": "1", + "name": "学园构想家", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/3ff5f658c790aa879a02c4f7c5050a66808e89bb.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "763", + "parent_id": "6", + "old_area_id": "1", + "name": "边境", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/de628c0fe4e28b0f83f0fb7c0fb02f3677f9f7a9.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "721", + "parent_id": "6", + "old_area_id": "1", + "name": "生化危机", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/f62c0597504418107dcc2bb2942d40b73d944faa.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "328", + "parent_id": "6", + "old_area_id": "1", + "name": "最终幻想", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/d8609f83fea8d722f9fd74addfe611684e1a675c.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "535", + "parent_id": "6", + "old_area_id": "1", + "name": "暗黑破坏神", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/cd63a76651834f2ef53115727abb86bdbac71222.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "751", + "parent_id": "6", + "old_area_id": "1", + "name": "森林之子", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/adcd20072f4d6ec01721ae4f163a19958e9cc8de.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "700", + "parent_id": "6", + "old_area_id": "1", + "name": "卧龙:苍天陨落", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/f5b0e66ea4d580aa829c01bd675f139962918e54.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "693", + "parent_id": "6", + "old_area_id": "1", + "name": "红色警戒2", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/3515dc05ff44427b0e3f01a7a7655ce82f5a0544.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "570", + "parent_id": "6", + "old_area_id": "1", + "name": "策略游戏", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/dbacc3268565e8c8f612b28dab9aedb797d2e0bc.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "707", + "parent_id": "6", + "old_area_id": "1", + "name": "禁闭求生", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/3a11807878be0d14cbccc42ada748260f282c079.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "579", + "parent_id": "6", + "old_area_id": "1", + "name": "战神", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/2994a3abe6d20c419e26fffdae76b132c3b3c511.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "694", + "parent_id": "6", + "old_area_id": "1", + "name": "斯普拉遁3", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/a17ce364e965108719d59240e48f045d293ecbf0.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "282", + "parent_id": "6", + "old_area_id": "1", + "name": "使命召唤19", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/eeb04e983aa4e432073fb60367b170deb3cf642d.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "555", + "parent_id": "6", + "old_area_id": "1", + "name": "艾尔登法环", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/03463203553cd149f39425d37a812f3df0b79846.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "636", + "parent_id": "6", + "old_area_id": "1", + "name": "聚会游戏", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/bcfff565f1720b991e1346c722646147398b90c3.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "591", + "parent_id": "6", + "old_area_id": "1", + "name": "Dread Hunger", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/ec01260d4ac3c065f7081d912f9abcbf4e549489.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "597", + "parent_id": "6", + "old_area_id": "1", + "name": "战地风云", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/b1e405cf5b1d106110504ae98d4f9c4e8a939718.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "357", + "parent_id": "6", + "old_area_id": "1", + "name": "糖豆人", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/74521f63cbe6a85ebdb3e1284d3dcb40a96af1af.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "586", + "parent_id": "6", + "old_area_id": "1", + "name": "消逝的光芒2", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/ecb583b5907ff2a2d4b3466d2a58d1627d65e0c2.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "245", + "parent_id": "6", + "old_area_id": "1", + "name": "只狼", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/50791497a4dc7af351e4e002f2e6025cf33ca6fe.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "578", + "parent_id": "6", + "old_area_id": "1", + "name": "怪物猎人", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/77d02004ab852c2ca52adf1ac8e2301423b876bd.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "720", + "parent_id": "6", + "old_area_id": "1", + "name": "宝可梦集换式卡牌游戏", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/5add6ce400d360b406d2413bec7eef3ba160fc3e.jpg", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "218", + "parent_id": "6", + "old_area_id": "1", + "name": "饥荒", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/277cb921c60d84e75f78d0298e3aef5f4d297868.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "228", + "parent_id": "6", + "old_area_id": "1", + "name": "精灵宝可梦", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/c8363817908aa1b50f3f3a386f3feff7b8e0e1bf.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "708", + "parent_id": "6", + "old_area_id": "1", + "name": "FIFA23", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/deae9e3c11360860ef0961c6af586c91f5cb1281.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "594", + "parent_id": "6", + "old_area_id": "1", + "name": "全面战争:战锤3", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/1fc7f1af56931884b6a6a2b2af845afcbf2da2ce.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "362", + "parent_id": "6", + "old_area_id": "1", + "name": "NBA2K", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/07d6278e7bb46e03aa05113ce3d9ff3e746d2ba1.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "548", + "parent_id": "6", + "old_area_id": "1", + "name": "帝国时代4", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/a5a5ace4b9708b8bb0d24cb69fe4a44f825af832.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "309", + "parent_id": "6", + "old_area_id": "1", + "name": "植物大战僵尸", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/eea9579e4753a5f69792f9f3b211810eff852893.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "433", + "parent_id": "6", + "old_area_id": "1", + "name": "格斗游戏", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/im/d8f6c4a02a73d6603c2374f118a31e4f91ff95ea.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "226", + "parent_id": "6", + "old_area_id": "1", + "name": "荒野大镖客2", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/b7e12939b9ff865fc478283b4fcedb971e5b0092.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "426", + "parent_id": "6", + "old_area_id": "1", + "name": "重生细胞", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/328c2d573f70f0f09a2c4e8d187606ea189215a6.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "227", + "parent_id": "6", + "old_area_id": "1", + "name": "刺客信条", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/058005faf068289daeb9f3ef812e52e733a3b3d9.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "387", + "parent_id": "6", + "old_area_id": "1", + "name": "恐鬼症", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/f824e1329ed307b6478488bc38d1a47528aa322c.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "219", + "parent_id": "6", + "old_area_id": "1", + "name": "以撒", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/8d9b86bd7d4c6faf85231bda5367280f523652c4.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "446", + "parent_id": "6", + "old_area_id": "1", + "name": "双人成行", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/d054451d10be07b87c356bd8eb8e9ae8a94a15c9.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "295", + "parent_id": "6", + "old_area_id": "1", + "name": "方舟", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/ca9ef58b15fc4dcd3a9ff06a51a638996f67d2dd.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "313", + "parent_id": "6", + "old_area_id": "1", + "name": "仁王2", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/089daa0c9da8e6a53996724db371e9b3c6efc3b8.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "244", + "parent_id": "6", + "old_area_id": "1", + "name": "鬼泣5", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/414ae4211a774d8fa055771e3d737b759d8af4dd.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "364", + "parent_id": "6", + "old_area_id": "1", + "name": "枪火重生", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/413653e02c33bd478fda2b0c7408b8ef4b3f4c35.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "341", + "parent_id": "6", + "old_area_id": "1", + "name": "盗贼之海", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/0fc24abefc0a461a3aa1bb320bff166bfa442c54.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "507", + "parent_id": "6", + "old_area_id": "1", + "name": "胡闹厨房", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/im/41334b99052111ab16883c144052baece5dae835.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "500", + "parent_id": "6", + "old_area_id": "1", + "name": "体育游戏", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/im/8c241fa1c7f5dc872b6d0615bdefb622563878ba.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "243", + "parent_id": "6", + "old_area_id": "1", + "name": "全境封锁2", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/d9eef591fc7b711d97c4fb8b29bd804967368996.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "326", + "parent_id": "6", + "old_area_id": "1", + "name": "骑马与砍杀", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/4185dad552d1110b7821506da1b6747affacb611.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "270", + "parent_id": "6", + "old_area_id": "1", + "name": "人类一败涂地", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/a29b9a2130b10b91f3c8a3d8e28d9166f95e73d1.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "273", + "parent_id": "6", + "old_area_id": "1", + "name": "无主之地3", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/973d2fe12c771207d49f6dff1440f73d153aa2b2.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "220", + "parent_id": "6", + "old_area_id": "1", + "name": "辐射76", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/4a5d2005a71de50dad4700c680e3c51e5b3d8b9f.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "257", + "parent_id": "6", + "old_area_id": "1", + "name": "全面战争", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/466b6b816600ed6c63f8f462d074311578618ebb.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "583", + "parent_id": "6", + "old_area_id": "1", + "name": "文字游戏", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/f50cebc0a767692c0152771166b344f22f3e360b.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "592", + "parent_id": "6", + "old_area_id": "1", + "name": "恋爱模拟游戏", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/4be6401f72d31f91f1fd3325c9a4616998f14d5d.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "593", + "parent_id": "6", + "old_area_id": "1", + "name": "泰拉瑞亚", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/ff37eb0c1a7ace5cecbfc0dee5c701809d801746.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "678", + "parent_id": "6", + "old_area_id": "1", + "name": "游戏速通", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/d674a4bd4d7f137ac152671fba5df83bb807daec.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "753", + "parent_id": "6", + "old_area_id": "1", + "name": "Roblox", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/c897afe14b5f3b8a5d08fa342c6b28b039f73c0d.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "803", + "parent_id": "6", + "old_area_id": "1", + "name": "雀魂麻将", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/1a7b294bc9c7ba1f4215ce1aeb0a32b089cefbbb.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + }, + { + "id": "235", + "parent_id": "6", + "old_area_id": "1", + "name": "其他单机", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/5baf148ef7da6c4b3943282a6e89daa794984067.png", + "complex_area_name": "", + "parent_name": "单机游戏", + "area_type": 0 + } + ] + }, + { + "id": 1, + "name": "娱乐", + "list": [ + { + "id": "740", + "parent_id": "1", + "old_area_id": "6", + "name": "聊天室", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/fe112b439e34d4ef3e254c00a9c0d3bd998043bd.png", + "complex_area_name": "", + "parent_name": "娱乐", + "area_type": 0 + }, + { + "id": "21", + "parent_id": "1", + "old_area_id": "10", + "name": "视频唱见", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/72b93ddafdf63c9f0b626ad546847a3c03c92b6f.png", + "complex_area_name": "視頻唱見", + "cate_id": "12", + "parent_name": "娱乐", + "area_type": 0 + }, + { + "id": "530", + "parent_id": "1", + "old_area_id": "6", + "name": "萌宅领域", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/5ea1fc721896f02eef1d5a712cd0bb151d1b8ca5.png", + "complex_area_name": "", + "parent_name": "娱乐", + "area_type": 0 + }, + { + "id": "145", + "parent_id": "1", + "old_area_id": "6", + "name": "视频聊天", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/14a8c9c6d0a7685091db270cb523690b9e78b523.png", + "complex_area_name": "", + "cate_id": "2", + "parent_name": "娱乐", + "area_type": 0 + }, + { + "id": "207", + "parent_id": "1", + "old_area_id": "10", + "name": "舞见", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/5837fa9608fab6c1465ec29c5abecab44f7bc376.png", + "complex_area_name": "", + "parent_name": "娱乐", + "area_type": 0 + } + ] + }, + { + "id": 5, + "name": "电台", + "list": [ + { + "id": "190", + "parent_id": "5", + "old_area_id": "10", + "name": "唱见电台", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/d22d7fafbf9b24e2bc3ce1df5eb9f006e6035e5d.png", + "complex_area_name": "", + "parent_name": "电台", + "area_type": 0 + }, + { + "id": "192", + "parent_id": "5", + "old_area_id": "6", + "name": "聊天电台", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/a95b25ebec1e009870c2e3dd823e8cae50fa5223.png", + "complex_area_name": "", + "parent_name": "电台", + "area_type": 0 + }, + { + "id": "193", + "parent_id": "5", + "old_area_id": "2", + "name": "配音", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/b3197172e0309103233ac2a333156f2304ab6478.png", + "complex_area_name": "", + "parent_name": "电台", + "area_type": 0 + } + ] + }, + { + "id": 9, + "name": "虚拟主播", + "list": [ + { + "id": "743", + "parent_id": "9", + "old_area_id": "6", + "name": "TopStar", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "1", + "pic": "https://i0.hdslb.com/bfs/live/339eaff3c8c72aa7dde28c0a3eb38d58b54d8e26.png", + "complex_area_name": "", + "parent_name": "虚拟主播", + "area_type": 0 + }, + { + "id": "744", + "parent_id": "9", + "old_area_id": "6", + "name": "虚拟Singer", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/e4fdb4e56af08128f8350d3a93f42ac1cd2223f7.png", + "complex_area_name": "", + "parent_name": "虚拟主播", + "area_type": 0 + }, + { + "id": "745", + "parent_id": "9", + "old_area_id": "6", + "name": "虚拟Gamer", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/1d9c6a2dc2185aa51c0a28a27cdfd36ca3ab76c1.png", + "complex_area_name": "", + "parent_name": "虚拟主播", + "area_type": 0 + }, + { + "id": "746", + "parent_id": "9", + "old_area_id": "6", + "name": "虚拟声优", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/0c3c2c96d8d7a13bcf6147ba4afbef93b9aea70b.png", + "complex_area_name": "", + "parent_name": "虚拟主播", + "area_type": 0 + }, + { + "id": "371", + "parent_id": "9", + "old_area_id": "6", + "name": "虚拟日常", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/cdf8c5a456de00c456bc6dede3c19569ef2c40bf.png", + "complex_area_name": "", + "parent_name": "虚拟主播", + "area_type": 0 + }, + { + "id": "789", + "parent_id": "9", + "old_area_id": "6", + "name": "虚拟APEX", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/38d830d183902937fa0ce9e469f6cf5290a4dffb.png", + "complex_area_name": "", + "parent_name": "虚拟主播", + "area_type": 0 + }, + { + "id": "775", + "parent_id": "9", + "old_area_id": "6", + "name": "虚拟PK", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/20ba135311a41425523f4c0c26e79e0de9c6f228.png", + "complex_area_name": "", + "parent_name": "虚拟主播", + "area_type": 0 + } + ] + }, + { + "id": 10, + "name": "生活", + "list": [ + { + "id": "646", + "parent_id": "10", + "old_area_id": "6", + "name": "生活分享", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/2e12bfcdd9f3680317c2a009ed7867cb2e18a222.png", + "complex_area_name": "", + "parent_name": "生活", + "area_type": 0 + }, + { + "id": "368", + "parent_id": "10", + "old_area_id": "6", + "name": "户外", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/a97daf9b1c6d16900495fab1237d8218667920c1.png", + "complex_area_name": "", + "parent_name": "生活", + "area_type": 0 + }, + { + "id": "810", + "parent_id": "10", + "old_area_id": "6", + "name": "日常", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/ba0e62a5411db65c2c24f2e38583fdbc8fb4a611.png", + "complex_area_name": "", + "parent_name": "生活", + "area_type": 0 + }, + { + "id": "623", + "parent_id": "10", + "old_area_id": "6", + "name": "情感", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/801695715c6bba03a5cb420130009a5e32fe1005.png", + "complex_area_name": "", + "parent_name": "生活", + "area_type": 0 + }, + { + "id": "628", + "parent_id": "10", + "old_area_id": "6", + "name": "运动", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/3ac39fd658fa60aa3153be392ed18cb0f7758953.png", + "complex_area_name": "", + "parent_name": "生活", + "area_type": 0 + }, + { + "id": "624", + "parent_id": "10", + "old_area_id": "6", + "name": "搞笑", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/5fc583762598d15d03749cc34fb5e8c735d8a7a2.png", + "complex_area_name": "", + "parent_name": "生活", + "area_type": 0 + }, + { + "id": "627", + "parent_id": "10", + "old_area_id": "6", + "name": "手工绘画", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/12ce34a3159e8c4ffead6c00280840e21f4233c1.png", + "complex_area_name": "", + "parent_name": "生活", + "area_type": 0 + }, + { + "id": "369", + "parent_id": "10", + "old_area_id": "6", + "name": "萌宠", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/b6814fc82bf62dd915e2b284e890770969c8ccd5.png", + "complex_area_name": "", + "parent_name": "生活", + "area_type": 0 + }, + { + "id": "367", + "parent_id": "10", + "old_area_id": "6", + "name": "美食", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/a3580fcae212085cb2950b82b590caeaebedda81.png", + "complex_area_name": "", + "parent_name": "生活", + "area_type": 0 + }, + { + "id": "378", + "parent_id": "10", + "old_area_id": "6", + "name": "时尚", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/05f60acaf542df41c406b00887a5fd448358d67a.png", + "complex_area_name": "", + "parent_name": "生活", + "area_type": 0 + } + ] + }, + { + "id": 11, + "name": "知识", + "list": [ + { + "id": "376", + "parent_id": "11", + "old_area_id": "6", + "name": "社科法律心理", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/af0b081acf0c8683cf69bda8cc4fafe891a3335a.png", + "complex_area_name": "", + "parent_name": "知识", + "area_type": 0 + }, + { + "id": "702", + "parent_id": "11", + "old_area_id": "6", + "name": "人文历史", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/daca5556d70a19771884818ef47525d701105712.png", + "complex_area_name": "", + "parent_name": "知识", + "area_type": 0 + }, + { + "id": "372", + "parent_id": "11", + "old_area_id": "6", + "name": "校园学习", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/eda3832d89020495e7724774845f47b59579d75f.png", + "complex_area_name": "", + "parent_name": "知识", + "area_type": 0 + }, + { + "id": "377", + "parent_id": "11", + "old_area_id": "6", + "name": "职场·技能", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/96c2911420b55b709ac2e12fec177a2dec992d6b.png", + "complex_area_name": "", + "parent_name": "知识", + "area_type": 0 + }, + { + "id": "375", + "parent_id": "11", + "old_area_id": "6", + "name": "科技", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/vc/97e9f50f3a5e1af7117daac82ef7117c6bc33ee7.png", + "complex_area_name": "", + "parent_name": "知识", + "area_type": 0 + }, + { + "id": "701", + "parent_id": "11", + "old_area_id": "6", + "name": "科学科普", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "https://i0.hdslb.com/bfs/live/5b3c9fdebe94e2563062b751fceae066ac908342.png", + "complex_area_name": "", + "parent_name": "知识", + "area_type": 0 + }, + { + "id": "715", + "parent_id": "11", + "old_area_id": "6", + "name": "时政", + "act_id": "0", + "pk_status": "0", + "hot_status": 0, + "lock_status": "1", + "pic": "https://i0.hdslb.com/bfs/live/b24b34e3970826a62e5fa8644ce704e293a4e2f2.png", + "complex_area_name": "", + "parent_name": "知识", + "area_type": 0 + } + ] + }, + { + "id": 13, + "name": "赛事", + "list": [ + { + "id": "561", + "parent_id": "13", + "old_area_id": "12", + "name": "游戏赛事", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "1", + "pic": "http://message.biliimg.com/bfs/im/696c699d93936ddbf67cbff3004f7198abcef69a.png", + "complex_area_name": "", + "parent_name": "赛事", + "area_type": 0 + }, + { + "id": "562", + "parent_id": "13", + "old_area_id": "12", + "name": "体育赛事", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/148d35c21c1c0f034fd1247920f0e55e21f43d45.png", + "complex_area_name": "", + "parent_name": "赛事", + "area_type": 0 + }, + { + "id": "563", + "parent_id": "13", + "old_area_id": "12", + "name": "赛事综合", + "act_id": "0", + "pk_status": "1", + "hot_status": 0, + "lock_status": "0", + "pic": "http://message.biliimg.com/bfs/im/e7bdaee17440bcbbfaae898bcdb7bc3481adc5dd.png", + "complex_area_name": "", + "parent_name": "赛事", + "area_type": 0 + } + ] + } +] \ No newline at end of file diff --git a/bilibili_api/data/subtitle_lan.json b/bilibili_api/data/subtitle_lan.json new file mode 100644 index 0000000000000000000000000000000000000000..2c506389dc13cd220fddbb344c00f3f242efe045 --- /dev/null +++ b/bilibili_api/data/subtitle_lan.json @@ -0,0 +1,746 @@ +[ + { + "lan": "zh-CN", + "doc_zh": "中文(中国)" + }, + { + "lan": "zh-HK", + "doc_zh": "中文(中国香港)" + }, + { + "lan": "zh-Hans", + "doc_zh": "中文(简体)" + }, + { + "lan": "zh-TW", + "doc_zh": "中文(中国台湾)" + }, + { + "lan": "zh-Hant", + "doc_zh": "中文(繁体)" + }, + { + "lan": "en-US", + "doc_zh": "英语(美国)" + }, + { + "lan": "ja", + "doc_zh": "日语" + }, + { + "lan": "ko", + "doc_zh": "韩语" + }, + { + "lan": "zh-SG", + "doc_zh": "中文(新加坡)" + }, + { + "lan": "ab", + "doc_zh": "阿布哈西亚语" + }, + { + "lan": "aa", + "doc_zh": "阿法尔语" + }, + { + "lan": "af", + "doc_zh": "南非荷兰语" + }, + { + "lan": "sq", + "doc_zh": "阿尔巴尼亚语" + }, + { + "lan": "ase", + "doc_zh": "美国手语" + }, + { + "lan": "am", + "doc_zh": "阿姆哈拉语" + }, + { + "lan": "arc", + "doc_zh": "阿拉米语" + }, + { + "lan": "hy", + "doc_zh": "亚美尼亚语" + }, + { + "lan": "as", + "doc_zh": "阿萨姆语" + }, + { + "lan": "ay", + "doc_zh": "艾马拉语" + }, + { + "lan": "az", + "doc_zh": "阿塞拜疆语" + }, + { + "lan": "bn", + "doc_zh": "孟加拉语" + }, + { + "lan": "ba", + "doc_zh": "巴什基尔语" + }, + { + "lan": "eu", + "doc_zh": "巴斯克语" + }, + { + "lan": "be", + "doc_zh": "白俄罗斯语" + }, + { + "lan": "bh", + "doc_zh": "比哈尔语" + }, + { + "lan": "bi", + "doc_zh": "比斯拉马语" + }, + { + "lan": "bs", + "doc_zh": "波斯尼亚语" + }, + { + "lan": "br", + "doc_zh": "布列塔尼语" + }, + { + "lan": "bg", + "doc_zh": "保加利亚语" + }, + { + "lan": "yue", + "doc_zh": "粤语" + }, + { + "lan": "yue-HK", + "doc_zh": "粤语(中国香港)" + }, + { + "lan": "ca", + "doc_zh": "加泰罗尼亚语" + }, + { + "lan": "chr", + "doc_zh": "切罗基语" + }, + { + "lan": "cho", + "doc_zh": "乔克托语" + }, + { + "lan": "co", + "doc_zh": "科西嘉语" + }, + { + "lan": "hr", + "doc_zh": "克罗地亚语" + }, + { + "lan": "cs", + "doc_zh": "捷克语" + }, + { + "lan": "da", + "doc_zh": "丹麦语" + }, + { + "lan": "nl", + "doc_zh": "荷兰语" + }, + { + "lan": "nl-BE", + "doc_zh": "荷兰语(比利时)" + }, + { + "lan": "nl-NL", + "doc_zh": "荷兰语(荷兰)" + }, + { + "lan": "dz", + "doc_zh": "宗卡语" + }, + { + "lan": "en", + "doc_zh": "英语" + }, + { + "lan": "en-CA", + "doc_zh": "英语(加拿大)" + }, + { + "lan": "en-IE", + "doc_zh": "英语(爱尔兰)" + }, + { + "lan": "en-GB", + "doc_zh": "英语(英国)" + }, + { + "lan": "eo", + "doc_zh": "世界语" + }, + { + "lan": "et", + "doc_zh": "爱沙尼亚语" + }, + { + "lan": "fo", + "doc_zh": "法罗语" + }, + { + "lan": "fj", + "doc_zh": "斐济语" + }, + { + "lan": "fil", + "doc_zh": "菲律宾语" + }, + { + "lan": "fi", + "doc_zh": "芬兰语" + }, + { + "lan": "fr", + "doc_zh": "法语" + }, + { + "lan": "fr-BE", + "doc_zh": "法语(比利时)" + }, + { + "lan": "fr-CA", + "doc_zh": "法语(加拿大)" + }, + { + "lan": "fr-FR", + "doc_zh": "法语(法国)" + }, + { + "lan": "fr-CH", + "doc_zh": "法语(瑞士)" + }, + { + "lan": "ff", + "doc_zh": "富拉语" + }, + { + "lan": "gl", + "doc_zh": "加利西亚语" + }, + { + "lan": "ka", + "doc_zh": "格鲁吉亚语" + }, + { + "lan": "de", + "doc_zh": "德语" + }, + { + "lan": "de-AT", + "doc_zh": "德语(奥地利)" + }, + { + "lan": "de-DE", + "doc_zh": "德语(德国)" + }, + { + "lan": "de-CH", + "doc_zh": "德语(瑞士)" + }, + { + "lan": "el", + "doc_zh": "希腊语" + }, + { + "lan": "kl", + "doc_zh": "格陵兰语" + }, + { + "lan": "gn", + "doc_zh": "瓜拉尼语" + }, + { + "lan": "gu", + "doc_zh": "古吉拉特语" + }, + { + "lan": "hak", + "doc_zh": "客家语" + }, + { + "lan": "hak-TW", + "doc_zh": "客家语(中国台湾)" + }, + { + "lan": "ha", + "doc_zh": "豪萨语" + }, + { + "lan": "iw", + "doc_zh": "希伯来语" + }, + { + "lan": "hi", + "doc_zh": "印地语" + }, + { + "lan": "hi-Latn", + "doc_zh": "印地语(注音)" + }, + { + "lan": "hu", + "doc_zh": "匈牙利语" + }, + { + "lan": "is", + "doc_zh": "冰岛语" + }, + { + "lan": "ig", + "doc_zh": "伊博语" + }, + { + "lan": "id", + "doc_zh": "印度尼西亚语" + }, + { + "lan": "ia", + "doc_zh": "国际语" + }, + { + "lan": "ie", + "doc_zh": "国际文字(E)" + }, + { + "lan": "iu", + "doc_zh": "因纽特语" + }, + { + "lan": "ik", + "doc_zh": "伊努皮克语" + }, + { + "lan": "ga", + "doc_zh": "爱尔兰语" + }, + { + "lan": "it", + "doc_zh": "意大利语" + }, + { + "lan": "jv", + "doc_zh": "爪哇语" + }, + { + "lan": "kn", + "doc_zh": "卡纳达语" + }, + { + "lan": "ks", + "doc_zh": "克什米尔语" + }, + { + "lan": "kk", + "doc_zh": "哈萨克语" + }, + { + "lan": "km", + "doc_zh": "高棉语" + }, + { + "lan": "rw", + "doc_zh": "卢旺达语" + }, + { + "lan": "tlh", + "doc_zh": "克林贡语" + }, + { + "lan": "ku", + "doc_zh": "库尔德语" + }, + { + "lan": "ky", + "doc_zh": "柯尔克孜语" + }, + { + "lan": "lo", + "doc_zh": "老挝语" + }, + { + "lan": "la", + "doc_zh": "拉丁语" + }, + { + "lan": "lv", + "doc_zh": "拉脱维亚语" + }, + { + "lan": "ln", + "doc_zh": "林加拉语" + }, + { + "lan": "lt", + "doc_zh": "立陶宛语" + }, + { + "lan": "lb", + "doc_zh": "卢森堡语" + }, + { + "lan": "mk", + "doc_zh": "马其顿语" + }, + { + "lan": "mg", + "doc_zh": "马拉加斯语" + }, + { + "lan": "ms", + "doc_zh": "马来语" + }, + { + "lan": "ml", + "doc_zh": "马拉雅拉姆语" + }, + { + "lan": "mt", + "doc_zh": "马耳他语" + }, + { + "lan": "mi", + "doc_zh": "毛利语" + }, + { + "lan": "mr", + "doc_zh": "马拉地语" + }, + { + "lan": "mas", + "doc_zh": "马赛语" + }, + { + "lan": "nan", + "doc_zh": "闽南语" + }, + { + "lan": "nan-TW", + "doc_zh": "闽南语(中国台湾)" + }, + { + "lan": "lus", + "doc_zh": "米佐语" + }, + { + "lan": "mo", + "doc_zh": "摩尔多瓦语" + }, + { + "lan": "mn", + "doc_zh": "蒙古语" + }, + { + "lan": "my", + "doc_zh": "缅甸语" + }, + { + "lan": "na", + "doc_zh": "瑙鲁语" + }, + { + "lan": "nv", + "doc_zh": "纳瓦霍语" + }, + { + "lan": "ne", + "doc_zh": "尼泊尔语" + }, + { + "lan": "no", + "doc_zh": "挪威语" + }, + { + "lan": "oc", + "doc_zh": "奥克语" + }, + { + "lan": "or", + "doc_zh": "奥里亚语" + }, + { + "lan": "om", + "doc_zh": "奥罗莫语" + }, + { + "lan": "ps", + "doc_zh": "普什图语" + }, + { + "lan": "fa", + "doc_zh": "波斯语" + }, + { + "lan": "fa-AF", + "doc_zh": "波斯语(阿富汗)" + }, + { + "lan": "fa-IR", + "doc_zh": "波斯语(伊朗)" + }, + { + "lan": "pl", + "doc_zh": "波兰语" + }, + { + "lan": "pt", + "doc_zh": "葡萄牙语" + }, + { + "lan": "pt-BR", + "doc_zh": "葡萄牙语(巴西)" + }, + { + "lan": "pt-PT", + "doc_zh": "葡萄牙语(葡萄牙)" + }, + { + "lan": "pa", + "doc_zh": "旁遮普语" + }, + { + "lan": "qu", + "doc_zh": "克丘亚语" + }, + { + "lan": "ro", + "doc_zh": "罗马尼亚语" + }, + { + "lan": "rm", + "doc_zh": "罗曼什语" + }, + { + "lan": "rn", + "doc_zh": "隆迪语" + }, + { + "lan": "ru", + "doc_zh": "俄语" + }, + { + "lan": "ru-Latn", + "doc_zh": "俄语(注音)" + }, + { + "lan": "sm", + "doc_zh": "萨摩亚语" + }, + { + "lan": "sg", + "doc_zh": "桑戈语" + }, + { + "lan": "sa", + "doc_zh": "梵语" + }, + { + "lan": "gd", + "doc_zh": "苏格兰盖尔语" + }, + { + "lan": "sr", + "doc_zh": "塞尔维亚语" + }, + { + "lan": "sr-Cyrl", + "doc_zh": "塞尔维亚语(西里尔文)" + }, + { + "lan": "sr-Latn", + "doc_zh": "塞尔维亚语(拉丁文)" + }, + { + "lan": "sh", + "doc_zh": "塞尔维亚-克罗地亚语" + }, + { + "lan": "sdp", + "doc_zh": "Sherdukpen" + }, + { + "lan": "sn", + "doc_zh": "绍纳语" + }, + { + "lan": "scn", + "doc_zh": "西西里语" + }, + { + "lan": "sd", + "doc_zh": "信德语" + }, + { + "lan": "si", + "doc_zh": "僧伽罗语" + }, + { + "lan": "sk", + "doc_zh": "斯洛伐克语" + }, + { + "lan": "sl", + "doc_zh": "斯洛文尼亚语" + }, + { + "lan": "so", + "doc_zh": "索马里语" + }, + { + "lan": "st", + "doc_zh": "南索托语" + }, + { + "lan": "es", + "doc_zh": "西班牙语" + }, + { + "lan": "es-419", + "doc_zh": "西班牙语(拉丁美洲)" + }, + { + "lan": "es-MX", + "doc_zh": "西班牙语(墨西哥)" + }, + { + "lan": "es-ES", + "doc_zh": "西班牙语(西班牙)" + }, + { + "lan": "es-US", + "doc_zh": "西班牙语(美国)" + }, + { + "lan": "su", + "doc_zh": "巽他语" + }, + { + "lan": "sw", + "doc_zh": "斯瓦希里语" + }, + { + "lan": "ss", + "doc_zh": "斯瓦蒂语" + }, + { + "lan": "sv", + "doc_zh": "瑞典语" + }, + { + "lan": "tl", + "doc_zh": "他加禄语" + }, + { + "lan": "tg", + "doc_zh": "塔吉克语" + }, + { + "lan": "ta", + "doc_zh": "泰米尔语" + }, + { + "lan": "tt", + "doc_zh": "鞑靼语" + }, + { + "lan": "te", + "doc_zh": "泰卢固语" + }, + { + "lan": "th", + "doc_zh": "泰语" + }, + { + "lan": "ti", + "doc_zh": "提格利尼亚语" + }, + { + "lan": "to", + "doc_zh": "汤加语" + }, + { + "lan": "ts", + "doc_zh": "聪加语" + }, + { + "lan": "tn", + "doc_zh": "茨瓦纳语" + }, + { + "lan": "tr", + "doc_zh": "土耳其语" + }, + { + "lan": "tk", + "doc_zh": "土库曼语" + }, + { + "lan": "tw", + "doc_zh": "契维语" + }, + { + "lan": "uk", + "doc_zh": "乌克兰语" + }, + { + "lan": "ur", + "doc_zh": "乌尔都语" + }, + { + "lan": "uz", + "doc_zh": "乌兹别克语" + }, + { + "lan": "vi", + "doc_zh": "越南语" + }, + { + "lan": "vo", + "doc_zh": "沃拉普克语" + }, + { + "lan": "cy", + "doc_zh": "威尔士语" + }, + { + "lan": "fy", + "doc_zh": "西弗里西亚语" + }, + { + "lan": "wo", + "doc_zh": "沃洛夫语" + }, + { + "lan": "xh", + "doc_zh": "科萨语" + }, + { + "lan": "yi", + "doc_zh": "意第绪语" + }, + { + "lan": "yo", + "doc_zh": "约鲁巴语" + }, + { + "lan": "zu", + "doc_zh": "祖鲁语" + } +] \ No newline at end of file diff --git a/bilibili_api/data/video_uploader_lines.json b/bilibili_api/data/video_uploader_lines.json new file mode 100644 index 0000000000000000000000000000000000000000..2222fa89630fceb86387cb85f1a5c22fd2c2c38b --- /dev/null +++ b/bilibili_api/data/video_uploader_lines.json @@ -0,0 +1,30 @@ +{ + "bda2": { + "os": "upos", + "upcdn": "bda2", + "probe_version": 20221109, + "query": "probe_version=20221109&upcdn=bda2", + "probe_url": "//upos-cs-upcdnbda2.bilivideo.com/OK" + }, + "bldsa": { + "os": "upos", + "upcdn": "bldsa", + "probe_version": 20221109, + "query": "upcdn=bldsa&probe_version=20221109", + "probe_url": "//upos-cs-upcdnbldsa.bilivideo.com/OK" + }, + "qn": { + "os": "upos", + "upcdn": "qn", + "probe_version": 20221109, + "query": "probe_version=20221109&upcdn=qn", + "probe_url": "//upos-cs-upcdnqn.bilivideo.com/OK" + }, + "ws": { + "os": "upos", + "upcdn": "ws", + "probe_version": 20221109, + "query": "upcdn=ws&probe_version=20221109", + "probe_url": "//upos-cs-upcdnws.bilivideo.com/OK" + } +} \ No newline at end of file diff --git a/bilibili_api/data/video_uploader_meta_pre.json b/bilibili_api/data/video_uploader_meta_pre.json new file mode 100644 index 0000000000000000000000000000000000000000..2a4717913ba3db29d0cdb62c5f29f68d6a30cb65 --- /dev/null +++ b/bilibili_api/data/video_uploader_meta_pre.json @@ -0,0 +1,4215 @@ +{ + "industry_list": [ + { + "id": 1, + "type": 0, + "name": "手机游戏" + }, + { + "id": 20, + "type": 0, + "name": "主机游戏" + }, + { + "id": 21, + "type": 0, + "name": "网页游戏" + }, + { + "id": 22, + "type": 0, + "name": "PC单机游戏" + }, + { + "id": 23, + "type": 0, + "name": "PC网络游戏" + }, + { + "id": 2, + "type": 0, + "name": "软件应用" + }, + { + "id": 3, + "type": 0, + "name": "日用品化妆品" + }, + { + "id": 4, + "type": 0, + "name": "服装鞋帽" + }, + { + "id": 5, + "type": 0, + "name": "箱包饰品" + }, + { + "id": 6, + "type": 0, + "name": "食品饮料" + }, + { + "id": 7, + "type": 0, + "name": "出版传媒" + }, + { + "id": 8, + "type": 0, + "name": "电脑硬件" + }, + { + "id": 9, + "type": 0, + "name": "其他" + }, + { + "id": 213, + "type": 0, + "name": "医疗类" + }, + { + "id": 214, + "type": 0, + "name": "金融" + } + ], + "neutral_mark": { + "marks": [ + "作者声明:该视频使用人工智能合成技术", + "作者声明:视频内含有危险行为,请勿轻易模仿", + "作者声明:该内容仅供娱乐,请勿过分解读", + "作者声明:该内容可能引人不适,请谨慎选择观看", + "作者声明:请理性适度消费", + "作者声明:个人观点,仅供参考" + ] + }, + "showtype_list": [ + { + "id": 15, + "type": 1, + "name": "Logo" + }, + { + "id": 10, + "type": 1, + "name": "其它" + }, + { + "id": 11, + "type": 1, + "name": "口播" + }, + { + "id": 12, + "type": 1, + "name": "贴片" + }, + { + "id": 14, + "type": 1, + "name": "TVC植入" + }, + { + "id": 19, + "type": 1, + "name": "定制软广" + }, + { + "id": 18, + "type": 1, + "name": "节目赞助" + }, + { + "id": 17, + "type": 1, + "name": "Slogan" + }, + { + "id": 16, + "type": 1, + "name": "二维码" + }, + { + "id": 13, + "type": 1, + "name": "字幕推广" + } + ], + "staff_activity_conf": { + "typelist": [ + { + "typeid": 22, + "max_staff": 10, + "title_ids": [7, 5, 6, 2, 1, 3, 4, 9, 10, 26], + "titles": [ + "填词", + "视频制作", + "参演", + "后期", + "配音", + "调音", + "剪辑", + "作曲", + "编曲", + "字幕" + ] + }, + { + "typeid": 26, + "max_staff": 10, + "title_ids": [7, 5, 6, 2, 1, 3, 4, 9, 10, 26], + "titles": [ + "填词", + "视频制作", + "参演", + "后期", + "配音", + "调音", + "剪辑", + "作曲", + "编曲", + "字幕" + ] + }, + { + "typeid": 126, + "max_staff": 10, + "title_ids": [7, 5, 6, 2, 1, 3, 4, 9, 10, 26], + "titles": [ + "填词", + "视频制作", + "参演", + "后期", + "配音", + "调音", + "剪辑", + "作曲", + "编曲", + "字幕" + ] + }, + { + "typeid": 127, + "max_staff": 10, + "title_ids": [7, 5, 6, 2, 1, 3, 4, 9, 10, 26], + "titles": [ + "填词", + "视频制作", + "参演", + "后期", + "配音", + "调音", + "剪辑", + "作曲", + "编曲", + "字幕" + ] + }, + { + "typeid": 28, + "max_staff": 10, + "title_ids": [ + 8, 9, 10, 11, 12, 13, 2, 5, 30, 6, 7, 1, 3, 4, 31, 32, 26, 35 + ], + "titles": [ + "作词", + "作曲", + "编曲", + "演唱", + "混音", + "曲绘", + "后期", + "视频制作", + "调校", + "参演", + "填词", + "配音", + "调音", + "剪辑", + "演奏", + "母带", + "字幕", + "策划" + ] + }, + { + "typeid": 29, + "max_staff": 10, + "title_ids": [ + 8, 9, 10, 11, 12, 13, 2, 5, 30, 6, 7, 1, 3, 4, 31, 32, 26, 35 + ], + "titles": [ + "作词", + "作曲", + "编曲", + "演唱", + "混音", + "曲绘", + "后期", + "视频制作", + "调校", + "参演", + "填词", + "配音", + "调音", + "剪辑", + "演奏", + "母带", + "字幕", + "策划" + ] + }, + { + "typeid": 30, + "max_staff": 10, + "title_ids": [ + 8, 9, 10, 11, 12, 13, 2, 5, 30, 6, 7, 1, 3, 4, 31, 32, 26, 35 + ], + "titles": [ + "作词", + "作曲", + "编曲", + "演唱", + "混音", + "曲绘", + "后期", + "视频制作", + "调校", + "参演", + "填词", + "配音", + "调音", + "剪辑", + "演奏", + "母带", + "字幕", + "策划" + ] + }, + { + "typeid": 31, + "max_staff": 10, + "title_ids": [ + 8, 9, 10, 11, 12, 13, 2, 5, 30, 6, 7, 1, 3, 4, 31, 32, 26, 35 + ], + "titles": [ + "作词", + "作曲", + "编曲", + "演唱", + "混音", + "曲绘", + "后期", + "视频制作", + "调校", + "参演", + "填词", + "配音", + "调音", + "剪辑", + "演奏", + "母带", + "字幕", + "策划" + ] + }, + { + "typeid": 54, + "max_staff": 10, + "title_ids": [ + 8, 9, 10, 11, 12, 13, 2, 5, 30, 6, 7, 1, 3, 4, 31, 32, 26, 35 + ], + "titles": [ + "作词", + "作曲", + "编曲", + "演唱", + "混音", + "曲绘", + "后期", + "视频制作", + "调校", + "参演", + "填词", + "配音", + "调音", + "剪辑", + "演奏", + "母带", + "字幕", + "策划" + ] + }, + { + "typeid": 59, + "max_staff": 10, + "title_ids": [ + 8, 9, 10, 11, 12, 13, 2, 5, 30, 6, 7, 1, 3, 4, 31, 32, 26, 35 + ], + "titles": [ + "作词", + "作曲", + "编曲", + "演唱", + "混音", + "曲绘", + "后期", + "视频制作", + "调校", + "参演", + "填词", + "配音", + "调音", + "剪辑", + "演奏", + "母带", + "字幕", + "策划" + ] + }, + { + "typeid": 130, + "max_staff": 10, + "title_ids": [ + 8, 9, 10, 11, 12, 13, 2, 5, 30, 6, 7, 1, 3, 4, 31, 32, 26, 35 + ], + "titles": [ + "作词", + "作曲", + "编曲", + "演唱", + "混音", + "曲绘", + "后期", + "视频制作", + "调校", + "参演", + "填词", + "配音", + "调音", + "剪辑", + "演奏", + "母带", + "字幕", + "策划" + ] + }, + { + "typeid": 193, + "max_staff": 10, + "title_ids": [ + 8, 9, 10, 11, 12, 13, 2, 5, 30, 6, 7, 1, 3, 4, 31, 32, 26, 35 + ], + "titles": [ + "作词", + "作曲", + "编曲", + "演唱", + "混音", + "曲绘", + "后期", + "视频制作", + "调校", + "参演", + "填词", + "配音", + "调音", + "剪辑", + "演奏", + "母带", + "字幕", + "策划" + ] + }, + { + "typeid": 194, + "max_staff": 10, + "title_ids": [ + 8, 9, 10, 11, 12, 13, 2, 5, 30, 6, 7, 1, 3, 4, 31, 32, 26, 35 + ], + "titles": [ + "作词", + "作曲", + "编曲", + "演唱", + "混音", + "曲绘", + "后期", + "视频制作", + "调校", + "参演", + "填词", + "配音", + "调音", + "剪辑", + "演奏", + "母带", + "字幕", + "策划" + ] + }, + { + "typeid": 20, + "max_staff": 10, + "title_ids": [22, 5, 6, 2, 7, 1, 3, 23, 11, 9, 10], + "titles": [ + "舞者", + "视频制作", + "参演", + "后期", + "填词", + "配音", + "调音", + "摄影", + "演唱", + "作曲", + "编曲" + ] + }, + { + "typeid": 154, + "max_staff": 10, + "title_ids": [22, 5, 6, 2, 7, 1, 3, 23, 11, 9, 10], + "titles": [ + "舞者", + "视频制作", + "参演", + "后期", + "填词", + "配音", + "调音", + "摄影", + "演唱", + "作曲", + "编曲" + ] + }, + { + "typeid": 156, + "max_staff": 10, + "title_ids": [22, 5, 6, 2, 7, 1, 3, 23, 11, 9, 10], + "titles": [ + "舞者", + "视频制作", + "参演", + "后期", + "填词", + "配音", + "调音", + "摄影", + "演唱", + "作曲", + "编曲" + ] + }, + { + "typeid": 198, + "max_staff": 10, + "title_ids": [22, 5, 6, 2, 7, 1, 3, 23, 11, 9, 10], + "titles": [ + "舞者", + "视频制作", + "参演", + "后期", + "填词", + "配音", + "调音", + "摄影", + "演唱", + "作曲", + "编曲" + ] + }, + { + "typeid": 199, + "max_staff": 10, + "title_ids": [22, 5, 6, 2, 7, 1, 3, 23, 11, 9, 10], + "titles": [ + "舞者", + "视频制作", + "参演", + "后期", + "填词", + "配音", + "调音", + "摄影", + "演唱", + "作曲", + "编曲" + ] + }, + { + "typeid": 200, + "max_staff": 10, + "title_ids": [22, 5, 6, 2, 7, 1, 3, 23, 11, 9, 10], + "titles": [ + "舞者", + "视频制作", + "参演", + "后期", + "填词", + "配音", + "调音", + "摄影", + "演唱", + "作曲", + "编曲" + ] + }, + { + "typeid": 24, + "max_staff": 10, + "title_ids": [20, 1, 5, 6, 2, 7, 3, 4, 26, 27, 28, 29], + "titles": [ + "文案", + "配音", + "视频制作", + "参演", + "后期", + "填词", + "调音", + "剪辑", + "字幕", + "渲染", + "模型", + "动作" + ] + }, + { + "typeid": 25, + "max_staff": 10, + "title_ids": [20, 1, 5, 6, 2, 7, 3, 4, 26, 27, 28, 29], + "titles": [ + "文案", + "配音", + "视频制作", + "参演", + "后期", + "填词", + "调音", + "剪辑", + "字幕", + "渲染", + "模型", + "动作" + ] + }, + { + "typeid": 27, + "max_staff": 10, + "title_ids": [20, 1, 5, 6, 2, 7, 3, 4, 26, 27, 28, 29], + "titles": [ + "文案", + "配音", + "视频制作", + "参演", + "后期", + "填词", + "调音", + "剪辑", + "字幕", + "渲染", + "模型", + "动作" + ] + }, + { + "typeid": 210, + "max_staff": 10, + "title_ids": [20, 1, 5, 6, 2, 7, 3, 4, 26, 27, 28, 29], + "titles": [ + "文案", + "配音", + "视频制作", + "参演", + "后期", + "填词", + "调音", + "剪辑", + "字幕", + "渲染", + "模型", + "动作" + ] + }, + { + "typeid": 47, + "max_staff": 10, + "title_ids": [20, 1, 5, 6, 2, 7, 3, 4, 26, 27, 28, 29], + "titles": [ + "文案", + "配音", + "视频制作", + "参演", + "后期", + "填词", + "调音", + "剪辑", + "字幕", + "渲染", + "模型", + "动作" + ] + }, + { + "typeid": 86, + "max_staff": 10, + "title_ids": [20, 1, 5, 6, 2, 7, 3, 4, 26, 27, 28, 29], + "titles": [ + "文案", + "配音", + "视频制作", + "参演", + "后期", + "填词", + "调音", + "剪辑", + "字幕", + "渲染", + "模型", + "动作" + ] + }, + { + "typeid": 39, + "max_staff": 10, + "title_ids": [16, 17, 6, 2, 23, 1, 20, 5, 7, 3, 4, 33, 34], + "titles": [ + "导演", + "编剧", + "参演", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 96, + "max_staff": 10, + "title_ids": [16, 17, 6, 2, 23, 1, 20, 5, 7, 3, 4, 33, 34], + "titles": [ + "导演", + "编剧", + "参演", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 98, + "max_staff": 10, + "title_ids": [16, 17, 6, 2, 23, 1, 20, 5, 7, 3, 4, 33, 34], + "titles": [ + "导演", + "编剧", + "参演", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 122, + "max_staff": 10, + "title_ids": [16, 17, 6, 2, 23, 1, 20, 5, 7, 3, 4, 33, 34], + "titles": [ + "导演", + "编剧", + "参演", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 124, + "max_staff": 10, + "title_ids": [16, 17, 6, 2, 23, 1, 20, 5, 7, 3, 4, 33, 34], + "titles": [ + "导演", + "编剧", + "参演", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 207, + "max_staff": 10, + "title_ids": [16, 17, 6, 2, 23, 1, 20, 5, 7, 3, 4, 33, 34], + "titles": [ + "导演", + "编剧", + "参演", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 208, + "max_staff": 10, + "title_ids": [16, 17, 6, 2, 23, 1, 20, 5, 7, 3, 4, 33, 34], + "titles": [ + "导演", + "编剧", + "参演", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 209, + "max_staff": 10, + "title_ids": [16, 17, 6, 2, 23, 1, 20, 5, 7, 3, 4, 33, 34], + "titles": [ + "导演", + "编剧", + "参演", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 201, + "max_staff": 10, + "title_ids": [16, 17, 6, 2, 23, 1, 20, 5, 7, 3, 4, 33, 34], + "titles": [ + "导演", + "编剧", + "参演", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 95, + "max_staff": 10, + "title_ids": [6, 2, 23, 1, 20, 5, 7, 3, 4, 33, 34], + "titles": [ + "参演", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 176, + "max_staff": 10, + "title_ids": [6, 17, 16, 1, 2, 23, 7, 5, 20, 3, 4, 33, 34], + "titles": [ + "参演", + "编剧", + "导演", + "配音", + "后期", + "摄影", + "填词", + "视频制作", + "文案", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 189, + "max_staff": 10, + "title_ids": [6, 2, 23, 1, 20, 5, 7, 3, 4, 33, 34], + "titles": [ + "参演", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 190, + "max_staff": 10, + "title_ids": [6, 2, 23, 1, 20, 5, 7, 3, 4, 33, 34], + "titles": [ + "参演", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 191, + "max_staff": 10, + "title_ids": [6, 2, 23, 1, 20, 5, 7, 3, 4, 33, 34], + "titles": [ + "参演", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 85, + "max_staff": 10, + "title_ids": [1, 2, 3, 4, 5, 6, 7, 16, 17, 19, 20], + "titles": [ + "配音", + "后期", + "调音", + "剪辑", + "视频制作", + "参演", + "填词", + "导演", + "编剧", + "封面设计", + "文案" + ] + }, + { + "typeid": 182, + "max_staff": 10, + "title_ids": [1, 2, 3, 4, 5, 6, 7, 16, 17, 19, 20], + "titles": [ + "配音", + "后期", + "调音", + "剪辑", + "视频制作", + "参演", + "填词", + "导演", + "编剧", + "封面设计", + "文案" + ] + }, + { + "typeid": 183, + "max_staff": 10, + "title_ids": [1, 2, 3, 4, 5, 6, 7, 16, 17, 19, 20], + "titles": [ + "配音", + "后期", + "调音", + "剪辑", + "视频制作", + "参演", + "填词", + "导演", + "编剧", + "封面设计", + "文案" + ] + }, + { + "typeid": 184, + "max_staff": 10, + "title_ids": [1, 2, 3, 4, 5, 6, 7, 16, 17, 19, 20], + "titles": [ + "配音", + "后期", + "调音", + "剪辑", + "视频制作", + "参演", + "填词", + "导演", + "编剧", + "封面设计", + "文案" + ] + }, + { + "typeid": 17, + "max_staff": 10, + "title_ids": [1, 2, 3, 4, 5, 6, 7, 20, 26], + "titles": [ + "配音", + "后期", + "调音", + "剪辑", + "视频制作", + "参演", + "填词", + "文案", + "字幕" + ] + }, + { + "typeid": 19, + "max_staff": 10, + "title_ids": [1, 2, 3, 4, 5, 6, 7, 20, 26], + "titles": [ + "配音", + "后期", + "调音", + "剪辑", + "视频制作", + "参演", + "填词", + "文案", + "字幕" + ] + }, + { + "typeid": 65, + "max_staff": 10, + "title_ids": [1, 2, 3, 4, 5, 6, 7, 20, 26], + "titles": [ + "配音", + "后期", + "调音", + "剪辑", + "视频制作", + "参演", + "填词", + "文案", + "字幕" + ] + }, + { + "typeid": 121, + "max_staff": 10, + "title_ids": [1, 2, 3, 4, 5, 6, 7, 20, 26], + "titles": [ + "配音", + "后期", + "调音", + "剪辑", + "视频制作", + "参演", + "填词", + "文案", + "字幕" + ] + }, + { + "typeid": 136, + "max_staff": 10, + "title_ids": [1, 2, 3, 4, 5, 6, 7, 20, 26], + "titles": [ + "配音", + "后期", + "调音", + "剪辑", + "视频制作", + "参演", + "填词", + "文案", + "字幕" + ] + }, + { + "typeid": 171, + "max_staff": 10, + "title_ids": [1, 2, 3, 4, 5, 6, 7, 20, 26], + "titles": [ + "配音", + "后期", + "调音", + "剪辑", + "视频制作", + "参演", + "填词", + "文案", + "字幕" + ] + }, + { + "typeid": 172, + "max_staff": 10, + "title_ids": [1, 2, 3, 4, 5, 6, 7, 20, 26], + "titles": [ + "配音", + "后期", + "调音", + "剪辑", + "视频制作", + "参演", + "填词", + "文案", + "字幕" + ] + }, + { + "typeid": 173, + "max_staff": 10, + "title_ids": [1, 2, 3, 4, 5, 6, 7, 20, 26], + "titles": [ + "配音", + "后期", + "调音", + "剪辑", + "视频制作", + "参演", + "填词", + "文案", + "字幕" + ] + }, + { + "typeid": 203, + "max_staff": 10, + "title_ids": [1, 2, 4, 5, 6, 20, 35], + "titles": ["配音", "后期", "剪辑", "视频制作", "参演", "文案", "策划"] + }, + { + "typeid": 204, + "max_staff": 10, + "title_ids": [1, 2, 4, 5, 6, 20, 35], + "titles": ["配音", "后期", "剪辑", "视频制作", "参演", "文案", "策划"] + }, + { + "typeid": 205, + "max_staff": 10, + "title_ids": [1, 2, 4, 5, 6, 20, 35], + "titles": ["配音", "后期", "剪辑", "视频制作", "参演", "文案", "策划"] + }, + { + "typeid": 206, + "max_staff": 10, + "title_ids": [1, 2, 4, 5, 6, 20, 35], + "titles": ["配音", "后期", "剪辑", "视频制作", "参演", "文案", "策划"] + }, + { + "typeid": 21, + "max_staff": 10, + "title_ids": [6, 5, 1, 4, 20, 33], + "titles": ["参演", "视频制作", "配音", "剪辑", "文案", "手工制作"] + }, + { + "typeid": 75, + "max_staff": 10, + "title_ids": [6, 5, 1, 20, 23, 2], + "titles": ["参演", "视频制作", "配音", "文案", "摄影", "后期"] + }, + { + "typeid": 76, + "max_staff": 10, + "title_ids": [1, 5, 6, 20, 33, 4], + "titles": ["配音", "视频制作", "参演", "文案", "手工制作", "剪辑"] + }, + { + "typeid": 138, + "max_staff": 10, + "title_ids": [6, 5, 1, 4, 20, 33], + "titles": ["参演", "视频制作", "配音", "剪辑", "文案", "手工制作"] + }, + { + "typeid": 161, + "max_staff": 10, + "title_ids": [6, 5, 1, 4, 20, 33], + "titles": ["参演", "视频制作", "配音", "剪辑", "文案", "手工制作"] + }, + { + "typeid": 162, + "max_staff": 10, + "title_ids": [6, 5, 1, 4, 20, 33], + "titles": ["参演", "视频制作", "配音", "剪辑", "文案", "手工制作"] + }, + { + "typeid": 163, + "max_staff": 10, + "title_ids": [6, 5, 1, 4, 20, 33], + "titles": ["参演", "视频制作", "配音", "剪辑", "文案", "手工制作"] + }, + { + "typeid": 174, + "max_staff": 10, + "title_ids": [6, 5, 1, 4, 20, 33], + "titles": ["参演", "视频制作", "配音", "剪辑", "文案", "手工制作"] + }, + { + "typeid": 175, + "max_staff": 10, + "title_ids": [6, 5, 1, 4, 20, 33], + "titles": ["参演", "视频制作", "配音", "剪辑", "文案", "手工制作"] + }, + { + "typeid": 157, + "max_staff": 10, + "title_ids": [1, 4, 5, 6, 20, 33], + "titles": ["配音", "剪辑", "视频制作", "参演", "文案", "手工制作"] + }, + { + "typeid": 158, + "max_staff": 10, + "title_ids": [1, 4, 5, 6, 20, 33], + "titles": ["配音", "剪辑", "视频制作", "参演", "文案", "手工制作"] + }, + { + "typeid": 159, + "max_staff": 10, + "title_ids": [1, 4, 5, 6, 20, 33], + "titles": ["配音", "剪辑", "视频制作", "参演", "文案", "手工制作"] + }, + { + "typeid": 164, + "max_staff": 10, + "title_ids": [1, 4, 5, 6, 20, 33], + "titles": ["配音", "剪辑", "视频制作", "参演", "文案", "手工制作"] + }, + { + "typeid": 192, + "max_staff": 10, + "title_ids": [1, 4, 5, 6, 20, 33], + "titles": ["配音", "剪辑", "视频制作", "参演", "文案", "手工制作"] + }, + { + "typeid": 71, + "max_staff": 10, + "title_ids": [4, 5, 6, 2, 20, 1, 16, 17, 7, 3, 11, 9, 10, 35, 19], + "titles": [ + "剪辑", + "视频制作", + "参演", + "后期", + "文案", + "配音", + "导演", + "编剧", + "填词", + "调音", + "演唱", + "作曲", + "编曲", + "策划", + "封面设计" + ] + }, + { + "typeid": 131, + "max_staff": 10, + "title_ids": [1, 4, 5, 6, 2, 3, 7, 9, 10, 35, 19, 11, 16, 17], + "titles": [ + "配音", + "剪辑", + "视频制作", + "参演", + "后期", + "调音", + "填词", + "作曲", + "编曲", + "策划", + "封面设计", + "演唱", + "导演", + "编剧" + ] + }, + { + "typeid": 137, + "max_staff": 10, + "title_ids": [4, 5, 6, 2, 20, 1, 16, 17, 7, 3, 11, 9, 10, 35, 19], + "titles": [ + "剪辑", + "视频制作", + "参演", + "后期", + "文案", + "配音", + "导演", + "编剧", + "填词", + "调音", + "演唱", + "作曲", + "编曲", + "策划", + "封面设计" + ] + }, + { + "typeid": 212, + "max_staff": 10, + "title_ids": [1, 5, 6, 20, 33, 4], + "titles": ["配音", "视频制作", "参演", "文案", "手工制作", "剪辑"] + }, + { + "typeid": 213, + "max_staff": 10, + "title_ids": [1, 5, 6, 20, 33, 4], + "titles": ["配音", "视频制作", "参演", "文案", "手工制作", "剪辑"] + }, + { + "typeid": 214, + "max_staff": 10, + "title_ids": [1, 5, 6, 20, 33, 4], + "titles": ["配音", "视频制作", "参演", "文案", "手工制作", "剪辑"] + }, + { + "typeid": 215, + "max_staff": 10, + "title_ids": [1, 5, 6, 20, 33, 4], + "titles": ["配音", "视频制作", "参演", "文案", "手工制作", "剪辑"] + }, + { + "typeid": 216, + "max_staff": 10, + "title_ids": [7, 5, 2, 1, 6, 3, 4, 9, 10, 26], + "titles": [ + "填词", + "视频制作", + "后期", + "配音", + "参演", + "调音", + "剪辑", + "作曲", + "编曲", + "字幕" + ] + }, + { + "typeid": 218, + "max_staff": 10, + "title_ids": [6, 5, 1, 20, 23, 2], + "titles": ["参演", "视频制作", "配音", "文案", "摄影", "后期"] + }, + { + "typeid": 219, + "max_staff": 10, + "title_ids": [6, 5, 1, 20, 23, 2], + "titles": ["参演", "视频制作", "配音", "文案", "摄影", "后期"] + }, + { + "typeid": 220, + "max_staff": 10, + "title_ids": [6, 5, 1, 20, 23, 2], + "titles": ["参演", "视频制作", "配音", "文案", "摄影", "后期"] + }, + { + "typeid": 221, + "max_staff": 10, + "title_ids": [6, 5, 1, 20, 23, 2], + "titles": ["参演", "视频制作", "配音", "文案", "摄影", "后期"] + }, + { + "typeid": 222, + "max_staff": 10, + "title_ids": [6, 5, 1, 20, 23, 2], + "titles": ["参演", "视频制作", "配音", "文案", "摄影", "后期"] + }, + { + "typeid": 224, + "max_staff": 10, + "title_ids": [6, 17, 16, 1, 2, 23, 7, 5, 20, 3, 4, 33, 34], + "titles": [ + "参演", + "编剧", + "导演", + "配音", + "后期", + "摄影", + "填词", + "视频制作", + "文案", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 225, + "max_staff": 10, + "title_ids": [6, 17, 16, 1, 2, 23, 7, 5, 20, 3, 4, 33, 34], + "titles": [ + "参演", + "编剧", + "导演", + "配音", + "后期", + "摄影", + "填词", + "视频制作", + "文案", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 226, + "max_staff": 10, + "title_ids": [6, 17, 16, 1, 2, 23, 7, 5, 20, 3, 4, 33, 34], + "titles": [ + "参演", + "编剧", + "导演", + "配音", + "后期", + "摄影", + "填词", + "视频制作", + "文案", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 227, + "max_staff": 10, + "title_ids": [6, 17, 16, 1, 2, 23, 7, 5, 20, 3, 4, 33, 34], + "titles": [ + "参演", + "编剧", + "导演", + "配音", + "后期", + "摄影", + "填词", + "视频制作", + "文案", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 228, + "max_staff": 10, + "title_ids": [16, 17, 2, 6, 1, 23, 20, 5, 4, 33, 34], + "titles": [ + "导演", + "编剧", + "后期", + "参演", + "配音", + "摄影", + "文案", + "视频制作", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 229, + "max_staff": 10, + "title_ids": [16, 17, 2, 6, 1, 23, 20, 5, 4, 33, 34], + "titles": [ + "导演", + "编剧", + "后期", + "参演", + "配音", + "摄影", + "文案", + "视频制作", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 230, + "max_staff": 10, + "title_ids": [6, 16, 17, 2, 23, 1, 20, 5, 4, 34, 33], + "titles": [ + "参演", + "导演", + "编剧", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "剪辑", + "研发", + "手工制作" + ] + }, + { + "typeid": 231, + "max_staff": 10, + "title_ids": [6, 16, 17, 2, 23, 1, 20, 5, 4, 34, 33], + "titles": [ + "参演", + "导演", + "编剧", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "剪辑", + "研发", + "手工制作" + ] + }, + { + "typeid": 232, + "max_staff": 10, + "title_ids": [6, 16, 17, 2, 23, 1, 20, 5, 4, 34, 33], + "titles": [ + "参演", + "导演", + "编剧", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "剪辑", + "研发", + "手工制作" + ] + }, + { + "typeid": 233, + "max_staff": 10, + "title_ids": [6, 16, 17, 2, 23, 1, 20, 5, 4, 34, 33], + "titles": [ + "参演", + "导演", + "编剧", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "剪辑", + "研发", + "手工制作" + ] + }, + { + "typeid": 239, + "max_staff": 10, + "title_ids": [17, 16, 20, 23, 6, 4, 1, 2, 5], + "titles": [ + "编剧", + "导演", + "文案", + "摄影", + "参演", + "剪辑", + "配音", + "后期", + "视频制作" + ] + }, + { + "typeid": 235, + "max_staff": 10, + "title_ids": [16, 17, 6, 2, 1, 20, 5, 7, 3, 4, 33], + "titles": [ + "导演", + "编剧", + "参演", + "后期", + "配音", + "文案", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作" + ] + }, + { + "typeid": 236, + "max_staff": 10, + "title_ids": [6, 16, 17, 2, 23, 20, 1, 5, 7, 3, 4, 33], + "titles": [ + "参演", + "导演", + "编剧", + "后期", + "摄影", + "文案", + "配音", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作" + ] + }, + { + "typeid": 237, + "max_staff": 10, + "title_ids": [6, 16, 17, 2, 23, 20, 1, 5, 7, 3, 4, 33], + "titles": [ + "参演", + "导演", + "编剧", + "后期", + "摄影", + "文案", + "配音", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作" + ] + }, + { + "typeid": 238, + "max_staff": 10, + "title_ids": [6, 16, 17, 2, 23, 20, 1, 5, 7, 3, 4, 33], + "titles": [ + "参演", + "导演", + "编剧", + "后期", + "摄影", + "文案", + "配音", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作" + ] + }, + { + "typeid": 240, + "max_staff": 10, + "title_ids": [1, 2, 3, 4, 5, 6, 7, 16, 17, 20, 23, 33, 34], + "titles": [ + "配音", + "后期", + "调音", + "剪辑", + "视频制作", + "参演", + "填词", + "导演", + "编剧", + "文案", + "摄影", + "手工制作", + "研发" + ] + }, + { + "typeid": 241, + "max_staff": 10, + "title_ids": [4, 5, 6, 2, 20, 1, 16, 17, 7, 3, 11, 9, 10, 35, 19], + "titles": [ + "剪辑", + "视频制作", + "参演", + "后期", + "文案", + "配音", + "导演", + "编剧", + "填词", + "调音", + "演唱", + "作曲", + "编曲", + "策划", + "封面设计" + ] + }, + { + "typeid": 242, + "max_staff": 10, + "title_ids": [4, 5, 6, 2, 20, 1, 16, 17, 7, 3, 11, 9, 10, 35, 19], + "titles": [ + "剪辑", + "视频制作", + "参演", + "后期", + "文案", + "配音", + "导演", + "编剧", + "填词", + "调音", + "演唱", + "作曲", + "编曲", + "策划", + "封面设计" + ] + }, + { + "typeid": 245, + "max_staff": 10, + "title_ids": [16, 17, 6, 2, 23, 1, 20, 5, 7, 3, 4, 33, 34], + "titles": [ + "导演", + "编剧", + "参演", + "后期", + "摄影", + "配音", + "文案", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作", + "研发" + ] + }, + { + "typeid": 243, + "max_staff": 10, + "title_ids": [ + 8, 9, 10, 11, 12, 13, 2, 5, 14, 6, 7, 1, 3, 4, 31, 32, 26, 35 + ], + "titles": [ + "作词", + "作曲", + "编曲", + "演唱", + "混音", + "曲绘", + "后期", + "视频制作", + "调教", + "参演", + "填词", + "配音", + "调音", + "剪辑", + "演奏", + "母带", + "字幕", + "策划" + ] + }, + { + "typeid": 244, + "max_staff": 10, + "title_ids": [ + 8, 9, 10, 11, 12, 13, 2, 5, 14, 6, 7, 1, 3, 4, 31, 32, 26, 35 + ], + "titles": [ + "作词", + "作曲", + "编曲", + "演唱", + "混音", + "曲绘", + "后期", + "视频制作", + "调教", + "参演", + "填词", + "配音", + "调音", + "剪辑", + "演奏", + "母带", + "字幕", + "策划" + ] + }, + { + "typeid": 249, + "max_staff": 10, + "title_ids": [16, 17, 6, 2, 1, 20, 5, 7, 3, 4, 33], + "titles": [ + "导演", + "编剧", + "参演", + "后期", + "配音", + "文案", + "视频制作", + "填词", + "调音", + "剪辑", + "手工制作" + ] + }, + { + "typeid": 250, + "max_staff": 10, + "title_ids": [6, 5, 1, 4, 20, 33], + "titles": ["参演", "视频制作", "配音", "剪辑", "文案", "手工制作"] + }, + { + "typeid": 251, + "max_staff": 10, + "title_ids": [5, 6, 2, 20, 1, 4, 35], + "titles": ["视频制作", "参演", "后期", "文案", "配音", "剪辑", "策划"] + }, + { + "typeid": 253, + "max_staff": 10, + "title_ids": [20, 1, 5, 6, 2, 7, 3, 4, 26, 27, 28, 29], + "titles": [ + "文案", + "配音", + "视频制作", + "参演", + "后期", + "填词", + "调音", + "剪辑", + "字幕", + "渲染", + "模型", + "动作" + ] + }, + { + "typeid": 254, + "max_staff": 10, + "title_ids": [6, 4, 20, 5, 1, 33], + "titles": ["参演", "剪辑", "文案", "视频制作", "配音", "手工制作"] + }, + { + "typeid": 255, + "max_staff": 10, + "title_ids": [22, 5, 6, 2, 7, 1, 3, 23, 11, 9, 10], + "titles": [ + "舞者", + "视频制作", + "参演", + "后期", + "填词", + "配音", + "调音", + "摄影", + "演唱", + "作曲", + "编曲" + ] + }, + { + "typeid": 257, + "max_staff": 10, + "title_ids": [20, 1, 5, 6, 2, 7, 3, 4, 26, 27, 28, 29], + "titles": [ + "文案", + "配音", + "视频制作", + "参演", + "后期", + "填词", + "调音", + "剪辑", + "字幕", + "渲染", + "模型", + "动作" + ] + }, + { + "typeid": 256, + "max_staff": 10, + "title_ids": [1, 2, 3, 4, 5, 6, 7, 16, 17, 19, 20], + "titles": [ + "配音", + "后期", + "调音", + "剪辑", + "视频制作", + "参演", + "填词", + "导演", + "编剧", + "封面设计", + "文案" + ] + } + ], + "titles": [ + { + "id": 195, + "tag_id": 1, + "name": "配音" + }, + { + "id": 196, + "tag_id": 2, + "name": "后期" + }, + { + "id": 197, + "tag_id": 3, + "name": "调音" + }, + { + "id": 198, + "tag_id": 4, + "name": "剪辑" + }, + { + "id": 216, + "tag_id": 5, + "name": "视频制作" + }, + { + "id": 217, + "tag_id": 6, + "name": "参演" + }, + { + "id": 218, + "tag_id": 7, + "name": "填词" + }, + { + "id": 219, + "tag_id": 8, + "name": "作词" + }, + { + "id": 220, + "tag_id": 9, + "name": "作曲" + }, + { + "id": 221, + "tag_id": 10, + "name": "编曲" + }, + { + "id": 222, + "tag_id": 11, + "name": "演唱" + }, + { + "id": 223, + "tag_id": 12, + "name": "混音" + }, + { + "id": 224, + "tag_id": 13, + "name": "曲绘" + }, + { + "id": 225, + "tag_id": 14, + "name": "调教" + }, + { + "id": 226, + "tag_id": 15, + "name": "合剪" + }, + { + "id": 227, + "tag_id": 16, + "name": "导演" + }, + { + "id": 228, + "tag_id": 17, + "name": "编剧" + }, + { + "id": 229, + "tag_id": 18, + "name": "主演" + }, + { + "id": 230, + "tag_id": 19, + "name": "封面设计" + }, + { + "id": 231, + "tag_id": 20, + "name": "文案" + }, + { + "id": 232, + "tag_id": 21, + "name": "合舞" + }, + { + "id": 234, + "tag_id": 22, + "name": "舞者" + }, + { + "id": 235, + "tag_id": 23, + "name": "摄影" + }, + { + "id": 238, + "tag_id": 26, + "name": "字幕" + }, + { + "id": 239, + "tag_id": 27, + "name": "渲染" + }, + { + "id": 240, + "tag_id": 28, + "name": "模型" + }, + { + "id": 241, + "tag_id": 29, + "name": "动作" + }, + { + "id": 242, + "tag_id": 30, + "name": "调校" + }, + { + "id": 243, + "tag_id": 31, + "name": "演奏" + }, + { + "id": 244, + "tag_id": 32, + "name": "母带" + }, + { + "id": 250, + "tag_id": 33, + "name": "手工制作" + }, + { + "id": 251, + "tag_id": 34, + "name": "研发" + }, + { + "id": 268, + "tag_id": 35, + "name": "策划" + }, + { + "id": 283, + "tag_id": 36, + "name": "设计" + } + ] + }, + "tid_list": [ + { + "id": 160, + "parent": 0, + "parent_name": "", + "name": "生活", + "description": "生活", + "desc": "生活", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 95, + "children": [ + { + "id": 138, + "parent": 160, + "parent_name": "", + "name": "搞笑", + "description": "搞笑挑战、剪辑、表演、配音以及各类日常沙雕视频", + "desc": "搞笑挑战、剪辑、表演、配音以及各类日常沙雕视频", + "intro_original": "能够选择自制的必须是up主个人或工作室自己制作剪辑的视频,除此之外的搬运视频字幕制作,对于视频进行加速、慢放等简易二次创作,在视频中添加前后贴片或者打水印等行为均不被认作自制", + "intro_copy": "转载需写明请注明转载作品详细信息原作者、原标题及出处(需为该视频最原始出处,如所标注明显为非原始出处的话会被打回)", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 30, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 254, + "parent": 160, + "parent_name": "", + "name": "亲子", + "description": "与萌娃、母婴、育儿相关的视频,包括但不限于萌娃日常、萌娃才艺、亲子互动、亲子教育、母婴经验分享、少儿用品分享等", + "desc": "与萌娃、母婴、育儿相关的视频,包括但不限于萌娃日常、萌娃才艺、亲子互动、亲子教育、母婴经验分享、少儿用品分享等", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 29, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 250, + "parent": 160, + "parent_name": "", + "name": "出行", + "description": "旅行、户外、本地探店相关的视频,如旅行vlog、治愈系风景、城市景点攻略、自驾游、户外露营、徒步、骑行、钓鱼、乡村游、本地探店体验,演出看展等 ", + "desc": "旅行、户外、本地探店相关的视频,如旅行vlog、治愈系风景、城市景点攻略、自驾游、户外露营、徒步、骑行、钓鱼、乡村游、本地探店体验,演出看展等 ", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 28, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 251, + "parent": 160, + "parent_name": "", + "name": "三农", + "description": "与农业、农村、农民相关的视频,包括但不限于农村生活、户外打野、种植技术、养殖技术、三农资讯", + "desc": "与农业、农村、农民相关的视频,包括但不限于农村生活、户外打野、种植技术、养殖技术、三农资讯", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 26, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 239, + "parent": 160, + "parent_name": "", + "name": "家居房产", + "description": "与买房、装修、居家生活相关的视频,如买房租房、装修改造、智能家居、园艺绿植、居家好物等", + "desc": "与买房、装修、居家生活相关的视频,如买房租房、装修改造、智能家居、园艺绿植、居家好物等", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 25, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 161, + "parent": 160, + "parent_name": "", + "name": "手工", + "description": "与手工艺、DIY、发明创造相关的视频,例如手工记录、四坑手作、毛毡粘土、手账文具、写字书法、模型、玩具、解压、传统手艺、非遗等", + "desc": "与手工艺、DIY、发明创造相关的视频,例如手工记录、四坑手作、毛毡粘土、手账文具、写字书法、模型、玩具、解压、传统手艺、非遗等", + "intro_original": "能够选择自制的必须是up主个人或工作室自己制作剪辑的视频,除此之外的搬运视频字幕制作,对于视频进行加速、慢放等简易二次创作,在视频中添加前后贴片或者打水印等行为均不被认作自制;\nUp主可以在简介中简易介绍该手工艺品的制作流程和使用素材", + "intro_copy": "转载需写明请注明转载作品详细信息原作者、原标题及出处(需为该视频最原始出处,如所标注明显为非原始出处的话会被打回)", + "notice": "【手工类型】+所制作的手工物品名字,例:【手账】、【黏土】", + "copy_right": 0, + "show": true, + "rank": 10, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 162, + "parent": 160, + "parent_name": "", + "name": "绘画", + "description": "与绘画、艺术、设计相关的视频,例如绘画记录、数字绘画、手绘、潮流艺术、创意作画、绘画教程、美术分享、动漫、插画等", + "desc": "与绘画、艺术、设计相关的视频,例如绘画记录、数字绘画、手绘、潮流艺术、创意作画、绘画教程、美术分享、动漫、插画等", + "intro_original": "能够选择自制的必须是up主个人或工作室自己制作的视频,除此之外的搬运视频字幕制作,对于视频进行加速、慢放等简易二次创作,在视频中添加前后贴片或者打水印等行为均不被认作自制;\nUP可以在简介中介绍自己的绘画工具或软件,所画的人物名字及出处,大图地址等", + "intro_copy": "转载需写明请注明转载作品详细信息原作者、原标题及出处(需为该视频最原始出处,如所标注明显为非原始出处的话会被打回)", + "notice": "【绘画种类】+作品名/教程,例:【板绘】侧面眼睛教程", + "copy_right": 0, + "show": true, + "rank": 5, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 21, + "parent": 160, + "parent_name": "", + "name": "日常", + "description": "一般日常向的生活类视频", + "desc": "一般日常向的生活类视频", + "intro_original": "能够选择自制的必须是up主个人或工作室自己制作剪辑的视频,除此之外的搬运视频字幕制作,对于视频进行加速、慢放等简易二次创作,在视频中添加前后贴片或者打水印等行为均不被认作自制", + "intro_copy": "转载需写明请注明转载作品详细信息原作者、原标题及出处(需为该视频最原始出处,如所标注明显为非原始出处的话会被打回)", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 4, + "max_video_count": 50, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 4, + "parent": 0, + "parent_name": "", + "name": "游戏", + "description": "游戏", + "desc": "游戏", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 90, + "children": [ + { + "id": 17, + "parent": 4, + "parent_name": "", + "name": "单机游戏", + "description": "以单机或其联机模式为主要内容的相关视频", + "desc": "以单机或其联机模式为主要内容的相关视频", + "intro_original": "建议在简介和TAG中添加正确的游戏名,以便在分区和搜索中得到更好的展示。\n录制他人直播(包括授权转载、授权录制)不属于自制内容,请选转载。", + "intro_copy": "建议在简介和TAG中添加正确的游戏名。\n搬运转载内容必须添加原作者、原链接地址信息。录制他人直播内容必须添加原主播信息、直播时间。\n未添加正确转载、录播信息的稿件可能被打回。", + "notice": "【UP主/节目名】+《游戏名》+主要标题+期号", + "copy_right": 0, + "show": true, + "rank": 35, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 65, + "parent": 4, + "parent_name": "", + "name": "网络游戏", + "description": "多人在线游戏为主要内容的相关视频", + "desc": "多人在线游戏为主要内容的相关视频", + "intro_original": "建议在简介和TAG中添加正确的游戏名,以便在分区和搜索中得到更好的展示。\n录制他人直播(包括授权转载、授权录制)不属于自制内容,请选转载。", + "intro_copy": "建议在简介和TAG中添加正确的游戏名。\n搬运转载内容请添加原作者、原链接地址信息。录制他人直播内容请添加原主播信息、直播时间。\n未添加正确转载、录播信息的稿件可能被打回。", + "notice": "【UP主/节目名】+《游戏名》+主要标题+期号", + "copy_right": 0, + "show": true, + "rank": 30, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 172, + "parent": 4, + "parent_name": "", + "name": "手机游戏", + "description": "手机及平板设备平台上的游戏相关视频", + "desc": "手机及平板设备平台上的游戏相关视频", + "intro_original": "建议在简介和TAG中添加正确的游戏名及操作平台,以便在分区和搜索中得到更好的展示。\n录制他人直播(包括授权转载、授权录制)不属于自制内容,请选转载。", + "intro_copy": "建议在简介和TAG中添加正确的游戏名。\n搬运转载内容请添加原作者、原链接地址信息。录制他人直播内容请添加原主播信息、直播时间。\n未添加正确转载、录播信息的稿件可能被打回。", + "notice": "【UP主/节目名】+《游戏名》+主要标题", + "copy_right": 0, + "show": true, + "rank": 25, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 171, + "parent": 4, + "parent_name": "", + "name": "电子竞技", + "description": "电子竞技游戏项目为主要内容的相关视频", + "desc": "电子竞技游戏项目为主要内容的相关视频", + "intro_original": "建议在简介和TAG中添加正确的游戏名、赛事名称和场次,以便在分区和搜索中得到更好的展示。\n录制他人直播(包括授权转载、授权录制)不属于自制内容,请选转载。", + "intro_copy": "建议在简介和TAG中添加正确的游戏名、赛事名称和场次。\n搬运转载内容请添加原作者、原链接地址信息。录制他人直播内容请添加原主播信息、直播时间。\n未添加正确转载、录播信息的稿件可能被打回。", + "notice": "【UP主/节目名】+《游戏名》+主要标题+期号", + "copy_right": 0, + "show": true, + "rank": 20, + "max_video_count": 100, + "request_id": "" + }, + { + "id": 173, + "parent": 4, + "parent_name": "", + "name": "桌游棋牌", + "description": "桌游、棋牌、卡牌、聚会游戏等相关视频", + "desc": "桌游、棋牌、卡牌、聚会游戏等相关视频", + "intro_original": "建议在简介和TAG中添加正确的游戏名,以便在分区和搜索中得到更好的展示。\n录制他人直播(包括授权转载、授权录制)不属于自制内容,请选转载。", + "intro_copy": "建议在简介和TAG中添加正确的游戏名。\n搬运转载内容请添加原作者、原链接地址信息。录制他人直播内容请添加原主播信息、直播时间。\n未添加正确转载、录播信息的稿件可能被打回。", + "notice": "【UP主/游戏名/节目名】+主要标题", + "copy_right": 0, + "show": true, + "rank": 15, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 136, + "parent": 4, + "parent_name": "", + "name": "音游", + "description": "通过配合音乐与节奏而进行的音乐类游戏视频", + "desc": "通过配合音乐与节奏而进行的音乐类游戏视频", + "intro_original": "建议在简介和TAG中添加正确的游戏名、曲名、难度及操作平台,以便在分区和搜索中得到更好的展示。\n录制他人直播(包括授权转载、授权录制)不属于自制内容,请选转载。", + "intro_copy": "建议在简介和TAG中添加正确的游戏名、曲名、难度及操作平台。\n搬运转载内容请添加原作者、原链接地址信息。录制他人直播内容请添加原主播信息、直播时间。\n未添加正确转载、录播信息的稿件可能被打回。", + "notice": "【UP主/游戏名】+曲名+难度+程度", + "copy_right": 0, + "show": true, + "rank": 10, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 121, + "parent": 4, + "parent_name": "", + "name": "GMV", + "description": "使用游戏内容或CG为素材制作的MV类型的视频", + "desc": "使用游戏内容或CG为素材制作的MV类型的视频", + "intro_original": "建议在简介和TAG中添加相关素材的游戏名、背景音乐信息,以便在分区和搜索中得到更好的展示。", + "intro_copy": "搬运转载内容请添加原作者、原链接地址信息。未添加正确转载信息的稿件可能被打回。", + "notice": "【游戏名/风格】主要标题 ", + "copy_right": 0, + "show": true, + "rank": 5, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 19, + "parent": 4, + "parent_name": "", + "name": "Mugen", + "description": "使用Mugen引擎制作或与Mugen相关的游戏视频", + "desc": "使用Mugen引擎制作或与Mugen相关的游戏视频", + "intro_original": "建议在简介和TAG中添加人物、分级、杯赛规则、描述等信息,以便在分区和搜索中得到更好的展示。", + "intro_copy": "搬运转载内容请添加原作者、原链接地址信息。 未添加正确转载信息的稿件可能被打回。", + "notice": "【Mugen】+杯名+级别+角色名+主要标题", + "copy_right": 0, + "show": true, + "rank": 1, + "max_video_count": 50, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 5, + "parent": 0, + "parent_name": "", + "name": "娱乐", + "description": "娱乐", + "desc": "娱乐", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 85, + "children": [ + { + "id": 71, + "parent": 5, + "parent_name": "", + "name": "综艺", + "description": "所有综艺相关,全部一手掌握!", + "desc": "所有综艺相关,全部一手掌握!", + "intro_original": "若为自制综艺的正片,需有该自制综艺的详细介绍和信息\n例:《我们相爱吧》是江苏卫视和韩国MBC电视台联合制作的明星恋爱实境真人秀节目,通过明星组成假想情侣,进行假想情侣\n若为综艺剪辑视频,需要在简介表明该视频中所用到的综艺名称。", + "intro_copy": "若为转载综艺的正片,需有该综艺节目的原标题、期数(时间)以及翻译字幕组\n例:【乃木坂不够热】 160412 NOGIBINGO!6 EP01+NOGIROOM4 EP01", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟", + "copy_right": 0, + "show": true, + "rank": 10, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 241, + "parent": 5, + "parent_name": "", + "name": "娱乐杂谈", + "description": "娱乐人物解读、娱乐热点点评、娱乐行业分析", + "desc": "娱乐人物解读、娱乐热点点评、娱乐行业分析", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟", + "copy_right": 0, + "show": true, + "rank": 9, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 242, + "parent": 5, + "parent_name": "", + "name": "粉丝创作", + "description": "粉丝向创作视频", + "desc": "粉丝向创作视频", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 8, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 137, + "parent": 5, + "parent_name": "", + "name": "明星综合", + "description": "娱乐圈动态、明星资讯相关", + "desc": "娱乐圈动态、明星资讯相关", + "intro_original": "简介需要写明素材来源、使用BGM等,列出相关明星。", + "intro_copy": "简介需要写明原出处或原作者,列出相关明星。", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 7, + "max_video_count": 100, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 36, + "parent": 0, + "parent_name": "", + "name": "知识", + "description": "知识", + "desc": "知识", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 80, + "children": [ + { + "id": 201, + "parent": 36, + "parent_name": "", + "name": "科学科普", + "description": "以自然科学或基于自然科学思维展开的知识视频", + "desc": "以自然科学或基于自然科学思维展开的知识视频", + "intro_original": "填写更全面的相关信息,让更多的人能找到你的视频吧!", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 100, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 124, + "parent": 36, + "parent_name": "", + "name": "社科·法律·心理", + "description": "法律/心理/社会学/观点输出类内容等", + "desc": "法律/心理/社会学/观点输出类内容等", + "intro_original": "填写更全面的相关信息,让更多的人能找到你的视频吧!", + "intro_copy": "转载稿件需标明出处,请注明原作者、原作者频道名或原作者投稿地址。\n可对相关内容进行补充说明。\n请勿加入涉政或具较大争议性的文字简介,否则将做打回处理。\n如是系列,也可附带上期视频地址。", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 95, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 228, + "parent": 36, + "parent_name": "", + "name": "人文历史", + "description": "人物/文学/历史/文化/奇闻/艺术等", + "desc": "人物/文学/历史/文化/奇闻/艺术等", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 91, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 207, + "parent": 36, + "parent_name": "", + "name": "财经商业", + "description": "财经/商业/经济金融/互联网等", + "desc": "财经/商业/经济金融/互联网等", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 90, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 208, + "parent": 36, + "parent_name": "", + "name": "校园学习", + "description": "学习方法及经验、课程教学、校园干货分享等", + "desc": "学习方法及经验、课程教学、校园干货分享等", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 85, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 209, + "parent": 36, + "parent_name": "", + "name": "职业职场", + "description": "职场技能、职业分享、行业分析、求职规划等", + "desc": "职场技能、职业分享、行业分析、求职规划等", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 80, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 229, + "parent": 36, + "parent_name": "", + "name": "设计·创意", + "description": "以设计美学或基于设计思维展开的知识视频", + "desc": "以设计美学或基于设计思维展开的知识视频", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 76, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 122, + "parent": 36, + "parent_name": "", + "name": "野生技能协会", + "description": "技能展示或技能教学分享类视频", + "desc": "技能展示或技能教学分享类视频", + "intro_original": "可对视频内容进行补充说明,并对所使用的视频素材进行标明。\n如是系列,也可附带上期视频地址。\n请勿加入涉政或具较大争议性的文字简介,否则将做打回处理。", + "intro_copy": "转载稿件需标明出处,请注明原作者、原作者频道名或原作者投稿地址。\n可对相关内容进行补充说明。\n请勿加入涉政或具较大争议性的文字简介,否则将做打回处理。\n如是系列,也可附带上期视频地址。", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 75, + "max_video_count": 100, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 181, + "parent": 0, + "parent_name": "", + "name": "影视", + "description": "影视", + "desc": "影视", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 80, + "children": [ + { + "id": 182, + "parent": 181, + "parent_name": "", + "name": "影视杂谈", + "description": "影视评论、解说、吐槽、科普、配音等", + "desc": "影视评论、解说、吐槽、科普、配音等", + "intro_original": "建议在简介和TAG中添加正确的影视剧名等信息,以便在分区和搜索中得到更好的展示。", + "intro_copy": "建议在简介和TAG中添加正确的影视剧名等信息。\n搬运转载内容必须添加原作者、原链接地址信息。", + "notice": "【UP主/节目名】+《影视剧名》(选填)+主要标题", + "copy_right": 0, + "show": true, + "rank": 15, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 183, + "parent": 181, + "parent_name": "", + "name": "影视剪辑", + "description": "对影视素材进行剪辑再创作的视频", + "desc": "对影视素材进行剪辑再创作的视频", + "intro_original": "建议在简介中添加正确的影视剧名、BGM等信息,以便在分区和搜索中得到更好的展示。CUT不属于自制内容,请选转载。", + "intro_copy": "建议在简介中添加正确的影视剧名等信息。搬运转载内容必须添加原作者、原链接地址信息。", + "notice": "【剪辑类型】+主要标题", + "copy_right": 0, + "show": true, + "rank": 10, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 85, + "parent": 181, + "parent_name": "", + "name": "小剧场", + "description": "单线或连续剧情,且有演绎成分的小剧场(短剧)内容", + "desc": "单线或连续剧情,且有演绎成分的小剧场(短剧)内容", + "intro_original": "", + "intro_copy": "搬运转载内容必须添加原短片相关信息及原链接地址信息等。", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 5, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 256, + "parent": 181, + "parent_name": "", + "name": "短片", + "description": "各种类型的短片,包括但不限于真人故事短片、微电影、学生作品、创意短片、励志短片、广告短片、摄影短片、纪实短片、科幻短片等", + "desc": "各种类型的短片,包括但不限于真人故事短片、微电影、学生作品、创意短片、励志短片、广告短片、摄影短片、纪实短片、科幻短片等", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!\n", + "copy_right": 0, + "show": true, + "rank": 3, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 184, + "parent": 181, + "parent_name": "", + "name": "预告·资讯", + "description": "影视类相关资讯,预告,花絮等视频", + "desc": "影视类相关资讯,预告,花絮等视频", + "intro_original": "建议在简介中添加正确的影视剧名等信息,以便在分区和搜索中得到更好的展示。", + "intro_copy": "建议在简介中添加正确的影视剧名等信息。搬运转载内容必须添加原链接地址信息。", + "notice": "【节目类型/节目名】+《影视剧名》+主要标题", + "copy_right": 0, + "show": true, + "rank": 0, + "max_video_count": 50, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 3, + "parent": 0, + "parent_name": "", + "name": "音乐", + "description": "音乐", + "desc": "音乐", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 75, + "children": [ + { + "id": 31, + "parent": 3, + "parent_name": "", + "name": "翻唱", + "description": "对曲目的人声再演绎视频。", + "desc": "对曲目的人声再演绎视频。", + "intro_original": "填写更全面的相关信息,让更多的人能找到你的视频吧!\n·请填写歌曲的详细制作人员信息,如后期、PV作者等。\n·请根据实际情况慎重选择投稿类型(自制或转载)。 ", + "intro_copy": "请认真填写您所上传视频的相关信息,如曲名,作者,来源出处地址等重要信息,以及其他有利于检索的关键信息,便于您的稿件能合理高效地检索及展示", + "notice": "建议使用【翻唱者】翻唱曲目 的类似格式填写标题", + "copy_right": 0, + "show": true, + "rank": 80, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 59, + "parent": 3, + "parent_name": "", + "name": "演奏", + "description": "乐器和非传统乐器器材的演奏作品。", + "desc": "乐器和非传统乐器器材的演奏作品。", + "intro_original": "填写更全面的相关信息,让更多的人能找到你的视频吧!\n·请填写歌曲的详细制作人员及所涉及到的乐器,如演奏、摄影人员等。\n·请根据实际情况慎重选择投稿类型(自制或转载)。", + "intro_copy": "请认真填写您所上传视频的相关信息,如曲名,作者,来源出处地址等重要信息,以及其他有利于检索的关键信息,便于您的稿件能合理高效地检索及展示", + "notice": "建议使用【乐器/器材名】曲名 的类似格式填写标题", + "copy_right": 0, + "show": true, + "rank": 70, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 130, + "parent": 3, + "parent_name": "", + "name": "音乐综合", + "description": "该分区收录所有无法被收纳到其他音乐二级分区的音乐类视频。包括但不限于个人选集以及任何形式的曲包。", + "desc": "该分区收录所有无法被收纳到其他音乐二级分区的音乐类视频。包括但不限于个人选集以及任何形式的曲包。", + "intro_original": "填写更全面的相关信息,让更多的人能找到你的视频吧!\n·请填写选集中涉及歌曲的信息,如“歌手 - 歌曲名”。\n·对他人歌曲进行变速、升降调、倒放等处理而产生的稿件,属于音乐选集区。\n·请根据实际情况慎重选择投稿类型(自制或转载)。", + "intro_copy": "请认真填写您所上传视频的相关信息,如曲名,作者,来源出处地址等重要信息,以及其他有利于检索的关键信息,便于您的稿件能合理高效地检索及展示", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 69, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 29, + "parent": 3, + "parent_name": "", + "name": "音乐现场", + "description": "该分区收录户外或专业演出场所公开进行音乐表演的实况视频,包括但不限于官方/个人拍摄的综艺节目、音乐剧、音乐节、演唱会等音乐演出内容。", + "desc": "该分区收录户外或专业演出场所公开进行音乐表演的实况视频,包括但不限于官方/个人拍摄的综艺节目、音乐剧、音乐节、演唱会等音乐演出内容。", + "intro_original": "填写更全面的相关信息,让更多的人能找到你的视频吧!\n·请填写歌曲的详细制作人员信息,如词、曲作者以及所属版权公司等。\n·请根据实际情况慎重选择投稿类型(自制或转载)。", + "intro_copy": "请认真填写您所上传视频的相关信息,如曲名,作者,来源出处地址等重要信息,以及其他有利于检索的关键信息,便于您的稿件能合理高效地检索及展示", + "notice": "建议使用 【音乐人】曲目名【现场标题】 的类似格式填写标题", + "copy_right": 0, + "show": true, + "rank": 65, + "max_video_count": 100, + "request_id": "" + }, + { + "id": 193, + "parent": 3, + "parent_name": "", + "name": "MV", + "description": "该分区收录为音乐作品配合拍摄或制作的音乐录影带(Music Video),包含但不限于官方MV或MV预告,以及自制拍摄、剪辑、翻拍的MV。", + "desc": "该分区收录为音乐作品配合拍摄或制作的音乐录影带(Music Video),包含但不限于官方MV或MV预告,以及自制拍摄、剪辑、翻拍的MV。", + "intro_original": "", + "intro_copy": "", + "notice": "建议使用 【音乐人】曲目名 的类似格式填写标题", + "copy_right": 0, + "show": true, + "rank": 60, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 244, + "parent": 3, + "parent_name": "", + "name": "音乐教学", + "description": "该分区收录以音乐教学为目的的内容,包括但不限于声乐教学、乐器教学、编曲制作教学、乐器设备测评等。", + "desc": "该分区收录以音乐教学为目的的内容,包括但不限于声乐教学、乐器教学、编曲制作教学、乐器设备测评等。", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 55, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 28, + "parent": 3, + "parent_name": "", + "name": "原创音乐", + "description": "以任何题材创作的、以音乐主体为主要原创考量标准的原创歌曲及纯音乐,包括对音乐歌曲中曲的改编、重编曲及remix。", + "desc": "以任何题材创作的、以音乐主体为主要原创考量标准的原创歌曲及纯音乐,包括对音乐歌曲中曲的改编、重编曲及remix。", + "intro_original": "填写更全面的相关信息,让更多的人能找到你的视频吧!\n·请填写歌曲的详细制作人员信息,如词、曲作者等。\n·如无作品原作者授权,请勿使用他人音乐作品投稿至原创音乐区。\n·请根据实际情况慎重选择投稿类型(自制或转载)", + "intro_copy": "原创音乐区原则上不接受转载视频,请获取作品原作者的有效授权并投稿至其他音乐相关分区。多次错误投稿的账号将有可能会被处理,请谨慎投稿。", + "notice": "建议使用【歌手名】曲目【风格】 的类似格式填写标题", + "copy_right": 0, + "show": true, + "rank": 50, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 30, + "parent": 3, + "parent_name": "", + "name": "VOCALOID·UTAU", + "description": "以VOCALOID等歌声合成引擎为基础,运用各类音源进行的创作。", + "desc": "以VOCALOID等歌声合成引擎为基础,运用各类音源进行的创作。", + "intro_original": "填写更全面的相关信息,让更多的人能找到你的视频吧!\n·请填写歌曲的详细制作人员信息,如词、曲、调教作者等。\n·请根据实际情况慎重选择投稿类型(自制或转载)。 ", + "intro_copy": "请认真填写您所上传视频的相关信息,如曲名,作者,来源出处地址等重要信息,以及其他有利于检索的关键信息,便于您的稿件能合理高效地检索及展示", + "notice": "建议使用【歌手名】曲目【作者】 的类似格式填写标题", + "copy_right": 0, + "show": true, + "rank": 40, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 243, + "parent": 3, + "parent_name": "", + "name": "乐评盘点", + "description": "该分区收录音乐资讯、音乐点评盘点、音乐故事等,包括但不限于音乐类新闻、盘点、点评、reaction、榜单、采访、幕后故事、唱片开箱等。", + "desc": "该分区收录音乐资讯、音乐点评盘点、音乐故事等,包括但不限于音乐类新闻、盘点、点评、reaction、榜单、采访、幕后故事、唱片开箱等。", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 30, + "max_video_count": 50, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 1, + "parent": 0, + "parent_name": "", + "name": "动画", + "description": "动画", + "desc": "动画", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 65, + "children": [ + { + "id": 24, + "parent": 1, + "parent_name": "", + "name": "MAD·AMV", + "description": "具有一定创作度的动/静画二次创作视频", + "desc": "具有一定创作度的动/静画二次创作视频", + "intro_original": "填写更全面的相关信息,让更多的人能找到你的视频吧! \nBGM 建议格式:音乐人-音乐名\n使用素材 举例:埃罗芒阿老师 Re:CREATORS\n使用别人的MAD作为自己作品的素材是不允许的!", + "intro_copy": "转载稿件要注明出处哦!出处包括:\n源地址 如amvnews, YouTube, Nico的作品发布链接\n原作者 举例:Pluvia\n原标题 举例:蒼天の白鯨と始まりのゼロ\n填写更全面的相关信息,让更多的人能找到你的视频吧!\nBGM 建议格式:音乐人-音乐名\n使用素材 举例:从零开始的魔法书 小魔女学园", + "notice": "建议使用【作品题材】+其他部分 的形式填写标题", + "copy_right": 0, + "show": true, + "rank": 20, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 25, + "parent": 1, + "parent_name": "", + "name": "MMD·3D", + "description": "使用MMD和其他3D软件制作的视频", + "desc": "使用MMD和其他3D软件制作的视频", + "intro_original": "借物表!借物表! 借物表! 重要的事情说三遍!模型!动作!场景!镜头!\n使用软件 举例:Maya,MMD(MikuMikuDance),MME\n填写详细的借物信息,让更多的人看到吧!", + "intro_copy": "转载稿件要注明出处哦!出处包括:\n源地址 如YouTube, Nico的作品发布链接\n原作者 举例:minusT\n原标题 举例:【3D東方】 Subterranean Stars", + "notice": "建议使用【作品题材】+其他部分 的形式填写标题", + "copy_right": 0, + "show": true, + "rank": 15, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 47, + "parent": 1, + "parent_name": "", + "name": "短片·手书", + "description": "追求个人特色和创意表达的动画短片及手书(绘)", + "desc": "追求个人特色和创意表达的动画短片及手书(绘)", + "intro_original": "(以下仅作参考)\n\n简介:……\n\n视频类型:原创动画、同人手书、配音、广播剧、有声漫等\n\n相关题材:非二次创作可填原创题材,题材可填写多个", + "intro_copy": "转载稿件要注明出处哦!出处包括:\n源地址 如vimeo, YouTube, Nico的作品发布链接\n原作者ID\n来自多个短片的合集也要注明出处哦!", + "notice": "标题建议提炼内容亮点", + "copy_right": 0, + "show": true, + "rank": 6, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 257, + "parent": 1, + "parent_name": "", + "name": "配音", + "description": "使用ACGN相关画面或台本素材进行人工配音创作的内容", + "desc": "使用ACGN相关画面或台本素材进行人工配音创作的内容", + "intro_original": "(以下仅作参考)\n\n相关题材:配音素材来源,如动画IP名称、站内授权稿件等\n\n简介:……", + "intro_copy": "(以下仅作参考)\n\n相关题材:配音素材来源,如动画IP名称、站内授权稿件等\n\n简介:……", + "notice": "标题建议提炼内容亮点", + "copy_right": 0, + "show": true, + "rank": 5, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 210, + "parent": 1, + "parent_name": "", + "name": "手办·模玩", + "description": "手办模玩的测评、改造或其他衍生内容", + "desc": "手办模玩的测评、改造或其他衍生内容", + "intro_original": "", + "intro_copy": "", + "notice": "【类型(拆箱、测评、改造等)】+ 标题", + "copy_right": 0, + "show": true, + "rank": 4, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 86, + "parent": 1, + "parent_name": "", + "name": "特摄", + "description": "特摄相关衍生视频", + "desc": "特摄相关衍生视频", + "intro_original": "建议在简介和TAG中添加正确的特摄片名等信息,以便在分区和搜索中得到更好的展示。", + "intro_copy": "建议在简介和TAG中添加正确的特摄片名等信息。搬运转载内容必须添加原作者、原链接地址信息。 ", + "notice": "【特摄片名】+主要标题", + "copy_right": 0, + "show": true, + "rank": 3, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 253, + "parent": 1, + "parent_name": "", + "name": "动漫杂谈", + "description": "ACGN文化圈杂谈内容", + "desc": "ACGN文化圈杂谈内容", + "intro_original": "(以下仅作参考)\n\n视频类型:如鉴赏、吐槽、评点、解说、推荐、科普、导视、总结回顾\n\n相关题材:如东方、JOJO、鬼灭、魔法少女小圆\n\n简介:……", + "intro_copy": "转载稿件要注明出处哦!出处包括:\n源地址 如vimeo, YouTube, Nico的作品发布链接\n原作者ID\n来自多个短片的合集也要注明出处哦!", + "notice": "标题建议提炼内容亮点", + "copy_right": 0, + "show": true, + "rank": 2, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 27, + "parent": 1, + "parent_name": "", + "name": "综合", + "description": "以动画及相关内容为素材的创作", + "desc": "以动画及相关内容为素材的创作", + "intro_original": "你想对观众说些什么呢?(以下仅作参考)\n\n视频类型:如音频替换、恶搞改编、排行榜\n\n相关题材:如东方、JOJO、鬼灭、间谍过家家\n\n简介:……", + "intro_copy": "转载稿件要注明出处哦!出处包括:\n• 源地址 如YouTube,Nico的作品发布链接\n• 原作者 举例:毒舌老外gigguk\n• 原作者创作不易,注明转载来源才是好孩子!", + "notice": "标题建议提炼内容亮点", + "copy_right": 0, + "show": true, + "rank": 1, + "max_video_count": 50, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 155, + "parent": 0, + "parent_name": "", + "name": "时尚", + "description": "时尚", + "desc": "时尚", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 60, + "children": [ + { + "id": 157, + "parent": 155, + "parent_name": "", + "name": "美妆护肤", + "description": "彩妆护肤、发型美甲、仿妆、美容美体、口腔、拍照技巧、颜值分析等变美相关内容分享或产品测评", + "desc": "彩妆护肤、发型美甲、仿妆、美容美体、口腔、拍照技巧、颜值分析等变美相关内容分享或产品测评", + "intro_original": "原创妆容/仿妆/cos妆;使用产品详细列表,如canmake花瓣腮红3#。请不要出现购买链接哟~", + "intro_copy": "转载自【YouTube/优酷/腾讯视频等视频网站】的【美妆/服饰/健身】内容。注明原PO的ID及联系方式,如脸书、微博等。若获得原PO授权请特别注明。产品详细列表,如canmake花瓣腮红3#。请不要出现购买链接哟~", + "notice": "清晰明了表明内容亮点的标题会更受众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 20, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 252, + "parent": 155, + "parent_name": "", + "name": "仿妆cos", + "description": "对明星、影视剧、文学作品、动漫、番剧、绘画作品、游戏等人物角色妆容或整体妆造进行模仿、还原、展示、演绎的内容", + "desc": "对明星、影视剧、文学作品、动漫、番剧、绘画作品、游戏等人物角色妆容或整体妆造进行模仿、还原、展示、演绎的内容", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 18, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 158, + "parent": 155, + "parent_name": "", + "name": "穿搭", + "description": "穿搭风格、穿搭技巧的展示分享,涵盖衣服、鞋靴、箱包配件、配饰(帽子、钟表、珠宝首饰)等", + "desc": "穿搭风格、穿搭技巧的展示分享,涵盖衣服、鞋靴、箱包配件、配饰(帽子、钟表、珠宝首饰)等", + "intro_original": "穿搭风格,如日常、原宿、制服、lo装、汉服等。", + "intro_copy": "转载自【YouTube/优酷/腾讯视频等视频网站】的【美妆/服饰/健身】内容。注明原PO的ID及联系方式,如脸书、微博等。若获得原PO授权请特别注明。产品详细列表,如canmake花瓣腮红3#。请不要出现购买链接哟~", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 15, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 159, + "parent": 155, + "parent_name": "", + "name": "时尚潮流", + "description": "时尚街拍、时装周、时尚大片,时尚品牌、潮流等行业相关记录及知识科普", + "desc": "时尚街拍、时装周、时尚大片,时尚品牌、潮流等行业相关记录及知识科普", + "intro_original": "建议注明品牌(系列)、时间、地点、模特名字,最好来一发解析哦", + "intro_copy": "转载自【YouTube/优酷/腾讯视频等视频网站】的内容。品牌(系列)、时间、地点、模特名字,最好来一发解析❤请不要出现购买链接及和视频内容无关二维码", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 10, + "max_video_count": 50, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 211, + "parent": 0, + "parent_name": "", + "name": "美食", + "description": "美食", + "desc": "美食", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 55, + "children": [ + { + "id": 76, + "parent": 211, + "parent_name": "", + "name": "美食制作", + "description": "包括但不限于料理制作教程,各种菜系、甜点、速食、饮料、小吃制作等", + "desc": "包括但不限于料理制作教程,各种菜系、甜点、速食、饮料、小吃制作等", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 100, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 212, + "parent": 211, + "parent_name": "", + "name": "美食侦探", + "description": "包括但不限于探店、街边美食、饮食文化,发现特色地域美食、路边摊与热门网红食物等", + "desc": "包括但不限于探店、街边美食、饮食文化,发现特色地域美食、路边摊与热门网红食物等", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 95, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 213, + "parent": 211, + "parent_name": "", + "name": "美食测评", + "description": "包括但不限于边吃边聊、测评推荐或吐槽各种美食等", + "desc": "包括但不限于边吃边聊、测评推荐或吐槽各种美食等", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 90, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 214, + "parent": 211, + "parent_name": "", + "name": "田园美食", + "description": "包括但不限于乡野美食、三农采摘、钓鱼赶海等", + "desc": "包括但不限于乡野美食、三农采摘、钓鱼赶海等", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 85, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 215, + "parent": 211, + "parent_name": "", + "name": "美食记录", + "description": "记录一日三餐,美食vlog、料理、便当、饮品合集、美食小剧场等", + "desc": "记录一日三餐,美食vlog、料理、便当、饮品合集、美食小剧场等", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 80, + "max_video_count": 50, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 223, + "parent": 0, + "parent_name": "", + "name": "汽车", + "description": "汽车", + "desc": "汽车", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 52, + "children": [ + { + "id": 245, + "parent": 223, + "parent_name": "", + "name": "赛车", + "description": "一切以汽车运动为代表的车手、汽车赛事、赛道、赛车模拟器、赛车、卡丁车及赛车衍生品相关的视频", + "desc": "一切以汽车运动为代表的车手、汽车赛事、赛道、赛车模拟器、赛车、卡丁车及赛车衍生品相关的视频", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 60, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 246, + "parent": 223, + "parent_name": "", + "name": "改装玩车", + "description": "汽车文化、玩车改装为代表的相关内容。包括但不限于痛车、汽车模型、改装车、自制车、老车修复、汽车文化知识、汽车历史、汽车聚会以及汽车创意玩法等衍生相关视频", + "desc": "汽车文化、玩车改装为代表的相关内容。包括但不限于痛车、汽车模型、改装车、自制车、老车修复、汽车文化知识、汽车历史、汽车聚会以及汽车创意玩法等衍生相关视频", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 56, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 247, + "parent": 223, + "parent_name": "", + "name": "新能源车", + "description": "新能源汽车相关内容,包括电动汽车、混合动力汽车等车型种类,包含不限于新车资讯、试驾体验、专业评测、技术解读、知识科普等内容", + "desc": "新能源汽车相关内容,包括电动汽车、混合动力汽车等车型种类,包含不限于新车资讯、试驾体验、专业评测、技术解读、知识科普等内容", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 55, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 248, + "parent": 223, + "parent_name": "", + "name": "房车", + "description": "房车及营地相关内容,包括不限于产品介绍、驾驶体验、房车生活和房车旅行等内容", + "desc": "房车及营地相关内容,包括不限于产品介绍、驾驶体验、房车生活和房车旅行等内容", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 30, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 240, + "parent": 223, + "parent_name": "", + "name": "摩托车", + "description": "与摩托车相关的视频,包括但不限于摩托车骑行、试驾、装备测评、教学、赛事、剪辑等相关内容", + "desc": "与摩托车相关的视频,包括但不限于摩托车骑行、试驾、装备测评、教学、赛事、剪辑等相关内容", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 25, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 227, + "parent": 223, + "parent_name": "", + "name": "购车攻略", + "description": "新车、二手车测评试驾,购车推荐,交易避坑攻略等", + "desc": "新车、二手车测评试驾,购车推荐,交易避坑攻略等", + "intro_original": "", + "intro_copy": "", + "notice": "丰富详实的购车建议和新车体验", + "copy_right": 0, + "show": true, + "rank": 10, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 176, + "parent": 223, + "parent_name": "", + "name": "汽车生活", + "description": "和汽车等交通工具相关的一切泛化内容的视频", + "desc": "和汽车等交通工具相关的一切泛化内容的视频", + "intro_original": "", + "intro_copy": "", + "notice": "分享汽车及出行相关的生活体验类视频", + "copy_right": 0, + "show": true, + "rank": 5, + "max_video_count": 50, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 234, + "parent": 0, + "parent_name": "", + "name": "运动", + "description": "运动", + "desc": "运动", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 51, + "children": [ + { + "id": 235, + "parent": 234, + "parent_name": "", + "name": "篮球", + "description": "与篮球相关的视频,包括但不限于篮球赛事、教学、评述、剪辑、剧情等相关内容", + "desc": "与篮球相关的视频,包括但不限于篮球赛事、教学、评述、剪辑、剧情等相关内容", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 60, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 249, + "parent": 234, + "parent_name": "", + "name": "足球", + "description": "与足球相关的视频,包括但不限于足球赛事、教学、评述、剪辑、剧情等相关内容", + "desc": "与足球相关的视频,包括但不限于足球赛事、教学、评述、剪辑、剧情等相关内容", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 55, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 164, + "parent": 234, + "parent_name": "", + "name": "健身", + "description": "与健身相关的视频,包括但不限于健身、健美、操舞、瑜伽、普拉提、跑步、街健、健康餐、健身小剧场等内容", + "desc": "与健身相关的视频,包括但不限于健身、健美、操舞、瑜伽、普拉提、跑步、街健、健康餐、健身小剧场等内容", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 50, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 236, + "parent": 234, + "parent_name": "", + "name": "竞技体育", + "description": "与竞技体育相关的视频,包括但不限于乒乓、羽毛球、排球、赛车等竞技项目的赛事、评述、剪辑、剧情等相关内容", + "desc": "与竞技体育相关的视频,包括但不限于乒乓、羽毛球、排球、赛车等竞技项目的赛事、评述、剪辑、剧情等相关内容", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 40, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 237, + "parent": 234, + "parent_name": "", + "name": "运动文化", + "description": "与运动文化相关的视频,包括但不限于球鞋、球衣、球星卡等运动衍生品的分享、解读,体育产业的分析、科普等相关内容", + "desc": "与运动文化相关的视频,包括但不限于球鞋、球衣、球星卡等运动衍生品的分享、解读,体育产业的分析、科普等相关内容", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 30, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 238, + "parent": 234, + "parent_name": "", + "name": "运动综合", + "description": "与运动综合相关的视频,包括但不限于钓鱼、骑行、滑板等日常运动分享、教学、Vlog等相关内容", + "desc": "与运动综合相关的视频,包括但不限于钓鱼、骑行、滑板等日常运动分享、教学、Vlog等相关内容", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 20, + "max_video_count": 50, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 188, + "parent": 0, + "parent_name": "", + "name": "科技", + "description": "科技", + "desc": "科技", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 50, + "children": [ + { + "id": 95, + "parent": 188, + "parent_name": "", + "name": "数码", + "description": "手机/电脑/相机/影音智能设备等", + "desc": "手机/电脑/相机/影音智能设备等", + "intro_original": "请注明产品名称,可对相关内容进行补充说明。\n不建议填写含贩售信息的相关网站地址。\n请勿加入涉政或具较大争议性的文字简介,否则将做打回处理。\n如是系列,可附带上期视频地址。", + "intro_copy": "转载稿件需标明出处,请注明原作者、原作者频道名或原作者投稿地址。\n可对相关内容进行补充说明。\n请勿加入涉政或具较大争议性的文字简介,否则将做打回处理。\n如是系列,也可附带上期视频地址。", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 25, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 230, + "parent": 188, + "parent_name": "", + "name": "软件应用", + "description": "围绕各类系统软件、应用软件、网站的相关视频", + "desc": "围绕各类系统软件、应用软件、网站的相关视频", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 20, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 231, + "parent": 188, + "parent_name": "", + "name": "计算机技术", + "description": "软硬件开发/人工智能/大数据/深度学习/IT运维等", + "desc": "软硬件开发/人工智能/大数据/深度学习/IT运维等", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 15, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 232, + "parent": 188, + "parent_name": "", + "name": "科工机械", + "description": "航空航天/工程建设/电子工程/机械制造/海洋工程等", + "desc": "航空航天/工程建设/电子工程/机械制造/海洋工程等", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 10, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 233, + "parent": 188, + "parent_name": "", + "name": "极客DIY", + "description": "硬核制作/发明创造/技术创新/极客文化等", + "desc": "硬核制作/发明创造/技术创新/极客文化等", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 5, + "max_video_count": 50, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 217, + "parent": 0, + "parent_name": "", + "name": "动物圈", + "description": "动物圈", + "desc": "动物圈", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 50, + "children": [ + { + "id": 218, + "parent": 217, + "parent_name": "", + "name": "喵星人", + "description": "与猫相关的视频,包括但不限于猫咪日常、猫咪喂养、猫咪知识、猫咪剧场、猫咪救助、猫咪娱乐相关的内容", + "desc": "与猫相关的视频,包括但不限于猫咪日常、猫咪喂养、猫咪知识、猫咪剧场、猫咪救助、猫咪娱乐相关的内容", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 60, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 219, + "parent": 217, + "parent_name": "", + "name": "汪星人", + "description": "与狗相关的视频,包括但不限于狗狗日常、狗狗喂养、狗狗知识、狗狗剧场、狗狗救助、狗狗娱乐相关的内容", + "desc": "与狗相关的视频,包括但不限于狗狗日常、狗狗喂养、狗狗知识、狗狗剧场、狗狗救助、狗狗娱乐相关的内容", + "intro_original": "", + "intro_copy": "", + "notice": "晰明了表明内容亮点的标题会更受观众欢迎哟!\u0007", + "copy_right": 0, + "show": true, + "rank": 55, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 222, + "parent": 217, + "parent_name": "", + "name": "小宠异宠", + "description": "非猫、狗的宠物。包括但不限于水族、爬宠、鸟类、鼠、兔等内容", + "desc": "非猫、狗的宠物。包括但不限于水族、爬宠、鸟类、鼠、兔等内容", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 54, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 221, + "parent": 217, + "parent_name": "", + "name": "野生动物", + "description": "与野生动物相关的视频,包括但不限于狮子、老虎、狼、大熊猫等动物内容", + "desc": "与野生动物相关的视频,包括但不限于狮子、老虎、狼、大熊猫等动物内容", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 51, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 220, + "parent": 217, + "parent_name": "", + "name": "动物二创", + "description": "以动物素材为主的包括但不限于配音、剪辑、解说、reaction的再创作内容", + "desc": "以动物素材为主的包括但不限于配音、剪辑、解说、reaction的再创作内容", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 30, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 75, + "parent": 217, + "parent_name": "", + "name": "动物综合", + "description": "收录除上述子分区外,其余动物相关视频以及非动物主体或多个动物主体的动物相关延伸内容。如动物资讯、动物趣闻、动物知识等", + "desc": "收录除上述子分区外,其余动物相关视频以及非动物主体或多个动物主体的动物相关延伸内容。如动物资讯、动物趣闻、动物知识等", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 20, + "max_video_count": 50, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 129, + "parent": 0, + "parent_name": "", + "name": "舞蹈", + "description": "舞蹈", + "desc": "舞蹈", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 48, + "children": [ + { + "id": 20, + "parent": 129, + "parent_name": "", + "name": "宅舞", + "description": "与ACG相关的翻跳、原创舞蹈", + "desc": "与ACG相关的翻跳、原创舞蹈", + "intro_original": "建议注明编舞、音源等出处信息", + "intro_copy": "请注明转载作品的详细转载地址、作者、音源等信息,未添加正确信息的稿件可能被打回", + "notice": "【舞者】曲名 的类似格式填写标题", + "copy_right": 0, + "show": true, + "rank": 25, + "max_video_count": 100, + "request_id": "" + }, + { + "id": 154, + "parent": 129, + "parent_name": "", + "name": "舞蹈综合", + "description": "收录无法定义到其他舞蹈子分区的舞蹈视频", + "desc": "收录无法定义到其他舞蹈子分区的舞蹈视频", + "intro_original": "建议注明编舞、BGM等出处信息", + "intro_copy": "请注明转载作品的详细转载地址、作者、BGM等信息,未添加正确信息的稿件可能被打回", + "notice": "【舞者/舞蹈工作室】曲名 或者【赛事】曲名 的类似格式填写标题", + "copy_right": 0, + "show": true, + "rank": 20, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 255, + "parent": 129, + "parent_name": "", + "name": "手势·网红舞", + "description": "手势舞及网红流行舞蹈、短视频舞蹈等相关视频", + "desc": "手势舞及网红流行舞蹈、短视频舞蹈等相关视频", + "intro_original": "", + "intro_copy": "", + "notice": "清晰明了表明内容亮点的标题会更受观众欢迎哟!", + "copy_right": 0, + "show": true, + "rank": 18, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 198, + "parent": 129, + "parent_name": "", + "name": "街舞", + "description": "收录街舞相关内容,包括赛事现场、舞室作品、个人翻跳、FREESTYLE等", + "desc": "收录街舞相关内容,包括赛事现场、舞室作品、个人翻跳、FREESTYLE等", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 10, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 199, + "parent": 129, + "parent_name": "", + "name": "明星舞蹈", + "description": "国内外明星发布的官方舞蹈及其翻跳内容", + "desc": "国内外明星发布的官方舞蹈及其翻跳内容", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 5, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 200, + "parent": 129, + "parent_name": "", + "name": "国风舞蹈", + "description": "收录国风向舞蹈内容,包括中国舞、民族民间舞、汉唐舞、国风爵士等", + "desc": "收录国风向舞蹈内容,包括中国舞、民族民间舞、汉唐舞、国风爵士等", + "intro_original": "", + "intro_copy": "", + "notice": "【舞者】曲名的类似格式填写标题", + "copy_right": 0, + "show": true, + "rank": 4, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 156, + "parent": 129, + "parent_name": "", + "name": "舞蹈教程", + "description": "动作分解,基础教程等具有教学意义的舞蹈视频", + "desc": "动作分解,基础教程等具有教学意义的舞蹈视频", + "intro_original": "建议在简介中注明编舞、音源等信息,在标签中注明舞种等信息", + "intro_copy": "请注明转载作品的详细转载地址、作者、舞种等信息,在标签中注明舞种等信息,未添加正确信息的稿件可能被打回", + "notice": "【舞者/工作室】曲名/舞种+教学类别(如动作分解、基础教程) 的类似格式填写标题", + "copy_right": 0, + "show": true, + "rank": 1, + "max_video_count": 100, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 167, + "parent": 0, + "parent_name": "", + "name": "国创", + "description": "国创", + "desc": "国创", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 45, + "children": [ + { + "id": 153, + "parent": 167, + "parent_name": "", + "name": "国产动画", + "description": "国产连载动画,国产完结动画", + "desc": "国产连载动画,国产完结动画", + "intro_original": "国产动画标题格式建议:剧集名称 00(集数)/预告。", + "intro_copy": "转载稿件请标明转载出处,包括源地址,原作者,原标题。", + "notice": "剧集名称+集数+分集标题;【合集】+剧集名称", + "copy_right": 0, + "show": true, + "rank": 20, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 168, + "parent": 167, + "parent_name": "", + "name": "国产原创相关", + "description": "以国产动画、漫画、小说为素材的二次创作", + "desc": "以国产动画、漫画、小说为素材的二次创作", + "intro_original": "请注明BGM和使用素材。BGM格式:音乐人-音乐名 素材格式:片名列表。", + "intro_copy": "转载稿件请标明转载出处,包括源地址,原作者,原标题。", + "notice": "【广播剧、动态漫画】+总标题+集数+分集标题;【类型(MAD、杂谈等)】+标题", + "copy_right": 0, + "show": true, + "rank": 15, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 169, + "parent": 167, + "parent_name": "", + "name": "布袋戏", + "description": "布袋戏以及相关剪辑节目", + "desc": "布袋戏以及相关剪辑节目", + "intro_original": "请认真填写您所上传视频的相关信息,如剧情简介,主要演员及制作人员。", + "intro_copy": "转载稿件请标明转载出处,包括源地址,原作者,原标题。", + "notice": "【合集】+剧集名称;【霹雳布袋戏、金光布袋戏等】+标题", + "copy_right": 0, + "show": true, + "rank": 10, + "max_video_count": 100, + "request_id": "" + }, + { + "id": 170, + "parent": 167, + "parent_name": "", + "name": "资讯", + "description": "原创国产动画、漫画的相关资讯、宣传节目等", + "desc": "原创国产动画、漫画的相关资讯、宣传节目等", + "intro_original": "资讯区标题格式建议:【X月/剧场版】国产动画名称 PV1/CM2【清晰度】", + "intro_copy": "转载稿件请标明转载出处,包括源地址,原作者,原标题。", + "notice": "【类型(PV/OP/访谈等)】+标题;剧集名称+集数+预告", + "copy_right": 0, + "show": true, + "rank": 5, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 195, + "parent": 167, + "parent_name": "", + "name": "动态漫·广播剧", + "description": "国产动态漫画、有声漫画、广播剧", + "desc": "国产动态漫画、有声漫画、广播剧", + "intro_original": "", + "intro_copy": "", + "notice": "国产动态漫画、有声漫画、广播剧", + "copy_right": 0, + "show": true, + "rank": 0, + "max_video_count": 50, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 119, + "parent": 0, + "parent_name": "", + "name": "鬼畜", + "description": "鬼畜", + "desc": "鬼畜", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 40, + "children": [ + { + "id": 22, + "parent": 119, + "parent_name": "", + "name": "鬼畜调教", + "description": "使用素材在音频、画面上做一定处理,达到与BGM具有一定同步感的视频", + "desc": "使用素材在音频、画面上做一定处理,达到与BGM具有一定同步感的视频", + "intro_original": "请正确的填写作品中使用的素材以及BGM(背景音乐),有助于视频在分区以及搜索中获得更好的展示。\n借用他人创作的画面内容时请注明借用作品的出处信息;借用他人创作的鬼畜音频时请先与原作者联系,获得作者授权许可后方可使用。", + "intro_copy": "请正确的填写作品中使用的素材以及BGM(背景音乐),有助于视频在分区以及搜索中获得更好的展示。\n搬运(转载)作品请注明出处以及sm番号或者ytb短链,否则无法通过审核哦~", + "notice": "【素材名】曲名/内容主题", + "copy_right": 0, + "show": true, + "rank": 15, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 26, + "parent": 119, + "parent_name": "", + "name": "音MAD", + "description": "使用素材音频进行一定的二次创作来达到还原原曲的非商业性质稿件", + "desc": "使用素材音频进行一定的二次创作来达到还原原曲的非商业性质稿件", + "intro_original": "请正确的填写作品中使用的素材以及BGM(背景音乐),有助于视频在分区以及搜索中获得更好的展示。\n借用他人创作的画面内容时请注明借用作品的出处信息;借用他人创作的鬼畜音频时请先与原作者联系,获得作者授权许可后方可使用。", + "intro_copy": "请正确的填写作品中使用的素材以及BGM(背景音乐),有助于视频在分区以及搜索中获得更好的展示。\n搬运(转载)作品请注明出处以及sm番号或者ytb短链,否则无法通过审核哦~", + "notice": "【素材名】曲名/内容主题", + "copy_right": 0, + "show": true, + "rank": 10, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 126, + "parent": 119, + "parent_name": "", + "name": "人力VOCALOID", + "description": "将人物或者角色的无伴奏素材进行人工调音,使其就像VOCALOID一样歌唱的技术", + "desc": "将人物或者角色的无伴奏素材进行人工调音,使其就像VOCALOID一样歌唱的技术", + "intro_original": "请正确的填写作品中使用的素材以及BGM(背景音乐),有助于视频在分区以及搜索中获得更好的展示。\n借用他人创作的画面内容时请注明借用作品的出处信息;借用他人创作的鬼畜音频时请先与原作者联系,获得作者授权许可后方可使用。", + "intro_copy": "请正确的填写作品中使用的素材以及BGM(背景音乐),有助于视频在分区以及搜索中获得更好的展示。\n搬运(转载)作品请注明出处以及sm番号或者ytb短链,否则无法通过审核哦~", + "notice": "【素材名】曲名/内容主题", + "copy_right": 0, + "show": true, + "rank": 5, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 216, + "parent": 119, + "parent_name": "", + "name": "鬼畜剧场", + "description": "使用素材进行人工剪辑编排的有剧情的视频", + "desc": "使用素材进行人工剪辑编排的有剧情的视频", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 4, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 127, + "parent": 119, + "parent_name": "", + "name": "教程演示", + "description": "鬼畜相关的科普和教程演示", + "desc": "鬼畜相关的科普和教程演示", + "intro_original": "非鬼畜相关的教程演示请投稿至科技-野生技术协会。", + "intro_copy": "非鬼畜相关的教程演示请投稿至科技-野生技术协会。", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 0, + "max_video_count": 50, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 177, + "parent": 0, + "parent_name": "", + "name": "纪录片", + "description": "非电影性质的完整纪录片", + "desc": "非电影性质的完整纪录片", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 30, + "children": [ + { + "id": 37, + "parent": 177, + "parent_name": "", + "name": "人文·历史", + "description": "除宣传片、影视剪辑外的,人文艺术历史纪录剧集或电影、预告、花絮、二创、5分钟以上纪录短片", + "desc": "除宣传片、影视剪辑外的,人文艺术历史纪录剧集或电影、预告、花絮、二创、5分钟以上纪录短片", + "intro_original": "", + "intro_copy": "请注明制作片方(如国家地理、历史频道),并对视频内容进行简要叙述。可对相关知识进行补充说明。", + "notice": "建议使用【频道名】节目名称-系列【生肉/字幕】【字幕组或其他信息】的类似格式填写标题", + "copy_right": 2, + "show": true, + "rank": 15, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 178, + "parent": 177, + "parent_name": "", + "name": "科学·探索·自然", + "description": "除演讲、网课、教程外的,科学探索自然纪录剧集或电影、预告、花絮、二创、5分钟以上纪录短片", + "desc": "除演讲、网课、教程外的,科学探索自然纪录剧集或电影、预告、花絮、二创、5分钟以上纪录短片", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 10, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 179, + "parent": 177, + "parent_name": "", + "name": "军事", + "description": "除时政军事新闻外的,军事纪录剧集或电影、预告、花絮、二创、5分钟以上纪录短片", + "desc": "除时政军事新闻外的,军事纪录剧集或电影、预告、花絮、二创、5分钟以上纪录短片", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 5, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 180, + "parent": 177, + "parent_name": "", + "name": "社会·美食·旅行", + "description": "除VLOG、风光摄影外的,社会美食旅行纪录剧集或电影、预告、花絮、二创、5分钟以上纪录短片", + "desc": "除VLOG、风光摄影外的,社会美食旅行纪录剧集或电影、预告、花絮、二创、5分钟以上纪录短片", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 0, + "max_video_count": 50, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 13, + "parent": 0, + "parent_name": "", + "name": "番剧", + "description": "番剧", + "desc": "番剧", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 25, + "children": [ + { + "id": 51, + "parent": 13, + "parent_name": "", + "name": "资讯", + "description": "以动画/轻小说/漫画/杂志为主的资讯内容,PV/CM/特报/冒头/映像/预告", + "desc": "以动画/轻小说/漫画/杂志为主的资讯内容,PV/CM/特报/冒头/映像/预告", + "intro_original": " ", + "intro_copy": "【标题规范】\n*来源:轻小说,小说,漫画,杂志,长篇,,原创动画,TV动画(开播月份已知时可省略),剧场版,OVA,Blu-ray\n国家(非日本动画时填写):美漫(美番),韩漫等;\n*开播年份:(为当年时开播可省略);\n*开播月份:1-12月(开播月份未知时可省略);\n*类别:PV,CM,TVCM,预告(预告前加集数),冒头,映像;\n清晰度:720P,1080P。\n注:*为必填项。", + "notice": "【X月/TV动画/剧场版】番剧名称 PV/CM0", + "copy_right": 2, + "show": true, + "rank": 10, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 152, + "parent": 13, + "parent_name": "", + "name": "官方延伸", + "description": "以动画番剧及声优为主的EVENT/生放送/DRAMA/RADIO/LIVE/特典/冒头等", + "desc": "以动画番剧及声优为主的EVENT/生放送/DRAMA/RADIO/LIVE/特典/冒头等", + "intro_original": " ", + "intro_copy": "", + "notice": " ", + "copy_right": 0, + "show": true, + "rank": 5, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 32, + "parent": 13, + "parent_name": "", + "name": "完结动画", + "description": "已完结TV/WEB动画及其独立系列,旧剧场版/OVA/SP/未放送", + "desc": "已完结TV/WEB动画及其独立系列,旧剧场版/OVA/SP/未放送", + "intro_original": " ", + "intro_copy": "【标题规范】\n*清晰度:360P,480P,720P,1080P(视频源分辨率高度);\n来源:TVRip,DVDRip,BDRip;\n*内容属性:合集(对应TV版本内容),剧场版,OVA,特典,SP;\n*字幕组名称:当前稿件视频源出品字幕组名称,如为无字幕视频源,则填写“生肉”;如为自制字幕,则填写“中文字幕”或“中字”。\n注:*为必填项。", + "notice": "【720P/BD】番剧名称 分季【XX字幕】", + "copy_right": 2, + "show": true, + "rank": 0, + "max_video_count": 50, + "request_id": "" + }, + { + "id": 33, + "parent": 13, + "parent_name": "", + "name": "连载动画", + "description": "连载中TV/WEB动画,新剧场版/OVA/SP/未放送/小剧场", + "desc": "连载中TV/WEB动画,新剧场版/OVA/SP/未放送/小剧场", + "intro_original": " ", + "intro_copy": "【标题规范】\n*开播月份:1-12月;\n*所属国别(非日本动画时填写):美漫(美番),韩漫等;\n*当前集数:当前稿件所更新的集数,如有多集,用“-”连接,如:01-05;\n*字幕组名称:当前稿件视频源出品字幕组名称,如为无字幕视频源,则填写“生肉”。\n清晰度:360P,480P,720P,1080P。\n注:*为必填项。", + "notice": "【X月/OVA/剧场版】番剧名称【XX字幕】", + "copy_right": 2, + "show": true, + "rank": 0, + "max_video_count": 50, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 11, + "parent": 0, + "parent_name": "", + "name": "电视剧", + "description": "电视剧", + "desc": "电视剧", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 20, + "children": [ + { + "id": 185, + "parent": 11, + "parent_name": "", + "name": "国产剧", + "description": "", + "desc": "", + "intro_original": "请投正片!请投正片!请投正片!【预告·花絮·剪辑·CUT·二创·短片·特摄等相关内容请投至“影视”分区】谢谢!", + "intro_copy": "请投正片!请投正片!请投正片!【预告·花絮·剪辑·CUT·二创·短片·特摄等相关内容请投至“影视”分区】谢谢!", + "notice": "请使用【类型】电视剧名称 年份【其他信息】的类型格式填写标题。", + "copy_right": 0, + "show": true, + "rank": 0, + "max_video_count": 50, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + }, + { + "id": 23, + "parent": 0, + "parent_name": "", + "name": "电影", + "description": "电影", + "desc": "电影", + "intro_original": "", + "intro_copy": "", + "notice": "", + "copy_right": 0, + "show": true, + "rank": 15, + "children": [ + { + "id": 147, + "parent": 23, + "parent_name": "", + "name": "国产电影", + "description": "", + "desc": "", + "intro_original": "请投正片!请投正片!请投正片!非正片请投至“影视”分区!谢谢!", + "intro_copy": "请投正片!请投正片!请投正片!非正片请投至“影视”分区!谢谢!", + "notice": "请使用【类型】电影名称 年份 【其他信息】 的类似格式填写标题。【合集投稿将不予审核通过】", + "copy_right": 2, + "show": true, + "rank": 0, + "max_video_count": 50, + "request_id": "" + } + ], + "max_video_count": 50, + "request_id": "" + } + ] +} diff --git a/bilibili_api/data/video_zone.json b/bilibili_api/data/video_zone.json new file mode 100644 index 0000000000000000000000000000000000000000..f1db12d59348002698c0e5d3240810273a753be9 --- /dev/null +++ b/bilibili_api/data/video_zone.json @@ -0,0 +1,1163 @@ +[ + { + "channelId": 2, + "name": "番剧", + "tid": 13, + "url": "//www.bilibili.com/anime/", + "icon": "ChannelAnime", + "sub": [ + { + "subChannelId": 20001, + "name": "连载动画", + "tid": 33, + "route": "serial", + "url": "//www.bilibili.com/v/anime/serial/" + }, + { + "subChannelId": 20002, + "name": "完结动画", + "tid": 32, + "route": "finish", + "url": "//www.bilibili.com/v/anime/finish" + }, + { + "subChannelId": 20003, + "name": "资讯", + "tid": 51, + "route": "information", + "url": "//www.bilibili.com/v/anime/information/" + }, + { + "subChannelId": 20004, + "name": "官方延伸", + "tid": 152, + "route": "offical", + "url": "//www.bilibili.com/v/anime/offical/" + }, + { + "subChannelId": 20005, + "name": "新番时间表", + "url": "//www.bilibili.com/anime/timeline/" + }, + { + "subChannelId": 20006, + "name": "番剧索引", + "url": "//www.bilibili.com/anime/index/" + } + ] + }, + { + "channelId": 3, + "name": "电影", + "tid": 23, + "url": "//www.bilibili.com/movie/", + "icon": "ChannelMovie" + }, + { + "channelId": 4, + "name": "国创", + "tid": 167, + "url": "//www.bilibili.com/guochuang/", + "icon": "ChannelGuochuang", + "sub": [ + { + "subChannelId": 40001, + "name": "国产动画", + "tid": 153, + "route": "chinese", + "url": "//www.bilibili.com/v/guochuang/chinese/" + }, + { + "subChannelId": 40002, + "name": "国产原创相关", + "tid": 168, + "route": "original", + "url": "//www.bilibili.com/v/guochuang/original/" + }, + { + "subChannelId": 40003, + "name": "布袋戏", + "tid": 169, + "route": "puppetry", + "url": "//www.bilibili.com/v/guochuang/puppetry/" + }, + { + "subChannelId": 40004, + "name": "动态漫·广播剧", + "tid": 195, + "route": "motioncomic", + "url": "//www.bilibili.com/v/guochuang/motioncomic/" + }, + { + "subChannelId": 40005, + "name": "资讯", + "tid": 170, + "route": "information", + "url": "//www.bilibili.com/v/guochuang/information/" + }, + { + "subChannelId": 40006, + "name": "新番时间表", + "url": "//www.bilibili.com/guochuang/timeline/" + }, + { + "subChannelId": 40007, + "name": "国产动画索引", + "url": "//www.bilibili.com/guochuang/index/" + } + ] + }, + { + "name": "电视剧", + "channelId": 5, + "tid": 11, + "url": "//www.bilibili.com/tv/", + "type": "first", + "icon": "ChannelTeleplay" + }, + { + "name": "综艺", + "channelId": 6, + "icon": "ChannelZongyi", + "url": "//www.bilibili.com/variety/" + }, + { + "name": "纪录片", + "tid": 177, + "channelId": 7, + "url": "//www.bilibili.com/documentary/", + "icon": "ChannelDocumentary" + }, + { + "name": "动画", + "channelId": 8, + "tid": 1, + "url": "//www.bilibili.com/v/douga/", + "icon": "ChannelDouga", + "sub": [ + { + "subChannelId": 80001, + "name": "MAD·AMV", + "route": "mad", + "tid": 24, + "url": "//www.bilibili.com/v/douga/mad/" + }, + { + "subChannelId": 80002, + "name": "MMD·3D", + "route": "mmd", + "tid": 25, + "url": "//www.bilibili.com/v/douga/mmd/" + }, + { + "subChannelId": 80003, + "name": "短片·手书", + "route": "handdrawn", + "tid": 47, + "url": "//www.bilibili.com/v/douga/handdrawn/" + }, + { + "subChannelId": 80008, + "name": "配音", + "route": "voice", + "tid": 257, + "url": "//www.bilibili.com/v/douga/voice/" + }, + { + "subChannelId": 80004, + "name": "手办·模玩", + "route": "garage_kit", + "tid": 210, + "url": "//www.bilibili.com/v/douga/garage_kit/" + }, + { + "subChannelId": 80005, + "name": "特摄", + "route": "tokusatsu", + "tid": 86, + "url": "//www.bilibili.com/v/douga/tokusatsu/" + }, + { + "subChannelId": 80007, + "name": "动漫杂谈", + "route": "acgntalks", + "tid": 253, + "url": "//www.bilibili.com/v/douga/acgntalks/" + }, + { + "subChannelId": 80006, + "name": "综合", + "route": "other", + "tid": 27, + "url": "//www.bilibili.com/v/douga/other/" + } + ] + }, + { + "name": "游戏", + "channelId": 11, + "tid": 4, + "url": "//www.bilibili.com/v/game/", + "icon": "ChannelGame", + "sub": [ + { + "subChannelId": 110001, + "name": "单机游戏", + "route": "stand_alone", + "tid": 17, + "url": "//www.bilibili.com/v/game/stand_alone" + }, + { + "subChannelId": 110002, + "name": "电子竞技", + "route": "esports", + "tid": 171, + "url": "//www.bilibili.com/v/game/esports" + }, + { + "subChannelId": 110003, + "name": "手机游戏", + "route": "mobile", + "tid": 172, + "url": "//www.bilibili.com/v/game/mobile" + }, + { + "subChannelId": 110004, + "name": "网络游戏", + "route": "online", + "tid": 65, + "url": "//www.bilibili.com/v/game/online" + }, + { + "subChannelId": 110005, + "name": "桌游棋牌", + "route": "board", + "tid": 173, + "url": "//www.bilibili.com/v/game/board" + }, + { + "subChannelId": 110006, + "name": "GMV", + "route": "gmv", + "tid": 121, + "url": "//www.bilibili.com/v/game/gmv" + }, + { + "subChannelId": 110007, + "name": "音游", + "route": "music", + "tid": 136, + "url": "//www.bilibili.com/v/game/music" + }, + { + "subChannelId": 110008, + "name": "Mugen", + "route": "mugen", + "tid": 19, + "url": "//www.bilibili.com/v/game/mugen" + }, + { + "subChannelId": 110009, + "name": "游戏赛事", + "url": "//www.bilibili.com/v/game/match/" + } + ] + }, + { + "name": "鬼畜", + "channelId": 20, + "tid": 119, + "url": "//www.bilibili.com/v/kichiku/", + "icon": "ChannelKichiku", + "sub": [ + { + "subChannelId": 200001, + "name": "鬼畜调教", + "route": "guide", + "tid": 22, + "url": "//www.bilibili.com/v/kichiku/guide" + }, + { + "subChannelId": 200002, + "name": "音MAD", + "route": "mad", + "tid": 26, + "url": "//www.bilibili.com/v/kichiku/mad" + }, + { + "subChannelId": 200003, + "name": "人力VOCALOID", + "route": "manual_vocaloid", + "tid": 126, + "url": "//www.bilibili.com/v/kichiku/manual_vocaloid" + }, + { + "subChannelId": 200004, + "name": "鬼畜剧场", + "route": "theatre", + "tid": 216, + "url": "//www.bilibili.com/v/kichiku/theatre" + }, + { + "subChannelId": 200005, + "name": "教程演示", + "route": "course", + "tid": 127, + "url": "//www.bilibili.com/v/kichiku/course" + } + ] + }, + { + "name": "音乐", + "channelId": 9, + "tid": 3, + "url": "//www.bilibili.com/v/music", + "icon": "ChannelMusic", + "sub": [ + { + "subChannelId": 90001, + "name": "原创音乐", + "route": "original", + "url": "//www.bilibili.com/v/music/original", + "tid": 28 + }, + { + "subChannelId": 90002, + "name": "翻唱", + "route": "cover", + "url": "//www.bilibili.com/v/music/cover", + "tid": 31 + }, + { + "subChannelId": 90005, + "name": "演奏", + "route": "perform", + "tid": 59, + "url": "//www.bilibili.com/v/music/perform" + }, + { + "subChannelId": 90003, + "name": "VOCALOID·UTAU", + "route": "vocaloid", + "url": "//www.bilibili.com/v/music/vocaloid", + "tid": 30 + }, + { + "subChannelId": 90007, + "name": "音乐现场", + "route": "live", + "tid": 29, + "url": "//www.bilibili.com/v/music/live" + }, + { + "subChannelId": 90006, + "name": "MV", + "route": "mv", + "tid": 193, + "url": "//www.bilibili.com/v/music/mv" + }, + { + "subChannelId": 900011, + "name": "乐评盘点", + "route": "commentary", + "tid": 243, + "url": "//www.bilibili.com/v/music/commentary" + }, + { + "subChannelId": 900012, + "name": "音乐教学", + "route": "tutorial", + "tid": 244, + "url": "//www.bilibili.com/v/music/tutorial" + }, + { + "subChannelId": 90008, + "name": "音乐综合", + "route": "other", + "tid": 130, + "url": "//www.bilibili.com/v/music/other" + }, + { + "subChannelId": 900010, + "name": "说唱", + "url": "//www.bilibili.com/v/rap" + } + ] + }, + { + "name": "舞蹈", + "channelId": 10, + "tid": 129, + "url": "//www.bilibili.com/v/dance/", + "icon": "ChannelDance", + "sub": [ + { + "subChannelId": 100001, + "name": "宅舞", + "route": "otaku", + "tid": 20, + "url": "//www.bilibili.com/v/dance/otaku/" + }, + { + "subChannelId": 100002, + "name": "街舞", + "route": "hiphop", + "tid": 198, + "url": "//www.bilibili.com/v/dance/hiphop/" + }, + { + "subChannelId": 100003, + "name": "明星舞蹈", + "route": "star", + "tid": 199, + "url": "//www.bilibili.com/v/dance/star/" + }, + { + "subChannelId": 100004, + "name": "国风舞蹈", + "route": "china", + "tid": 200, + "url": "//www.bilibili.com/v/dance/china/" + }, + { + "subChannelId": 100007, + "name": "手势·网红舞", + "route": "gestures", + "tid": 255, + "url": "//www.bilibili.com/v/dance/gestures/" + }, + { + "subChannelId": 100005, + "name": "舞蹈综合", + "route": "three_d", + "tid": 154, + "url": "//www.bilibili.com/v/dance/three_d/" + }, + { + "subChannelId": 100006, + "name": "舞蹈教程", + "route": "demo", + "tid": 156, + "url": "//www.bilibili.com/v/dance/demo/" + } + ] + }, + { + "name": "影视", + "channelId": 25, + "tid": 181, + "url": "//www.bilibili.com/v/cinephile", + "icon": "ChannelCinephile", + "sub": [ + { + "subChannelId": 250001, + "name": "影视杂谈", + "route": "cinecism", + "tid": 182, + "url": "//www.bilibili.com/v/cinephile/cinecism" + }, + { + "subChannelId": 250002, + "name": "影视剪辑", + "route": "montage", + "tid": 183, + "url": "//www.bilibili.com/v/cinephile/montage" + }, + { + "subChannelId": 250003, + "name": "小剧场", + "route": "shortplay", + "tid": 85, + "url": "//www.bilibili.com/v/cinephile/shortplay" + }, + { + "subChannelId": 250005, + "name": "短片", + "route": "shortfilm", + "tid": 256, + "url": "//www.bilibili.com/v/cinephile/shortfilm" + }, + { + "subChannelId": 250004, + "name": "预告·资讯", + "route": "trailer_info", + "tid": 184, + "url": "//www.bilibili.com/v/cinephile/trailer_info" + } + ] + }, + { + "name": "娱乐", + "channelId": 23, + "tid": 5, + "url": "//www.bilibili.com/v/ent/", + "icon": "ChannelEnt", + "sub": [ + { + "subChannelId": 230001, + "name": "综艺", + "route": "variety", + "tid": 71, + "url": "//www.bilibili.com/v/ent/variety" + }, + { + "subChannelId": 230003, + "name": "娱乐杂谈", + "route": "talker", + "tid": 241, + "url": "//www.bilibili.com/v/ent/talker" + }, + { + "subChannelId": 230004, + "name": "粉丝创作", + "route": "fans", + "tid": 242, + "url": "//www.bilibili.com/v/ent/fans" + }, + { + "subChannelId": 230002, + "name": "明星综合", + "route": "celebrity", + "tid": 137, + "url": "//www.bilibili.com/v/ent/celebrity" + } + ] + }, + { + "name": "知识", + "channelId": 12, + "tid": 36, + "url": "//www.bilibili.com/v/knowledge/", + "icon": "ChannelKnowledge", + "sub": [ + { + "subChannelId": 120001, + "name": "科学科普", + "route": "science", + "tid": 201, + "url": "//www.bilibili.com/v/knowledge/science" + }, + { + "subChannelId": 120002, + "name": "社科·法律·心理", + "route": "social_science", + "tid": 124, + "url": "//www.bilibili.com/v/knowledge/social_science" + }, + { + "subChannelId": 120003, + "name": "人文历史", + "route": "humanity_history", + "tid": 228, + "url": "//www.bilibili.com/v/knowledge/humanity_history" + }, + { + "subChannelId": 120004, + "name": "财经商业", + "route": "business", + "tid": 207, + "url": "//www.bilibili.com/v/knowledge/business" + }, + { + "subChannelId": 120005, + "name": "校园学习", + "route": "campus", + "tid": 208, + "url": "//www.bilibili.com/v/knowledge/campus" + }, + { + "subChannelId": 120006, + "name": "职业职场", + "route": "career", + "tid": 209, + "url": "//www.bilibili.com/v/knowledge/career" + }, + { + "subChannelId": 120007, + "name": "设计·创意", + "route": "design", + "tid": 229, + "url": "//www.bilibili.com/v/knowledge/design" + }, + { + "subChannelId": 120008, + "name": "野生技能协会", + "route": "skill", + "tid": 122, + "url": "//www.bilibili.com/v/knowledge/skill" + } + ] + }, + { + "name": "科技", + "channelId": 13, + "tid": 188, + "url": "//www.bilibili.com/v/tech/", + "icon": "ChannelTech", + "sub": [ + { + "subChannelId": 130001, + "name": "数码", + "route": "digital", + "tid": 95, + "url": "//www.bilibili.com/v/tech/digital" + }, + { + "subChannelId": 130002, + "name": "软件应用", + "route": "application", + "tid": 230, + "url": "//www.bilibili.com/v/tech/application" + }, + { + "subChannelId": 130003, + "name": "计算机技术", + "route": "computer_tech", + "tid": 231, + "url": "//www.bilibili.com/v/tech/computer_tech" + }, + { + "subChannelId": 130004, + "name": "科工机械", + "route": "industry", + "tid": 232, + "url": "//www.bilibili.com/v/tech/industry" + }, + { + "subChannelId": 130005, + "name": "极客DIY", + "route": "diy", + "url": "//www.bilibili.com/v/tech/diy" + } + ] + }, + { + "name": "资讯", + "channelId": 21, + "tid": 202, + "url": "//www.bilibili.com/v/information/", + "icon": "ChannelInformation", + "sub": [ + { + "subChannelId": 210001, + "name": "热点", + "route": "hotspot", + "tid": 203, + "url": "//www.bilibili.com/v/information/hotspot" + }, + { + "subChannelId": 210002, + "name": "环球", + "route": "global", + "tid": 204, + "url": "//www.bilibili.com/v/information/global" + }, + { + "subChannelId": 210003, + "name": "社会", + "route": "social", + "tid": 205, + "url": "//www.bilibili.com/v/information/social" + }, + { + "subChannelId": 210004, + "name": "综合", + "route": "multiple", + "tid": 206, + "url": "//www.bilibili.com/v/information/multiple" + } + ] + }, + { + "name": "美食", + "channelId": 17, + "tid": 211, + "url": "//www.bilibili.com/v/food", + "icon": "ChannelFood", + "sub": [ + { + "subChannelId": 170001, + "name": "美食制作", + "route": "make", + "tid": 76, + "url": "//www.bilibili.com/v/food/make" + }, + { + "subChannelId": 170002, + "name": "美食侦探", + "route": "detective", + "tid": 212, + "url": "//www.bilibili.com/v/food/detective" + }, + { + "subChannelId": 170003, + "name": "美食测评", + "route": "measurement", + "tid": 213, + "url": "//www.bilibili.com/v/food/measurement" + }, + { + "subChannelId": 170004, + "name": "田园美食", + "route": "rural", + "tid": 214, + "url": "//www.bilibili.com/v/food/rural" + }, + { + "subChannelId": 170005, + "name": "美食记录", + "route": "record", + "tid": 215, + "url": "//www.bilibili.com/v/food/record" + } + ] + }, + { + "name": "生活", + "channelId": 16, + "tid": 160, + "url": "//www.bilibili.com/v/life", + "icon": "ChannelLife", + "sub": [ + { + "subChannelId": 160001, + "name": "搞笑", + "route": "funny", + "tid": 138, + "url": "//www.bilibili.com/v/life/funny" + }, + { + "subChannelId": 160008, + "name": "亲子", + "route": "parenting", + "tid": 254, + "url": "//www.bilibili.com/v/life/parenting" + }, + { + "subChannelId": 160006, + "name": "出行", + "route": "travel", + "tid": 250, + "url": "//www.bilibili.com/v/life/travel" + }, + { + "subChannelId": 160007, + "name": "三农", + "route": "rurallife", + "tid": 251, + "url": "//www.bilibili.com/v/life/rurallife" + }, + { + "subChannelId": 160002, + "name": "家居房产", + "route": "home", + "tid": 239, + "url": "//www.bilibili.com/v/life/home" + }, + { + "subChannelId": 160003, + "name": "手工", + "route": "handmake", + "tid": 161, + "url": "//www.bilibili.com/v/life/handmake" + }, + { + "subChannelId": 160004, + "name": "绘画", + "route": "painting", + "tid": 162, + "url": "//www.bilibili.com/v/life/painting" + }, + { + "subChannelId": 160005, + "name": "日常", + "route": "daily", + "tid": 21, + "url": "//www.bilibili.com/v/life/daily" + } + ] + }, + { + "name": "汽车", + "channelId": 15, + "tid": 223, + "url": "//www.bilibili.com/v/car", + "icon": "ChannelCar", + "sub": [ + { + "subChannelId": 150007, + "name": "赛车", + "route": "racing", + "tid": 245, + "url": "//www.bilibili.com/v/car/racing" + }, + { + "subChannelId": 150008, + "name": "改装玩车", + "route": "modifiedvehicle", + "tid": 246, + "url": "//www.bilibili.com/v/car/modifiedvehicle" + }, + { + "subChannelId": 150009, + "name": "新能源车", + "route": "newenergyvehicle", + "tid": 246, + "url": "//www.bilibili.com/v/car/newenergyvehicle" + }, + { + "subChannelId": 150010, + "name": "房车", + "route": "touringcar", + "tid": 248, + "url": "//www.bilibili.com/v/car/touringcar" + }, + { + "subChannelId": 150006, + "name": "摩托车", + "route": "motorcycle", + "tid": 240, + "url": "//www.bilibili.com/v/car/motorcycle" + }, + { + "subChannelId": 150005, + "name": "购车攻略", + "route": "strategy", + "tid": 227, + "url": "//www.bilibili.com/v/car/strategy" + }, + { + "subChannelId": 150001, + "name": "汽车生活", + "route": "life", + "tid": 176, + "url": "//www.bilibili.com/v/car/life" + } + ] + }, + { + "name": "时尚", + "channelId": 22, + "tid": 155, + "url": "//www.bilibili.com/v/fashion", + "icon": "ChannelFashion", + "sub": [ + { + "subChannelId": 220001, + "name": "美妆护肤", + "route": "makeup", + "tid": 157, + "url": "//www.bilibili.com/v/fashion/makeup" + }, + { + "subChannelId": 220004, + "name": "仿妆cos", + "route": "cos", + "tid": 252, + "url": "//www.bilibili.com/v/fashion/cos" + }, + { + "subChannelId": 220002, + "name": "穿搭", + "route": "clothing", + "tid": 158, + "url": "//www.bilibili.com/v/fashion/clothing" + }, + { + "subChannelId": 220003, + "name": "时尚潮流", + "route": "trend", + "tid": 159, + "url": "//www.bilibili.com/v/fashion/trend" + } + ] + }, + { + "name": "运动", + "channelId": 14, + "tid": 234, + "url": "//www.bilibili.com/v/sports", + "icon": "ChannelSports", + "sub": [ + { + "subChannelId": 140001, + "name": "篮球", + "route": "basketball", + "tid": 235, + "url": "//www.bilibili.com/v/sports/basketball" + }, + { + "subChannelId": 140006, + "name": "足球", + "route": "football", + "tid": 249, + "url": "//www.bilibili.com/v/sports/football" + }, + { + "subChannelId": 140002, + "name": "健身", + "route": "aerobics", + "tid": 164, + "url": "//www.bilibili.com/v/sports/aerobics" + }, + { + "subChannelId": 140003, + "name": "竞技体育", + "route": "athletic", + "tid": 236, + "url": "//www.bilibili.com/v/sports/athletic" + }, + { + "subChannelId": 140004, + "name": "运动文化", + "route": "culture", + "tid": 237, + "url": "//www.bilibili.com/v/sports/culture" + }, + { + "subChannelId": 140005, + "name": "运动综合", + "route": "comprehensive", + "tid": 238, + "url": "//www.bilibili.com/v/sports/comprehensive" + } + ] + }, + { + "name": "动物圈", + "channelId": 18, + "tid": 217, + "url": "//www.bilibili.com/v/animal", + "icon": "ChannelAnimal", + "sub": [ + { + "subChannelId": 180001, + "name": "喵星人", + "route": "cat", + "tid": 218, + "url": "//www.bilibili.com/v/animal/cat" + }, + { + "subChannelId": 180002, + "name": "汪星人", + "route": "dog", + "tid": 219, + "url": "//www.bilibili.com/v/animal/dog" + }, + { + "subChannelId": 180007, + "name": "小宠异宠", + "route": "reptiles", + "tid": 222, + "url": "//www.bilibili.com/v/animal/reptiles" + }, + { + "subChannelId": 180004, + "name": "野生动物", + "route": "wild_animal", + "tid": 221, + "url": "//www.bilibili.com/v/animal/wild_animal" + }, + { + "subChannelId": 180008, + "name": "动物二创", + "route": "second_edition", + "tid": 220, + "url": "//www.bilibili.com/v/animal/second_edition" + }, + { + "subChannelId": 180006, + "name": "动物综合", + "route": "animal_composite", + "tid": 75, + "url": "//www.bilibili.com/v/animal/animal_composite" + } + ] + }, + { + "name": "VLOG", + "channelId": 19, + "url": "//www.bilibili.com/v/life/daily/#/530003", + "icon": "ChannelVlog" + }, + { + "channelId": 160001, + "name": "搞笑", + "route": "stand_alone", + "tid": 138, + "icon": "ChannelGaoxiao", + "url": "//www.bilibili.com/v/life/funny" + }, + { + "channelId": 110001, + "name": "单机游戏", + "route": "stand_alone", + "tid": 17, + "icon": "ChannelDanjiyouxi", + "url": "//www.bilibili.com/v/game/stand_alone" + }, + { + "channelId": 31, + "name": "虚拟UP主", + "icon": "ChannelVtuber", + "url": "//www.bilibili.com/v/virtual" + }, + { + "channelId": 32, + "name": "公益", + "icon": "ChannelLove", + "url": "//love.bilibili.com" + }, + { + "channelId": 33, + "name": "公开课", + "icon": "ChannelGongkaike", + "url": "//www.bilibili.com/mooc" + }, + { + "name": "专栏", + "channelId": 26, + "url": "//www.bilibili.com/read/home", + "icon": "ChannelRead", + "sideIcon": "SideArticleIcon" + }, + { + "name": "直播", + "channelId": 1, + "url": "//live.bilibili.com", + "icon": "ChannelLive", + "sideIcon": "SideLiveIcon", + "sub": [ + { + "subChannelId": 10001, + "name": "全部", + "url": "//live.bilibili.com/all?visit_id=5icxsa0kmts0" + }, + { + "subChannelId": 10002, + "name": "网游", + "url": "//live.bilibili.com/p/eden/area-tags?parentAreaId=2&areaId=0&visit_id=5icxsa0kmts0#/2/0" + }, + { + "subChannelId": 10003, + "name": "手游", + "url": "//live.bilibili.com/p/eden/area-tags?parentAreaId=3&areaId=0&visit_id=5icxsa0kmts0#/3/0" + }, + { + "subChannelId": 10004, + "name": "单机", + "url": "//live.bilibili.com/p/eden/area-tags?parentAreaId=6&areaId=0" + }, + { + "subChannelId": 10005, + "name": "娱乐", + "url": "//live.bilibili.com/p/eden/area-tags?parentAreaId=1&areaId=0&visit_id=5icxsa0kmts0#/1/0" + }, + { + "subChannelId": 10006, + "name": "电台", + "url": "//live.bilibili.com/p/eden/area-tags?parentAreaId=5&areaId=0&visit_id=5icxsa0kmts0#/5/0" + }, + { + "subChannelId": 10007, + "name": "虚拟", + "url": "//live.bilibili.com/p/eden/area-tags?parentAreaId=9&areaId=0" + }, + { + "subChannelId": 10008, + "name": "生活", + "url": "//live.bilibili.com/p/eden/area-tags?parentAreaId=10&areaId=0" + }, + { + "subChannelId": 10009, + "name": "知识", + "url": "//live.bilibili.com/p/eden/area-tags?parentAreaId=11&areaId=0" + }, + { + "subChannelId": 10010, + "name": "赛事", + "url": "//live.bilibili.com/p/eden/area-tags?parentAreaId=13&areaId=0" + } + ] + }, + { + "name": "活动", + "channelId": 28, + "url": "//www.bilibili.com/blackboard/activity-list.html?", + "icon": "ChannelActivity", + "sideIcon": "SideActivityIcon" + }, + { + "name": "课堂", + "channelId": 27, + "url": "//www.bilibili.com/cheese/", + "icon": "ChannelZhishi", + "sideIcon": "SideCheeseIcon", + "sub": [ + { + "name": "通识科普", + "subChannelId": 270001, + "url": "https://www.bilibili.com/cheese/category?first=95&csource=Channel_class" + }, + { + "name": "兴趣生活", + "subChannelId": 270008, + "url": "https://www.bilibili.com/cheese/category?first=94&csource=Channel_class" + }, + { + "name": "语言学习", + "subChannelId": 270002, + "url": "https://www.bilibili.com/cheese/category?first=93&csource=Channel_class" + }, + { + "name": "考研", + "subChannelId": 270003, + "url": "https://www.bilibili.com/cheese/category?first=88&csource=Channel_class" + }, + { + "name": "考试考证", + "subChannelId": 270005, + "url": "https://www.bilibili.com/cheese/category?first=87&csource=Channel_class" + }, + { + "name": "影视·设计", + "subChannelId": 270012, + "url": "https://www.bilibili.com/cheese/category?first=164&csource=Channel_class" + }, + { + "name": "IT互联网", + "subChannelId": 270007, + "url": "https://www.bilibili.com/cheese/category?first=89&csource=Channel_class" + }, + { + "name": "职业职场", + "subChannelId": 270009, + "url": "https://www.bilibili.com/cheese/category?first=92&csource=Channel_class" + }, + { + "name": "个人成长", + "subChannelId": 270011, + "url": "https://www.bilibili.com/cheese/category?first=181&csource=Channel_class" + }, + { + "name": "我的课程", + "subChannelId": 270010, + "url": "https://www.bilibili.com/cheese/mine/list?csource=Channel_class" + } + ] + }, + { + "name": "社区中心", + "channelId": 29, + "url": "https://www.bilibili.com/blackboard/activity-5zJxM3spoS.html", + "icon": "ChannelBlackroom", + "sideIcon": "SideBlackboardIcon" + }, + { + "name": "新歌热榜", + "channelId": 24, + "url": "//music.bilibili.com/pc/music-center", + "icon": "ChannelMusicplus", + "sideIcon": "SideHotMusicIcon" + } +] diff --git a/bilibili_api/dynamic.py b/bilibili_api/dynamic.py new file mode 100644 index 0000000000000000000000000000000000000000..01768a2b4df52bb33f4d6ccd28fc8c22759a4753 --- /dev/null +++ b/bilibili_api/dynamic.py @@ -0,0 +1,1017 @@ +""" +bilibili_api.dynamic + +动态相关 +""" + +import os +import re +import sys +import json +import asyncio +from enum import Enum +from datetime import datetime +from typing import Any, List, Tuple, Union, Optional + +import httpx + +from .user import name2uid_sync +from .utils import utils +from .utils.sync import sync +from .utils.picture import Picture +from . import user, vote, exceptions +from .utils.credential import Credential +from .utils.network import Api +from .utils import cache_pool +from .exceptions.DynamicExceedImagesException import DynamicExceedImagesException +from . import opus + +API = utils.get_api("dynamic") +raise_for_statement = utils.raise_for_statement + + +class DynamicType(Enum): + """ + 动态类型 + + + ALL: 所有动态 + + ANIME: 追番追剧 + + ARTICLE: 文章 + + VIDEO: 视频投稿 + """ + + ALL = "all" + ANIME = "pgc" + ARTICLE = "article" + VIDEO = "video" + + +class SendDynamicType(Enum): + """ + 发送动态类型 + scene 参数 + + + TEXT: 纯文本 + + IMAGE: 图片 + """ + + TEXT = 1 + IMAGE = 2 + + +class DynamicContentType(Enum): + """ + 动态内容类型 + + + TEXT: 文本 + + EMOJI: 表情 + + AT: @User + + VOTE: 投票 + """ + + TEXT = 1 + EMOJI = 9 + AT = 2 + VOTE = 4 + + +async def _parse_at(text: str) -> Tuple[str, str, str]: + """ + @人格式:“@用户名 ”(注意最后有空格) + + Args: + text (str): 原始文本 + + Returns: + tuple(str, str(int[]), str(dict)): 替换后文本,解析出艾特的 UID 列表,AT 数据 + """ + text += " " + pattern = re.compile(r"(?<=@).*?(?=\s)") + match_result = re.finditer(pattern, text) + uid_list = [] + names = [] + for match in match_result: + uname = match.group() + try: + uid = (await user.name2uid(uname))["uid_list"][0]["uid"] + except KeyError: + # 没有此用户 + continue + + uid_list.append(str(uid)) + names.append(uname + " ") + at_uids = ",".join(uid_list) + ctrl = [] + last_index = 0 + for i, name in enumerate(names): + index = text.index(f"@{name}", last_index) + last_index = index + 1 + length = 2 + len(name) + ctrl.append( + {"location": index, "type": 1, "length": length, "data": int(uid_list[i])} + ) + + return text, at_uids, json.dumps(ctrl, ensure_ascii=False) + + +async def _get_text_data(text: str) -> dict: + """ + 获取文本动态请求参数 + + Args: + text (str): 文本内容 + + Returns: + dict: 文本动态请求数据 + """ + new_text, at_uids, ctrl = await _parse_at(text) + data = { + "dynamic_id": 0, + "type": 4, + "rid": 0, + "content": new_text, + "extension": '{"emoji_type":1}', + "at_uids": at_uids, + "ctrl": ctrl, + } + return data + + +async def _get_draw_data( + text: str, images: List[Picture], credential: Credential +) -> dict: + """ + 获取图片动态请求参数,将会自动上传图片 + + Args: + text (str) : 文本内容 + + images (List[Picture]): 图片流 + """ + new_text, at_uids, ctrl = await _parse_at(text) + images_info = await asyncio.gather( + *[upload_image(stream, credential) for stream in images] + ) + + def transformPicInfo(image: dict): + return { + "img_src": image["image_url"], + "img_width": image["image_width"], + "img_height": image["image_height"], + } + + pictures = list(map(transformPicInfo, images_info)) + data = { + "biz": 3, + "category": 3, + "type": 0, + "pictures": json.dumps(pictures), + "title": "", + "tags": "", + "description": new_text, + "content": new_text, + "from": "create.dynamic.web", + "up_choose_comment": 0, + "extension": json.dumps( + {"emoji_type": 1, "from": {"emoji_type": 1}, "flag_cfg": {}} + ), + "at_uids": at_uids, + "at_control": ctrl, + "setting": json.dumps({"copy_forbidden": 0, "cachedTime": 0}), + } + return data + + +async def upload_image( + image: Picture, credential: Credential, data: dict = None +) -> dict: + """ + 上传动态图片 + + Args: + image (Picture) : 图片流. 有格式要求. + + credential (Credential): 凭据 + + data (dict): 自定义请求体 + Returns: + dict: 调用 API 返回的结果 + """ + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + + api = API["send"]["upload_img"] + raw = image.content + + if data is None: + data = {"biz": "new_dyn", "category": "daily"} + + files = {"file_up": open(image._write_to_temp_file(), "rb")} + return_info = ( + await Api(**api, credential=credential).update_data(**data).request(files=files) + ) + return return_info + + +def upload_image_sync( + image: Picture, credential: Credential, data: dict = None +) -> dict: + """ + 上传动态图片 (同步函数) + + Args: + image (Picture) : 图片流. 有格式要求. + + credential (Credential): 凭据 + + data (dict): 自定义请求体 + Returns: + dict: 调用 API 返回的结果 + """ + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + + api = API["send"]["upload_img"] + raw = image.content + + if data is None: + data = {"biz": "new_dyn", "category": "daily"} + + files = {"file_up": open(image._write_to_temp_file(), "rb")} + return_info = ( + Api(**api, credential=credential).update_data(**data).request_sync(files=files) + ) + return return_info + + +class BuildDynamic: + """ + 构建动态内容. 提供两种 API. + + - 1. 链式调用构建 + + ``` python + BuildDynamic.empty().add_plain_text("114514").add_image(Picture.from_url("https://www.bilibili.com/favicon.ico")) + ``` + + - 2. 参数构建 + + ``` python + BuildDynamic.create_by_args(text="114514", topic_id=114514) + ``` + """ + + def __init__(self) -> None: + """ + 构建动态内容 + """ + self.contents: list = [] + self.pics: List[Picture] = [] + self.attach_card: Optional[dict] = None + self.topic: Optional[dict] = None + self.options: dict = {} + self.time: Optional[datetime] = None + + @staticmethod + def empty(): + """ + 新建空的动态以链式逐步构建 + """ + return BuildDynamic() + + @staticmethod + def create_by_args( + text: str = "", + pics: List[Picture] = [], + topic_id: int = -1, + vote_id: int = -1, + live_reserve_id: int = -1, + send_time: Union[datetime, None] = None, + ): + """ + 通过参数构建动态 + + Args: + text (str , optional): 动态文字. Defaults to "". + + pics (List[Picture] , optional): 动态图片列表. Defaults to []. + + topic_id (int , optional): 动态话题 id. Defaults to -1. + + vote_id (int , optional): 动态中的投票的 id. 将放在整个动态的最后面. Defaults to -1. + + live_reserve_id (int , optional): 直播预约 oid. 通过 `live.create_live_reserve` 获取. Defaults to -1. + + send_time (datetime | None, optional): 发送时间. Defaults to None. + """ + dyn = BuildDynamic() + dyn.add_text(text) + dyn.add_image(pics) + if topic_id != -1: + dyn.set_topic(topic_id) + if vote_id != -1: + dyn.add_vote(vote.Vote(vote_id=vote_id)) + if live_reserve_id != -1: + dyn.set_attach_card(live_reserve_id) + if send_time != None: + dyn.set_send_time(send_time) + return dyn + + def add_plain_text(self, text: str) -> "BuildDynamic": + """ + 添加纯文本 + + Args: + text (str): 文本内容 + """ + self.contents.append( + {"biz_id": "", "type": DynamicContentType.TEXT.value, "raw_text": text} + ) + return self + + def add_at(self, uid: Union[int, user.User]) -> "BuildDynamic": + """ + 添加@用户,支持传入 User 类或 UID + + Args: + uid (Union[int, user.User]): 用户ID + """ + if isinstance(uid, user.User): + uid = uid.__uid + name = user.User(uid).get_user_info_sync().get("name") + self.contents.append( + {"biz_id": uid, "type": DynamicContentType.AT.value, "raw_text": f"@{name}"} + ) + return self + + def add_emoji(self, emoji_id: int) -> "BuildDynamic": + """ + 添加表情 + + Args: + emoji_id (int): 表情ID + """ + with open( + os.path.join(os.path.dirname(__file__), "data/emote.json"), encoding="UTF-8" + ) as f: + emote_info = json.load(f) + if str(emoji_id) not in emote_info: + raise ValueError("不存在此表情") + self.contents.append( + { + "biz_id": "", + "type": DynamicContentType.EMOJI.value, + "raw_text": emote_info[str(emoji_id)], + } + ) + return self + + def add_vote(self, vote: vote.Vote) -> "BuildDynamic": + vote.get_info_sync() + self.contents.append( + { + "biz_id": str(vote.get_vote_id()), + "type": DynamicContentType.VOTE.value, + "raw_text": vote.title, + } + ) + return self + + def add_image(self, image: Union[List[Picture], Picture]) -> "BuildDynamic": + """ + 添加图片 + + Args: + image (Picture | List[Picture]): 图片类 + """ + if isinstance(image, Picture): + image = [image] + self.pics += image + return self + + def add_text(self, text: str) -> "BuildDynamic": + """ + 添加文本 (可包括 at, 表情包) + + Args: + text (str): 文本内容 + """ + + def _get_ats(text: str) -> List: + text += " " + pattern = re.compile(r"(?<=@).*?(?=\s)") + match_result = re.finditer(pattern, text) + uid_list = [] + names = [] + for match in match_result: + uname = match.group() + try: + name_to_uid_resp = name2uid_sync(uname) + uid = name_to_uid_resp["uid_list"][0]["uid"] + except KeyError: + # 没有此用户 + continue + uid_list.append(str(uid)) + names.append(uname) + data = [] + last_index = 0 + for i, name in enumerate(names): + index = text.index(f"@{name}", last_index) + last_index = index + 1 + length = 2 + len(name) + data.append( + { + "location": index, + "length": length, + "text": f"@{name} ", + "type": "at", + "uid": uid_list[i], + } + ) + return data + + def _get_emojis(text: str) -> List: + with open( + os.path.join(os.path.dirname(__file__), "data/emote.json"), + encoding="UTF-8", + ) as f: + emote_info = json.load(f) + all_emojis = [] + for key, item in emote_info.items(): + all_emojis.append(item) + pattern = re.compile(r"(?<=\[).*?(?=\])") + match_result = re.finditer(pattern, text) + emotes = [] + for match in match_result: + emote = match.group(0) + if f"[{emote}]" not in all_emojis: + continue + emotes.append(f"[{emote}]") + data = [] + last_index = 0 + for i, emoji in enumerate(emotes): + index = text.index(emoji, last_index) + last_index = index + 1 + length = len(emoji) + data.append( + { + "location": index, + "length": length, + "text": emoji, + "type": "emoji", + } + ) + return data + + all_at_and_emoji = _get_ats(text) + _get_emojis(text) + + def split_text_to_plain_at_and_emoji(text: str, at_and_emoji: List): + def base_split(texts: List[str], at_and_emoji: List, last_length: int): + if len(at_and_emoji) == 0: + return texts + last_piece_of_text = texts.pop(-1) + next_at_or_emoji = at_and_emoji.pop(0) + texts += [ + last_piece_of_text[: next_at_or_emoji["location"] - last_length], + next_at_or_emoji, + last_piece_of_text[ + next_at_or_emoji["location"] + + next_at_or_emoji["length"] + - last_length : + ], + ] + last_length += ( + next_at_or_emoji["length"] + + next_at_or_emoji["location"] + - last_length + ) + return base_split(texts, at_and_emoji, last_length) + + old_recursion_limit = sys.getrecursionlimit() + sys.setrecursionlimit(100000) + all_pieces = base_split([text], at_and_emoji, 0) + sys.setrecursionlimit(old_recursion_limit) + return all_pieces + + all_pieces = split_text_to_plain_at_and_emoji(text, all_at_and_emoji) + for piece in all_pieces: + if isinstance(piece, str): + self.add_plain_text(piece) + elif piece["type"] == "at": + self.contents.append( + { + "biz_id": piece["uid"], + "type": DynamicContentType.AT.value, + "raw_text": piece["text"], + } + ) + else: + self.contents.append( + { + "biz_id": "", + "type": DynamicContentType.EMOJI.value, + "raw_text": piece["text"], + } + ) + return self + + def set_attach_card(self, oid: int) -> "BuildDynamic": + """ + 设置直播预约 + + 在 live.create_live_reserve 中获取 oid + + Args: + oid (int): 卡片oid + """ + self.attach_card = { + "type": 14, + "biz_id": oid, + "reserve_source": 1, # 疑似0为视频预告但没法验证... + "reserve_lottery": 0, + } + return self + + def set_topic(self, topic_id: int) -> "BuildDynamic": + """ + 设置话题 + + Args: + topic_id (int): 话题ID + """ + self.topic = {"id": topic_id} + return self + + def set_options( + self, up_choose_comment: bool = False, close_comment: bool = False + ) -> "BuildDynamic": + """ + 设置选项 + + Args: + up_choose_comment (bool): 精选评论flag + + close_comment (bool): 关闭评论flag + """ + if up_choose_comment: + self.options["up_choose_comment"] = 1 + if close_comment: + self.options["close_comment"] = 1 + return self + + def set_send_time(self, time: datetime): + """ + 设置发送时间 + + Args: + time (datetime): 发送时间 + """ + self.time = time + return self + + def get_dynamic_type(self) -> SendDynamicType: + if len(self.pics) != 0: + return SendDynamicType.IMAGE + return SendDynamicType.TEXT + + def get_contents(self) -> list: + return self.contents + + def get_pics(self) -> list: + return self.pics + + def get_attach_card(self) -> Optional[dict]: + return self.attach_card + + def get_topic(self) -> Optional[dict]: + return self.topic + + def get_options(self) -> dict: + return self.options + + +async def send_dynamic(info: BuildDynamic, credential: Credential): + """ + 发送动态 + + Args: + info (BuildDynamic): 动态内容 + + credential (Credential): 凭据 + + Returns: + dict: 调用 API 返回的结果 + """ + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + pic_data = [] + for image in info.pics: + await image.upload_file(credential) + pic_data.append( + {"img_src": image.url, "img_width": image.width, "img_height": image.height} + ) + + async def schedule(type_: int): + api = API["send"]["schedule"] + text = "".join( + [part["raw_text"] for part in info.contents if part["type"] != 4] + ) + send_time = info.time + if len(info.pics) > 0: + # 画册动态 + request_data = await _get_draw_data(text, info.pics, credential) # type: ignore + request_data.pop("setting") + else: + # 文字动态 + request_data = await _get_text_data(text) + data = { + "type": type_, + "publish_time": int(send_time.timestamp()), # type: ignore + "request": json.dumps(request_data, ensure_ascii=False), + } + return await Api(**api, credential=credential).update_data(**data).result + + if info.time != None: + return await schedule(2 if len(info.pics) == 0 else 4) + api = API["send"]["instant"] + data = { + "dyn_req": { + "content": {"contents": info.get_contents()}, # 必要参数 + "scene": info.get_dynamic_type().value, # 必要参数 + "meta": { + "app_meta": {"from": "create.dynamic.web", "mobi_app": "web"}, + }, + } + } + if len(info.get_pics()) != 0: + data["dyn_req"]["pics"] = pic_data + if info.get_topic() is not None: + data["dyn_req"]["topic"] = info.get_topic() + if len(info.get_options()) > 0: + data["dyn_req"]["option"] = info.get_options() + if info.get_attach_card() is not None: + data["dyn_req"]["attach_card"] = {} + data["dyn_req"]["attach_card"]["common_card"] = info.get_attach_card() + else: + data["dyn_req"]["attach_card"] = None + params = {"csrf": credential.bili_jct} + send_result = ( + await Api(**api, credential=credential, json_body=True) + .update_data(**data) + .update_params(**params) + .result + ) + return send_result + + +# 定时动态操作 + + +async def get_schedules_list(credential: Credential) -> dict: + """ + 获取待发送定时动态列表 + + Args: + credential (Credential): 凭据 + + Returns: + dict: 调用 API 返回的结果 + """ + credential.raise_for_no_sessdata() + + api = API["schedule"]["list"] + return await Api(**api, credential=credential).result + + +async def send_schedule_now(draft_id: int, credential: Credential) -> dict: + """ + 立即发送定时动态 + + Args: + draft_id (int): 定时动态 ID + + credential (Credential): 凭据 + + Returns: + dict: 调用 API 返回的结果 + """ + credential.raise_for_no_sessdata() + + api = API["schedule"]["publish_now"] + data = {"draft_id": draft_id} + return await Api(**api, credential=credential).update_data(**data).result + + +async def delete_schedule(draft_id: int, credential: Credential) -> dict: + """ + 删除定时动态 + + Args: + draft_id (int): 定时动态 ID + + credential (Credential): 凭据 + + Returns: + dict: 调用 API 返回的结果 + """ + credential.raise_for_no_sessdata() + + api = API["schedule"]["delete"] + data = {"draft_id": draft_id} + return await Api(**api, credential=credential).update_data(**data).result + + +class Dynamic: + """ + 动态类 + + Attributes: + credential (Credential): 凭据类 + """ + + def __init__( + self, dynamic_id: int, credential: Union[Credential, None] = None + ) -> None: + """ + Args: + dynamic_id (int) : 动态 ID + credential (Credential | None, optional): 凭据类. Defaults to None. + """ + self.__dynamic_id = dynamic_id + self.credential = credential if credential is not None else Credential() + + if cache_pool.dynamic_is_opus.get(self.__dynamic_id): + self.__opus = cache_pool.dynamic_is_opus[self.__dynamic_id] + else: + 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.__opus = data["item"]["basic"]["comment_type"] != 11 + cache_pool.dynamic_is_opus[self.__dynamic_id] = self.__opus + + def get_dynamic_id(self) -> int: + return self.__dynamic_id + + def is_opus(self) -> DynamicType: + """ + 判断是否为 opus 动态 + + Returns: + bool: 是否为 opus 动态 + """ + return self.__opus + + def turn_to_opus(self) -> "opus.Opus": + """ + 对 opus 动态,将其转换为图文 + """ + raise_for_statement(self.__opus, "仅支持图文动态") + return opus.Opus(self.__dynamic_id, credential=self.credential) + + async def get_info(self, features: str = "itemOpusStyle") -> dict: + """ + (对 Opus 动态,获取动态内容建议使用 Opus.get_detail()) + + 获取动态信息 + + Args: + features (str, optional): 默认 itemOpusStyle. + + Returns: + dict: 调用 API 返回的结果 + """ + + api = API["info"]["detail"] + params = { + "id": self.__dynamic_id, + "timezone_offset": -480, + "features": features, + } + data = ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + return data + + async def get_reaction(self, offset: str = "") -> dict: + """ + 获取点赞、转发 + + Args: + offset (str, optional): 偏移值(下一页的第一个动态 ID,为该请求结果中的 offset 键对应的值),类似单向链表. Defaults to "" + + Returns: + dict: 调用 API 返回的结果 + """ + + 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 + ) + + async def get_reposts(self, offset: str = "0") -> dict: + """ + 获取动态转发列表 + + Args: + offset (str, optional): 偏移值(下一页的第一个动态 ID,为该请求结果中的 offset 键对应的值),类似单向链表. Defaults to "0" + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["repost"] + params: dict[str, Any] = {"dynamic_id": self.__dynamic_id} + if offset != "0": + params["offset"] = offset + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_likes(self, pn: int = 1, ps: int = 30) -> dict: + """ + 获取动态点赞列表 + + Args: + pn (int, optional): 页码,defaults to 1 + + ps (int, optional): 每页大小,defaults to 30 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["likes"] + params = {"dynamic_id": self.__dynamic_id, "pn": pn, "ps": ps} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def set_like(self, status: bool = True) -> dict: + """ + 设置动态点赞状态 + + Args: + status (bool, optional): 点赞状态. Defaults to True. + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["operate"]["like"] + + user_info = await user.get_self_info(credential=self.credential) + + self_uid = user_info["mid"] + data = { + "dynamic_id": self.__dynamic_id, + "up": 1 if status else 2, + "uid": self_uid, + } + return await Api(**api, credential=self.credential).update_data(**data).result + + async def delete(self) -> dict: + """ + 删除动态 + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + + api = API["operate"]["delete"] + data = {"dynamic_id": self.__dynamic_id} + return await Api(**api, credential=self.credential).update_data(**data).result + + async def repost(self, text: str = "转发动态") -> dict: + """ + 转发动态 + + Args: + text (str, optional): 转发动态时的文本内容. Defaults to "转发动态" + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + + api = API["operate"]["repost"] + data = await _get_text_data(text) + data["dynamic_id"] = self.__dynamic_id + return await Api(**api, credential=self.credential).update_data(**data).result + + +async def get_new_dynamic_users(credential: Union[Credential, None] = None) -> dict: + """ + 获取更新动态的关注者 + + Args: + credential (Credential | None): 凭据类. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + credential = credential if credential else Credential() + credential.raise_for_no_sessdata() + api = API["info"]["attention_new_dynamic"] + return await Api(**api, credential=credential).result + + +async def get_live_users( + size: int = 10, credential: Union[Credential, None] = None +) -> dict: + """ + 获取正在直播的关注者 + + Args: + size (int) : 获取的数据数量. Defaults to 10. + + credential (Credential | None): 凭据类. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + credential = credential if credential else Credential() + credential.raise_for_no_sessdata() + api = API["info"]["attention_live"] + params = {"size": size} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_dynamic_page_UPs_info(credential: Credential) -> dict: + """ + 获取动态页 UP 主列表 + + Args: + credential (Credential): 凭据类. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["dynamic_page_UPs_info"] + return await Api(**api, credential=credential).result + + +async def get_dynamic_page_info( + credential: Credential, + _type: Optional[DynamicType] = None, + host_mid: Optional[int] = None, + features: str = "itemOpusStyle", + pn: int = 1, + offset: Optional[int] = None, +) -> List[Dynamic]: + """ + 获取动态页动态信息 + + 获取全部动态或者相应类型需传入 _type + + 获取指定 UP 主动态需传入 host_mid + + Args: + credential (Credential): 凭据类. + + _type (DynamicType, optional): 动态类型. Defaults to DynamicType.ALL. + + host_mid (int, optional): 获取对应 UP 主动态的 mid. Defaults to None. + + features (str, optional): 默认 itemOpusStyle. + + pn (int, optional): 页码. Defaults to 1. + + offset (int, optional): 偏移值(下一页的第一个动态 ID,为该请求结果中的 offset 键对应的值),类似单向链表. Defaults to None. + + Returns: + list[Dynamic]: 动态类列表 + """ + + api = API["info"]["dynamic_page_info"] + params = { + "timezone_offset": -480, + "features": features, + "page": pn, + } + params.update({"offset": offset} if offset else {}) + if _type: # 全部动态 + params["type"] = _type.value + elif host_mid: # 指定 UP 主动态 + params["host_mid"] = host_mid + elif not _type: + api["params"].pop("type") + elif not host_mid: + api["params"].pop("host_mid") + + dynmaic_data = ( + await Api(**api, credential=credential).update_params(**params).result + ) + return [ + Dynamic(dynamic_id=int(dynamic["id_str"]), credential=credential) + for dynamic in dynmaic_data["items"] + ] diff --git a/bilibili_api/emoji.py b/bilibili_api/emoji.py new file mode 100644 index 0000000000000000000000000000000000000000..da38ba1a3eaadbd2df2c395cb133639fe2f0e3d7 --- /dev/null +++ b/bilibili_api/emoji.py @@ -0,0 +1,25 @@ +""" +bilibili_api.emoji + +表情包相关 +""" + +from .utils.utils import get_api +from .utils.network import Api + +API = get_api("emoji") + + +async def get_emoji_list(business: str = "reply") -> dict: + """ + 获取表情包列表 + + Args: + business (str): 使用场景, reply / dynamic + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["list"] + params = {"business": business} + return await Api(**api).update_params(**params).result diff --git a/bilibili_api/errors/__init__.py b/bilibili_api/errors/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e756ebc72d40849d42d24e0db16b75c09c10c5d6 --- /dev/null +++ b/bilibili_api/errors/__init__.py @@ -0,0 +1,7 @@ +""" +bilibili_api.errors + +错误 +""" + +from ..exceptions import * diff --git a/bilibili_api/errors/__pycache__/__init__.cpython-38.pyc b/bilibili_api/errors/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..90aed12347e22052857f9ffba823aafcc3c5e539 Binary files /dev/null and b/bilibili_api/errors/__pycache__/__init__.cpython-38.pyc differ diff --git a/bilibili_api/exceptions/ApiException.py b/bilibili_api/exceptions/ApiException.py new file mode 100644 index 0000000000000000000000000000000000000000..c473e144ef0094795afbb7f274310ea3dbb72b62 --- /dev/null +++ b/bilibili_api/exceptions/ApiException.py @@ -0,0 +1,18 @@ +""" +bilibili_api.exceptions.ApiException + +API 异常基类。 +""" + + +class ApiException(Exception): + """ + API 基类异常。 + """ + + def __init__(self, msg: str = "出现了错误,但是未说明具体原因。"): + super().__init__(msg) + self.msg = msg + + def __str__(self): + return self.msg diff --git a/bilibili_api/exceptions/ArgsException.py b/bilibili_api/exceptions/ArgsException.py new file mode 100644 index 0000000000000000000000000000000000000000..f977d5fb0f1058d2fc43e6e129cd3e41fd449773 --- /dev/null +++ b/bilibili_api/exceptions/ArgsException.py @@ -0,0 +1,21 @@ +""" +bilibili_api.exceptions.ArgsException + +参数错误。 +""" + +from .ApiException import ApiException + + +class ArgsException(ApiException): + """ + 参数错误。 + """ + + def __init__(self, msg: str): + """ + Args: + msg (str): 错误消息。 + """ + super().__init__(msg) + self.msg = msg diff --git a/bilibili_api/exceptions/CredentialNoAcTimeValueException.py b/bilibili_api/exceptions/CredentialNoAcTimeValueException.py new file mode 100644 index 0000000000000000000000000000000000000000..26d5c4e9fb4bf7af83c9c509b83bff359481446c --- /dev/null +++ b/bilibili_api/exceptions/CredentialNoAcTimeValueException.py @@ -0,0 +1,17 @@ +""" +bilibili_api.exceptions.CredentialNoAcTimeValue + +Credential 类未提供 ac_time_value 时的异常。 +""" + +from .ApiException import ApiException + + +class CredentialNoAcTimeValueException(ApiException): + """ + Credential 类未提供 ac_time_value 时的异常。 + """ + + def __init__(self): + super().__init__() + self.msg = "Credential 类未提供 ac_time_value 或者为空。" diff --git a/bilibili_api/exceptions/CredentialNoBiliJctException.py b/bilibili_api/exceptions/CredentialNoBiliJctException.py new file mode 100644 index 0000000000000000000000000000000000000000..8fbea3f5b7adfe43076cfe038b366f668164ae81 --- /dev/null +++ b/bilibili_api/exceptions/CredentialNoBiliJctException.py @@ -0,0 +1,17 @@ +""" +bilibili_api.exceptions.CredentialNoBiliJctException + +Credential 类未提供 bili_jct 时的异常。 +""" + +from .ApiException import ApiException + + +class CredentialNoBiliJctException(ApiException): + """ + Credential 类未提供 bili_jct 时的异常。 + """ + + def __init__(self): + super().__init__() + self.msg = "Credential 类未提供 bili_jct 或者为空。" diff --git a/bilibili_api/exceptions/CredentialNoBuvid3Exception.py b/bilibili_api/exceptions/CredentialNoBuvid3Exception.py new file mode 100644 index 0000000000000000000000000000000000000000..2529a196e2d7679ec8aa82c31b934287906a1e68 --- /dev/null +++ b/bilibili_api/exceptions/CredentialNoBuvid3Exception.py @@ -0,0 +1,17 @@ +""" +bilibili_api.exceptions.CredentialNoBuvid3Exception + +Credential 类未提供 buvid3 时的异常。 +""" + +from .ApiException import ApiException + + +class CredentialNoBuvid3Exception(ApiException): + """ + Credential 类未提供 bili_jct 时的异常。 + """ + + def __init__(self): + super().__init__() + self.msg = "Credential 类未提供 buvid3 或者为空。" diff --git a/bilibili_api/exceptions/CredentialNoDedeUserIDException.py b/bilibili_api/exceptions/CredentialNoDedeUserIDException.py new file mode 100644 index 0000000000000000000000000000000000000000..e08188df6a18cae9621112325920b987485f641a --- /dev/null +++ b/bilibili_api/exceptions/CredentialNoDedeUserIDException.py @@ -0,0 +1,17 @@ +""" +bilibili_api.exceptions.CredentialNoDedeUserID + +Credential 类未提供 sessdata 时的异常。 +""" + +from .ApiException import ApiException + + +class CredentialNoDedeUserIDException(ApiException): + """ + Credential 类未提供 DedeUserID 时的异常。 + """ + + def __init__(self): + super().__init__() + self.msg = "Credential 类未提供 DedeUserID 或者为空。" diff --git a/bilibili_api/exceptions/CredentialNoSessdataException.py b/bilibili_api/exceptions/CredentialNoSessdataException.py new file mode 100644 index 0000000000000000000000000000000000000000..e750c65042822b9af244a676f317a936dd93a831 --- /dev/null +++ b/bilibili_api/exceptions/CredentialNoSessdataException.py @@ -0,0 +1,17 @@ +""" +bilibili_api.exceptions.CredentialNoSessdataException + +Credential 类未提供 sessdata 时的异常。 +""" + +from .ApiException import ApiException + + +class CredentialNoSessdataException(ApiException): + """ + Credential 类未提供 sessdata 时的异常。 + """ + + def __init__(self): + super().__init__() + self.msg = "Credential 类未提供 sessdata 或者为空。" diff --git a/bilibili_api/exceptions/DanmakuClosedException.py b/bilibili_api/exceptions/DanmakuClosedException.py new file mode 100644 index 0000000000000000000000000000000000000000..b5c3b983c13bcbb73a18f4e656b2109c688fcb32 --- /dev/null +++ b/bilibili_api/exceptions/DanmakuClosedException.py @@ -0,0 +1,17 @@ +""" +bilibili_api.exceptions.DanmakuClosedException + +视频弹幕被关闭错误。 +""" + +from .ApiException import ApiException + + +class DanmakuClosedException(ApiException): + """ + 视频弹幕被关闭错误。 + """ + + def __init__(self): + super().__init__() + self.msg = "视频弹幕已关闭。" diff --git a/bilibili_api/exceptions/DynamicExceedImagesException.py b/bilibili_api/exceptions/DynamicExceedImagesException.py new file mode 100644 index 0000000000000000000000000000000000000000..680ac8741e37154818e57d1e25b55f81f30699b2 --- /dev/null +++ b/bilibili_api/exceptions/DynamicExceedImagesException.py @@ -0,0 +1,17 @@ +""" +bilibili_api.exceptions.DanmakuClosedException + +动态上传图片数量超过限制 +""" + +from .ApiException import ApiException + + +class DynamicExceedImagesException(ApiException): + """ + 动态上传图片数量超过限制 + """ + + def __init__(self): + super().__init__() + self.msg = "最多上传 9 张图片" diff --git a/bilibili_api/exceptions/LiveException.py b/bilibili_api/exceptions/LiveException.py new file mode 100644 index 0000000000000000000000000000000000000000..59e3701a94af1cc3ad23180b33a8a2a635f97f43 --- /dev/null +++ b/bilibili_api/exceptions/LiveException.py @@ -0,0 +1,12 @@ +""" +bilibili_api.exceptions.LiveException + +""" + +from .ApiException import ApiException + + +class LiveException(ApiException): + def __init__(self, msg: str): + super().__init__() + self.msg = msg diff --git a/bilibili_api/exceptions/LoginError.py b/bilibili_api/exceptions/LoginError.py new file mode 100644 index 0000000000000000000000000000000000000000..252c60ddecbb16e723c9584cbfb17de811546385 --- /dev/null +++ b/bilibili_api/exceptions/LoginError.py @@ -0,0 +1,21 @@ +""" +bilibili_api.exceptions.LoginError + +登录错误。 +""" + +from .ApiException import ApiException + + +class LoginError(ApiException): + """ + 参数错误。 + """ + + def __init__(self, msg: str): + """ + Args: + msg (str): 错误消息。 + """ + super().__init__(msg) + self.msg = msg diff --git a/bilibili_api/exceptions/NetworkException.py b/bilibili_api/exceptions/NetworkException.py new file mode 100644 index 0000000000000000000000000000000000000000..1ab73e00bc84d78d8ca0a7fd7e397e3d24710ac2 --- /dev/null +++ b/bilibili_api/exceptions/NetworkException.py @@ -0,0 +1,28 @@ +""" +bilibili_api.exceptions.NetworkException + +网络错误。 +""" + +from .ApiException import ApiException + + +class NetworkException(ApiException): + """ + 网络错误。 + """ + + def __init__(self, status: int, msg: str): + """ + + Args: + status (int): 状态码。 + + msg (str): 状态消息。 + """ + super().__init__(msg) + self.status = status + self.msg = f"网络错误,状态码:{status} - {msg}。" + + def __str__(self): + return self.msg diff --git a/bilibili_api/exceptions/ResponseCodeException.py b/bilibili_api/exceptions/ResponseCodeException.py new file mode 100644 index 0000000000000000000000000000000000000000..fd1e48bc3545a731b8d73fd55a51d3d6e0d59f44 --- /dev/null +++ b/bilibili_api/exceptions/ResponseCodeException.py @@ -0,0 +1,31 @@ +""" +bilibili_api.exceptions.ResponseCodeException + +API 返回 code 错误。 +""" + +from .ApiException import ApiException + + +class ResponseCodeException(ApiException): + """ + API 返回 code 错误。 + """ + + def __init__(self, code: int, msg: str, raw: dict = None): + """ + + Args: + code (int): 错误代码。 + + msg (str): 错误信息。 + + raw (dict, optional): 原始响应数据. Defaults to None. + """ + super().__init__(msg) + self.msg = msg + self.code = code + self.raw = raw + + def __str__(self): + return f"接口返回错误代码:{self.code},信息:{self.msg}。\n{self.raw}" diff --git a/bilibili_api/exceptions/ResponseException.py b/bilibili_api/exceptions/ResponseException.py new file mode 100644 index 0000000000000000000000000000000000000000..ec53c6600b56907f79187933cfcc1e4cdf7c98b0 --- /dev/null +++ b/bilibili_api/exceptions/ResponseException.py @@ -0,0 +1,22 @@ +""" +bilibili_api.exceptions.ResponseException + +API 响应异常。 +""" + +from .ApiException import ApiException + + +class ResponseException(ApiException): + """ + API 响应异常。 + """ + + def __init__(self, msg: str): + """ + + Args: + msg (str): 错误消息。 + """ + super().__init__(msg) + self.msg = msg diff --git a/bilibili_api/exceptions/StatementException.py b/bilibili_api/exceptions/StatementException.py new file mode 100644 index 0000000000000000000000000000000000000000..70381671c52e78c42a69a725cc61f7d84b1a4e3a --- /dev/null +++ b/bilibili_api/exceptions/StatementException.py @@ -0,0 +1,22 @@ +""" +bilibili_api.exceptions.StatementException + +条件异常。 +""" + +from .ApiException import ApiException + + +class StatementException(ApiException): + """ + 条件异常。 + """ + + def __init__(self, msg: str): + """ + + Args: + msg (str): 错误消息。 + """ + super().__init__(msg) + self.msg = msg diff --git a/bilibili_api/exceptions/VideoUploadException.py b/bilibili_api/exceptions/VideoUploadException.py new file mode 100644 index 0000000000000000000000000000000000000000..ad06a292090dd4ede2c996e16c19c29e7c066aca --- /dev/null +++ b/bilibili_api/exceptions/VideoUploadException.py @@ -0,0 +1,21 @@ +""" +bilibili_api.exceptions.VideoUploadException + +视频上传错误。 +""" + +from .ApiException import ApiException + + +class VideoUploadException(ApiException): + """ + 视频上传错误。 + """ + + def __init__(self, msg: str): + """ + Args: + msg (str): 错误消息。 + """ + super().__init__(msg) + self.msg = msg diff --git a/bilibili_api/exceptions/__init__.py b/bilibili_api/exceptions/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9fb8578c83b7a953e2867140d229d5fcaa182f3e --- /dev/null +++ b/bilibili_api/exceptions/__init__.py @@ -0,0 +1,22 @@ +""" +bilibili_api.exceptions + +错误 +""" + +from .LoginError import * +from .ApiException import * +from .ArgsException import * +from .NetworkException import * +from .ResponseException import * +from .VideoUploadException import * +from .ResponseCodeException import * +from .DanmakuClosedException import * +from .LiveException import * +from .CredentialNoBuvid3Exception import * +from .CredentialNoBiliJctException import * +from .CredentialNoSessdataException import * +from .CredentialNoDedeUserIDException import * +from .DynamicExceedImagesException import * +from .CredentialNoAcTimeValueException import * +from .StatementException import * diff --git a/bilibili_api/exceptions/__pycache__/ApiException.cpython-38.pyc b/bilibili_api/exceptions/__pycache__/ApiException.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ab45a4e7ec86ba7a51699d21df7c1ad49d61bc47 Binary files /dev/null and b/bilibili_api/exceptions/__pycache__/ApiException.cpython-38.pyc differ diff --git a/bilibili_api/exceptions/__pycache__/ArgsException.cpython-38.pyc b/bilibili_api/exceptions/__pycache__/ArgsException.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..55840a338b6e0d49d88b9a6cf83cafe901e60663 Binary files /dev/null and b/bilibili_api/exceptions/__pycache__/ArgsException.cpython-38.pyc differ diff --git a/bilibili_api/exceptions/__pycache__/CredentialNoAcTimeValueException.cpython-38.pyc b/bilibili_api/exceptions/__pycache__/CredentialNoAcTimeValueException.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fec357a0cea89768dc08ee7db2f6ce1e955147a9 Binary files /dev/null and b/bilibili_api/exceptions/__pycache__/CredentialNoAcTimeValueException.cpython-38.pyc differ diff --git a/bilibili_api/exceptions/__pycache__/CredentialNoBiliJctException.cpython-38.pyc b/bilibili_api/exceptions/__pycache__/CredentialNoBiliJctException.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..62d45151dea499e953083a2d5da988c7053733f8 Binary files /dev/null and b/bilibili_api/exceptions/__pycache__/CredentialNoBiliJctException.cpython-38.pyc differ diff --git a/bilibili_api/exceptions/__pycache__/CredentialNoBuvid3Exception.cpython-38.pyc b/bilibili_api/exceptions/__pycache__/CredentialNoBuvid3Exception.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..19ea649dacaccb4d00ba4a52b10d229c17b527cb Binary files /dev/null and b/bilibili_api/exceptions/__pycache__/CredentialNoBuvid3Exception.cpython-38.pyc differ diff --git a/bilibili_api/exceptions/__pycache__/CredentialNoDedeUserIDException.cpython-38.pyc b/bilibili_api/exceptions/__pycache__/CredentialNoDedeUserIDException.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a4e71c806fa6cc6a3c2364fe3ab15d7244e6a529 Binary files /dev/null and b/bilibili_api/exceptions/__pycache__/CredentialNoDedeUserIDException.cpython-38.pyc differ diff --git a/bilibili_api/exceptions/__pycache__/CredentialNoSessdataException.cpython-38.pyc b/bilibili_api/exceptions/__pycache__/CredentialNoSessdataException.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee78041b98a43ca79c78c7d061eb8b6ea5fb78a1 Binary files /dev/null and b/bilibili_api/exceptions/__pycache__/CredentialNoSessdataException.cpython-38.pyc differ diff --git a/bilibili_api/exceptions/__pycache__/DanmakuClosedException.cpython-38.pyc b/bilibili_api/exceptions/__pycache__/DanmakuClosedException.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..430e5c15963931cd15ad2c0332544d30dd7c37a0 Binary files /dev/null and b/bilibili_api/exceptions/__pycache__/DanmakuClosedException.cpython-38.pyc differ diff --git a/bilibili_api/exceptions/__pycache__/DynamicExceedImagesException.cpython-38.pyc b/bilibili_api/exceptions/__pycache__/DynamicExceedImagesException.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4482d8350b24befe88102d7ebd4e719365461c49 Binary files /dev/null and b/bilibili_api/exceptions/__pycache__/DynamicExceedImagesException.cpython-38.pyc differ diff --git a/bilibili_api/exceptions/__pycache__/LiveException.cpython-38.pyc b/bilibili_api/exceptions/__pycache__/LiveException.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26f92f82b732ec43d7c8f7a8086bf47a87f5acf0 Binary files /dev/null and b/bilibili_api/exceptions/__pycache__/LiveException.cpython-38.pyc differ diff --git a/bilibili_api/exceptions/__pycache__/LoginError.cpython-38.pyc b/bilibili_api/exceptions/__pycache__/LoginError.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8bfada682f77996dc5a401e358717c91b1471c51 Binary files /dev/null and b/bilibili_api/exceptions/__pycache__/LoginError.cpython-38.pyc differ diff --git a/bilibili_api/exceptions/__pycache__/NetworkException.cpython-38.pyc b/bilibili_api/exceptions/__pycache__/NetworkException.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d104d1bef0cacfacd9fefa767eea2da25f7c433a Binary files /dev/null and b/bilibili_api/exceptions/__pycache__/NetworkException.cpython-38.pyc differ diff --git a/bilibili_api/exceptions/__pycache__/ResponseCodeException.cpython-38.pyc b/bilibili_api/exceptions/__pycache__/ResponseCodeException.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d44d748ecf09988c9a5565ee007553509e86bae8 Binary files /dev/null and b/bilibili_api/exceptions/__pycache__/ResponseCodeException.cpython-38.pyc differ diff --git a/bilibili_api/exceptions/__pycache__/ResponseException.cpython-38.pyc b/bilibili_api/exceptions/__pycache__/ResponseException.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..71e8c42a16ccb75e14624d530bf1f605e6561d0b Binary files /dev/null and b/bilibili_api/exceptions/__pycache__/ResponseException.cpython-38.pyc differ diff --git a/bilibili_api/exceptions/__pycache__/StatementException.cpython-38.pyc b/bilibili_api/exceptions/__pycache__/StatementException.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..662f25acceca837238860892212e3720193798b8 Binary files /dev/null and b/bilibili_api/exceptions/__pycache__/StatementException.cpython-38.pyc differ diff --git a/bilibili_api/exceptions/__pycache__/VideoUploadException.cpython-38.pyc b/bilibili_api/exceptions/__pycache__/VideoUploadException.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..72680279b646124f7eddc9706bb76a70f402d72d Binary files /dev/null and b/bilibili_api/exceptions/__pycache__/VideoUploadException.cpython-38.pyc differ diff --git a/bilibili_api/exceptions/__pycache__/__init__.cpython-38.pyc b/bilibili_api/exceptions/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1c67d13518e54a20235cc600c8b746faf3fe0a39 Binary files /dev/null and b/bilibili_api/exceptions/__pycache__/__init__.cpython-38.pyc differ diff --git a/bilibili_api/favorite_list.py b/bilibili_api/favorite_list.py new file mode 100644 index 0000000000000000000000000000000000000000..ef0ddbd12d049b093b8e77c28e49fb0257e8ee4e --- /dev/null +++ b/bilibili_api/favorite_list.py @@ -0,0 +1,602 @@ +""" +bilibili_api.favorite_list + +收藏夹操作。 +""" + +from enum import Enum +from typing import List, Union, Optional + +from . import user +from .video import Video +from .utils.utils import join, get_api, raise_for_statement +from .utils.credential import Credential +from .utils.network import Api +from .exceptions.ArgsException import ArgsException + +API = get_api("favorite-list") + + +class FavoriteListContentOrder(Enum): + """ + 收藏夹列表内容排序方式枚举。 + + + MTIME : 最近收藏 + + VIEW : 最多播放 + + PUBTIME: 最新投稿 + """ + + MTIME = "mtime" + VIEW = "view" + PUBTIME = "pubtime" + + +class FavoriteListType(Enum): + """ + 收藏夹类型枚举 + + + VIDEO : 视频收藏夹 + + ARTICLE: 专栏收藏夹 + + CHEESE : 课程收藏夹 + """ + + VIDEO = "video" + ARTICLE = "articles" + CHEESE = "pugvfav" + + +class SearchFavoriteListMode(Enum): + """ + 收藏夹搜索模式枚举 + + + ONLY : 仅当前收藏夹 + + ALL : 该用户所有收藏夹 + """ + + ONLY = 0 + ALL = 1 + + +class FavoriteList: + """ + 收藏夹类 + + Attributes: + credential (Credential): 凭据类 + """ + + def __init__( + self, + type_: FavoriteListType = FavoriteListType.VIDEO, + media_id: Union[int, None] = None, + credential: Union[Credential, None] = None, + ) -> None: + """ + Args: + type_ (FavoriteListType, optional): 收藏夹类型. Defaults to FavoriteListType.VIDEO. + + media_id (int, optional) : 收藏夹号(仅为视频收藏夹时提供). Defaults to None. + + credential (Credential, optional) : 凭据类. Defaults to Credential(). + """ + self.__type = type_ + self.__media_id = media_id + self.credential = credential if credential else Credential() + + def is_video_favorite_list(self) -> bool: + """ + 收藏夹是否为视频收藏夹 + + Returns: + bool: 是否为视频收藏夹 + """ + return self.__type == FavoriteListType.VIDEO + + def get_media_id(self) -> Union[int, None]: + return self.__media_id + + def get_favorite_list_type(self) -> FavoriteListType: + return self.__type + + async def get_info(self): + """ + 获取收藏夹信息。 + + Returns: + dict: 调用 API 返回的结果 + """ + raise_for_statement(self.__media_id != None, "视频收藏夹需要 media_id") + + api = API["info"]["info"] + params = {"media_id": self.__media_id} + + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_content_video( + self, + page: int = 1, + keyword: Union[str, None] = None, + order: FavoriteListContentOrder = FavoriteListContentOrder.MTIME, + tid=0, + ) -> dict: + """ + 获取视频收藏夹内容。 + + Args: + page (int, optional) : 页码. Defaults to 1. + + keyword (str | None, optional) : 搜索关键词. Defaults to None. + + order (FavoriteListContentOrder, optional): 排序方式. Defaults to FavoriteListContentOrder.MTIME. + + tid (int, optional) : 分区 ID. Defaults to 0. + + Returns: + dict: 调用 API 返回的结果 + """ + raise_for_statement(self.__type == FavoriteListType.VIDEO, "此函数仅在收藏夹为视频收藏家时可用") + raise_for_statement(self.__media_id != None, "视频收藏夹需要 media_id") + + return await get_video_favorite_list_content( + self.__media_id, page, keyword, order, tid, self.credential + ) + + async def get_content(self, page: int = 1) -> dict: + """ + 获取收藏夹内容。 + + Args: + page (int, optional): 页码. Defaults to 1. + + Returns: + dict: 调用 API 返回的结果 + """ + if self.__type == FavoriteListType.ARTICLE: + return await get_article_favorite_list(page, self.credential) + elif self.__type == FavoriteListType.CHEESE: + return await get_course_favorite_list(page, self.credential) + elif self.__type == FavoriteListType.VIDEO: + raise_for_statement(self.__media_id != None, "视频收藏夹需要 media_id") + return await get_video_favorite_list_content( + self.__media_id, page, credential=self.credential + ) + else: + raise ArgsException("无法识别传入的类型") + + async def get_content_ids_info(self) -> dict: + """ + 获取收藏夹所有内容的 ID。 + + Returns: + dict: 调用 API 返回的结果 + """ + raise_for_statement(self.__media_id != None, "视频收藏夹需要 media_id") + + api = API["info"]["list_content_id_list"] + params = {"media_id": self.__media_id} + + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + +async def get_video_favorite_list( + uid: int, + video: Union[Video, None] = None, + credential: Union[Credential, None] = None, +) -> dict: + """ + 获取视频收藏夹列表。 + + Args: + uid (int) : 用户 UID。 + + video (Video | None, optional): 视频类。若提供该参数则结果会附带该收藏夹是否存在该视频。Defaults to None. + + credential (Credential | None, optional) : 凭据. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["list_list"] + params = {"up_mid": uid, "type": 2} + + if video is not None: + params["rid"] = video.get_aid() + + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_video_favorite_list_content( + media_id: int, + page: int = 1, + keyword: Union[str, None] = None, + order: FavoriteListContentOrder = FavoriteListContentOrder.MTIME, + tid: int = 0, + mode: SearchFavoriteListMode = SearchFavoriteListMode.ONLY, + credential: Union[Credential, None] = None, +) -> dict: + """ + 获取视频收藏夹列表内容,也可用于搜索收藏夹内容。 + + mode 参数见 SearchFavoriteListMode 枚举。 + + Args: + media_id (int) : 收藏夹 ID。 + + page (int, optional) : 页码. Defaults to 1. + + keyword (str, optional) : 搜索关键词. Defaults to None. + + order (FavoriteListContentOrder, optional): 排序方式. Defaults to FavoriteListContentOrder.MTIME. + + tid (int, optional) : 分区 ID. Defaults to 0. + + mode (SearchFavoriteListMode, optional) : 搜索模式,默认仅当前收藏夹. + + credential (Credential, optional) : Credential. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["list_content"] + params = { + "media_id": media_id, + "pn": page, + "ps": 20, + "order": order.value, + "tid": tid, + "type": mode.value, + } + + if keyword is not None: + params["keyword"] = keyword + + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_topic_favorite_list( + page: int = 1, credential: Union[None, Credential] = None +) -> dict: + """ + 获取自己的话题收藏夹内容。 + + Args: + page (int, optional) : 页码. Defaults to 1. + + credential (Credential | None, optional): Credential + + Returns: + dict: 调用 API 返回的结果 + """ + if credential is None: + credential = Credential() + + credential.raise_for_no_sessdata() + + api = API["info"]["list_topics"] + params = {"page_num": page, "page_size": 16} + + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_article_favorite_list( + page: int = 1, credential: Union[None, Credential] = None +) -> dict: + """ + 获取自己的专栏收藏夹内容。 + + Args: + page (int, optional) : 页码. Defaults to 1. + + credential (Credential | None, optional): Credential. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + if credential is None: + credential = Credential() + + credential.raise_for_no_sessdata() + + api = API["info"]["list_articles"] + params = {"pn": page, "ps": 16} + + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_course_favorite_list( + page: int = 1, credential: Union[None, Credential] = None +) -> dict: + """ + 获取自己的课程收藏夹内容。 + + Args: + page (int, optional) : 页码. Defaults to 1. + + credential (Credential | None, optional): Credential. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + if credential is None: + credential = Credential() + + credential.raise_for_no_sessdata() + + api = API["info"]["list_courses"] + self_info = await user.get_self_info(credential) + params = {"pn": page, "ps": 10, "mid": self_info["mid"]} + + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_note_favorite_list( + page: int = 1, credential: Union[None, Credential] = None +) -> dict: + """ + 获取自己的笔记收藏夹内容。 + + Args: + page (int, optional) : 页码. Defaults to 1. + + credential (Credential | None, optional): Credential. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + if credential is None: + credential = Credential() + + credential.raise_for_no_sessdata() + + api = API["info"]["list_notes"] + params = {"pn": page, "ps": 16} + + return await Api(**api, credential=credential).update_params(**params).result + + +async def create_video_favorite_list( + title: str, + introduction: str = "", + private: bool = False, + credential: Union[None, Credential] = None, +) -> dict: + """ + 新建视频收藏夹列表。 + + Args: + title (str) : 收藏夹名。 + + introduction (str, optional) : 收藏夹简介. Defaults to ''. + + private (bool, optional) : 是否为私有. Defaults to False. + + credential (Credential, optional): 凭据. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + if credential is None: + credential = Credential() + + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + + api = API["operate"]["new"] + data = { + "title": title, + "intro": introduction, + "privacy": 1 if private else 0, + "cover": "", + } + + return await Api(**api, credential=credential).update_data(**data).result + + +async def modify_video_favorite_list( + media_id: int, + title: str, + introduction: str = "", + private: bool = False, + credential: Union[None, Credential] = None, +) -> dict: + """ + 修改视频收藏夹信息。 + + Args: + media_id (int) : 收藏夹 ID. + + title (str) : 收藏夹名。 + + introduction (str, optional) : 收藏夹简介. Defaults to ''. + + private (bool, optional) : 是否为私有. Defaults to False. + + credential (Credential, optional): Credential. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + + if credential is None: + credential = Credential() + + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + + api = API["operate"]["modify"] + data = { + "title": title, + "intro": introduction, + "privacy": 1 if private else 0, + "cover": "", + "media_id": media_id, + } + + return await Api(**api, credential=credential).update_data(**data).result + + +async def delete_video_favorite_list( + media_ids: List[int], credential: Credential +) -> dict: + """ + 删除视频收藏夹,可批量删除。 + + Args: + media_ids (List[int]) : 收藏夹 ID 列表。 + + credential (Credential): Credential. + + Returns: + dict: 调用 API 返回的结果 + """ + + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + + data = {"media_ids": join(",", media_ids)} + api = API["operate"]["delete"] + + return await Api(**api, credential=credential).update_data(**data).result + + +async def copy_video_favorite_list_content( + media_id_from: int, media_id_to: int, aids: List[int], credential: Credential +) -> dict: + """ + 复制视频收藏夹内容 + + Args: + media_id_from (int) : 要复制的源收藏夹 ID。 + + media_id_to (int) : 目标收藏夹 ID。 + + aids (List[int]) : 被复制的视频 ID 列表。 + + credential (Credential): 凭据 + + Returns: + dict: 调用 API 返回的结果 + """ + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + api = API["operate"]["content_copy"] + + self_info = await user.get_self_info(credential=credential) + + data = { + "src_media_id": media_id_from, + "tar_media_id": media_id_to, + "mid": self_info["mid"], + "resources": ",".join(map(lambda x: f"{str(x)}:2", aids)), + } + + return await Api(**api, credential=credential).update_data(**data).result + + +async def move_video_favorite_list_content( + media_id_from: int, media_id_to: int, aids: List[int], credential: Credential +) -> dict: + """ + 移动视频收藏夹内容 + + Args: + media_id_from (int) : 要移动的源收藏夹 ID。 + + media_id_to (int) : 目标收藏夹 ID。 + + aids (List[int]) : 被移动的视频 ID 列表。 + + credential (Credential): 凭据 + + Returns: + dict: 调用 API 返回的结果 + """ + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + api = API["operate"]["content_move"] + + data = { + "src_media_id": media_id_from, + "tar_media_id": media_id_to, + "resources": ",".join(map(lambda x: f"{str(x)}:2", aids)), + } + + return await Api(**api, credential=credential).update_data(**data).result + + +async def delete_video_favorite_list_content( + media_id: int, aids: List[int], credential: Credential +) -> dict: + """ + 删除视频收藏夹内容 + + Args: + media_id (int) : 收藏夹 ID。 + + aids (List[int]) : 被删除的视频 ID 列表。 + + credential (Credential): 凭据 + + Returns: + dict: API 调用结果。 + """ + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + api = API["operate"]["content_rm"] + + data = { + "media_id": media_id, + "resources": ",".join(map(lambda x: f"{str(x)}:2", aids)), + } + + return await Api(**api, credential=credential).update_data(**data).result + + +async def clean_video_favorite_list_content( + media_id: int, credential: Credential +) -> dict: + """ + 清除视频收藏夹失效内容 + + Args: + media_id (int) : 收藏夹 ID + + credential (Credential): 凭据 + + Returns: + dict: API 调用结果。 + """ + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + api = API["operate"]["content_clean"] + + data = {"media_id": media_id} + + return await Api(**api, credential=credential).update_data(**data).result + + +async def get_favorite_collected( + uid: int, + pn: int = 1, + ps: int = 20, + credential: Union[Credential, None] = None, +) -> dict: + """ + 获取收藏合集列表 + + Args: + uid (int) : 用户 UID。 + + pn (int, optional) : 页码. Defaults to 1. + + ps (int, optional) : 每页数据大小. Defaults to 20. + + credential (Credential | None, optional) : Credential. Defaults to None. + """ + api = API["info"]["collected"] + params = {"up_mid": uid, "platform": "web", "pn": pn, "ps": ps} + return await Api(**api, credential=credential).update_params(**params).result diff --git a/bilibili_api/festival.py b/bilibili_api/festival.py new file mode 100644 index 0000000000000000000000000000000000000000..fd9e1c11ac384d161c4ef1e0bc5955409adae60e --- /dev/null +++ b/bilibili_api/festival.py @@ -0,0 +1,39 @@ +""" +bilibili_api.festival + +节日专门页相关 +""" + +from typing import Optional +from .utils.credential import Credential +from .utils.initial_state import get_initial_state +from .video import Video + +class Festival: + """ + 节日专门页 + + Attributes: + fes_id (str) : 节日专门页编号 + credential (Credential): 凭证类 + """ + def __init__(self, fes_id: str, credential: Optional[Credential] = None) -> None: + """ + Args: + fes_id (str): 节日专门页编号 + credential (Credential, optional): 凭据类. Defaults to None. + """ + self.fes_id = fes_id + self.credential = credential if credential else Credential() + + async def get_info(self) -> dict: + """ + 获取节日信息 + + Returns: + dict: 调用 API 返回的结果 + """ + return (await get_initial_state( + f"https://www.bilibili.com/festival/{self.fes_id}", + credential=self.credential + ))[0] diff --git a/bilibili_api/game.py b/bilibili_api/game.py new file mode 100644 index 0000000000000000000000000000000000000000..5dcb0f3621618adb26b77dad08906704ae91a3fb --- /dev/null +++ b/bilibili_api/game.py @@ -0,0 +1,243 @@ +""" +bilibili_api.game + +游戏相关 +""" + +import json +import re +from enum import Enum +from typing import Union +from .errors import ApiException + +from httpx import AsyncClient + +from .utils.credential import Credential +from .utils.network import HEADERS, Api, get_session +from .utils.utils import get_api + +API = get_api("game") + + +class Game: + """ + 游戏类 + + Attributes: + credential (Credential): 凭据类 + """ + + def __init__(self, game_id: int, credential: Union[None, Credential] = None): + """ + Args: + game_id (int) : 游戏 id + + credential (Credential): 凭据类. Defaults to None. + """ + self.__game_id = game_id + self.credential = credential if credential else Credential() + + def get_game_id(self) -> int: + return self.__game_id + + async def get_info(self) -> dict: + """ + 获取游戏简介 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["info"] + params = {"game_base_id": self.__game_id} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_up_info(self) -> dict: + """ + 获取游戏官方账号 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["UP"] + params = {"game_base_id": self.__game_id} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_detail(self) -> dict: + """ + 获取游戏详情 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["detail"] + params = {"game_base_id": self.__game_id} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_wiki(self) -> dict: + """ + 获取游戏教程(wiki) + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["wiki"] + params = {"game_base_id": self.__game_id} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_videos(self) -> dict: + """ + 获取游戏介绍视频 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["videos"] + params = {"game_base_id": self.__game_id} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + # async def get_score(self) -> dict: + # """ + # 获取游戏评分 + + # 该接口需要鉴权,暂时停用 + + # Returns: + # dict: 调用 API 返回的结果 + # """ + # api = API["info"]["score"] + # params = {"game_base_id": self.__game_id} + # return await request( + # "GET", api["url"], params=params, credential=self.credential + # ) + + # async def get_comments(self) -> dict: + # """ + # 获取游戏的评论 + + # Returns: + # dict: 调用 API 返回的结果 + # """ + # api = API["info"]["comment"] + # params = {"game_base_id": self.__game_id} + # return await request( + # "GET", api["url"], params=params, credential=self.credential + # ) + + +class GameRankType(Enum): + """ + 游戏排行榜类型枚举 + + - HOT: 热度榜 + - SUBSCRIBE: 预约榜 + - NEW: 新游榜 + - REPUTATION: 口碑榜 + - BILIBILI: B指榜 + - CLIENT: 端游榜 + """ + + HOT = 1 + SUBSCRIBE = 5 + NEW = 6 + REPUTATION = 2 + BILIBILI = 7 + CLIENT = 11 + + +async def get_game_rank( + rank_type: GameRankType, page_num: int = 1, page_size: int = 20 +) -> dict: + """ + 获取游戏排行榜 + + Args: + rank_type (GameRankType): 游戏排行榜类型 + page_num (int, optional): 页码. Defaults to 1. + page_size (int, optional): 每页游戏数量. Defaults to 20. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["rank"] + params = { + "ranking_type": rank_type.value, + "page_num": page_num, + "page_size": page_size, + } + return await Api(**api).update_params(**params).result + + +async def get_start_test_list(page_num: int = 1, page_size: int = 20) -> dict: + """ + 获取游戏公测时间线 + + Args: + page_num (int, optional): 页码. Defaults to 1. + page_size (int, optional): 每页游戏数量. Defaults to 20. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["start_test"] + params = {"x-fix-page-num": 1, "page_num": page_num, "page_size": page_size} + return await Api(**api).update_params(**params).result + + +def get_wiki_api_root(game_id: str) -> str: + """ + 获取游戏 WIKI 对应的 api 链接,以便传入第三方库进行其他解析操作。 + + Args: + game_id (str): 游戏编码 + + Returns: + str: 游戏 WIKI 对应的 api 链接 + """ + return f"https://wiki.biligame.com/{game_id}/api.php" + + +async def game_name2id(game_name: str) -> str: + """ + 将游戏名转换为游戏的编码 + + Args: + game_name (str): 游戏名 + + Returns: + str: 游戏编码 + """ + sess: AsyncClient = get_session() + try: + wiki_page_title = json.loads( + ( + await sess.get( + f"https://wiki.biligame.com/wiki/api.php?action=opensearch&format=json&formatversion=2&search={game_name}&namespace=0&limit=10" + ) + ).text + )[3][0].lstrip("https://wiki.biligame.com/wiki/") + except IndexError as e: + raise ApiException("未找到游戏") + wiki_page_content = ( + await sess.get( + f"https://wiki.biligame.com/wiki/api.php?action=query&prop=revisions&titles={wiki_page_title}&rvprop=content&format=json", + ) + ).text + wiki_page_template_re = re.compile(r"\{\{(.*?)\}\}") + match = re.search(wiki_page_template_re, wiki_page_content) + if match is None: + raise ApiException("获取游戏编码失败") + wiki_page_template_content = match.group(1) + wiki_page_template_content = wiki_page_template_content.encode("ascii").decode("unicode-escape") + for prop in wiki_page_template_content.split("|"): + if prop.startswith("WIKI域名="): + return prop.lstrip("WIKI域名=").rstrip() diff --git a/bilibili_api/homepage.py b/bilibili_api/homepage.py new file mode 100644 index 0000000000000000000000000000000000000000..d3a51d16ec17107c9192dd1484a3afe96d01e560 --- /dev/null +++ b/bilibili_api/homepage.py @@ -0,0 +1,72 @@ +""" +bilibili_api.homepage + +主页相关操作。 +""" + +from typing import Union + +from .utils.utils import get_api +from .utils.credential import Credential +from .utils.network import Api + +API = get_api("homepage") + + +async def get_top_photo() -> dict: + """ + 获取主页最上方的图像。 + 例如:b 站的风叶穿行,通过这个 API 获取的图片就是风叶穿行的图片。 + + Returns: + 调用 API 返回的结果。 + """ + api = API["info"]["top_photo"] + params = {"resource_id": 142} + return await Api(**api).update_params(**params).result + + +async def get_links(credential: Union[Credential, None] = None): + """ + 获取主页左面的链接。 + 可能和个人喜好有关。 + + Args: + credential (Credential | None): 凭据类 + + Returns: + 调用 API 返回的结果 + """ + api = API["info"]["links"] + params = {"pf": 0, "ids": 4694} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_popularize(credential: Union[Credential, None] = None): + """ + 获取推广的项目。 + (有视频有广告) + + Args: + credential(Credential | None): 凭据类 + + Returns: + 调用 API 返回的结果 + """ + api = API["info"]["popularize"] + params = {"pf": 0, "ids": 34} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_videos(credential: Union[Credential, None] = None): + """ + 获取首页推荐的视频。 + + Args: + credential (Credential | None): 凭据类 + + Returns: + 调用 API 返回的结果 + """ + api = API["info"]["videos"] + return await Api(**api, credential=credential).result diff --git a/bilibili_api/hot.py b/bilibili_api/hot.py new file mode 100644 index 0000000000000000000000000000000000000000..1f922f8085f8663a9b7fd296797bd5e83778357e --- /dev/null +++ b/bilibili_api/hot.py @@ -0,0 +1,83 @@ +""" +bilibili_api.hot + +热门相关 API +""" + +from .utils.utils import get_api +from .utils.network import Api + +API_rank = get_api("rank") +API = get_api("hot") + + +async def get_hot_videos(pn: int = 1, ps: int = 20) -> dict: + """ + 获取热门视频 + + Args: + pn (int): 第几页. Default to 1. + + ps (int): 每页视频数. Default to 20. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API_rank["info"]["hot"] + params = {"ps": ps, "pn": pn} + return await Api(**api).update_params(**params).result + + +async def get_weekly_hot_videos_list() -> dict: + """ + 获取每周必看列表(仅概述) + + Returns: + 调用 API 返回的结果 + """ + api = API_rank["info"]["weekly_series"] + return await Api(**api).result + + +async def get_weekly_hot_videos(week: int = 1) -> dict: + """ + 获取一周的每周必看视频列表 + + Args: + week(int): 第几周. Default to 1. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API_rank["info"]["weekly_details"] + params = {"number": week} + return await Api(**api).update_params(**params).result + + +async def get_history_popular_videos() -> dict: + """ + 获取入站必刷 85 个视频 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API_rank["info"]["history_popular"] + params = {"page_size": 85, "page": 1} + return await Api(**api).update_params(**params).result + + +async def get_hot_buzzwords(page_num: int = 1, page_size: int = 20) -> dict: + """ + 获取热词图鉴信息 + + Args: + page_num (int): 页码. Defaults to 1. + + page_size (int): 每一页的数据大小. Defaults to 20. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["buzzwords"] + params = {"pn": page_num, "ps": page_size, "type_id": 4} + return await Api(**api).update_params(**params).result diff --git a/bilibili_api/interactive_video.py b/bilibili_api/interactive_video.py new file mode 100644 index 0000000000000000000000000000000000000000..90ecba489062a185bbedf0576c3d698d1b2e631d --- /dev/null +++ b/bilibili_api/interactive_video.py @@ -0,0 +1,1370 @@ +""" +bilibili_api.interactive_video + +互动视频相关操作 +""" + +# pylint: skip-file + +import os +import copy +import enum +import json +import time +import shutil +import zipfile +from urllib import parse +from random import randint as rand +from asyncio import CancelledError, create_task +from typing import List, Tuple, Union, Callable, Coroutine + +import httpx + +from . import settings +from .video import Video +from .utils.utils import get_api +from .utils.AsyncEvent import AsyncEvent +from .utils.credential import Credential +from .utils.network import Api + +API = get_api("interactive_video") + + +class InteractiveButtonAlign(enum.Enum): + """ + 按钮的文字在按钮中的位置 + + + ``` text + ----- + |xxx|----o (TEXT_LEFT) + ----- + + ----- + o----|xxx| (TEXT_RIGHT) + ----- + + ---------- + |XXXXXXXX| (DEFAULT) + ---------- + ``` + + - DEFAULT + - TEXT_UP + - TEXT_RIGHT + - TEXT_DOWN + - TEXT_LEFT + """ + + DEFAULT = 0 + TEXT_UP = 1 + TEXT_RIGHT = 2 + TEXT_DOWN = 3 + TEXT_LEFT = 4 + + +class InteractiveNodeJumpingType(enum.Enum): + """ + 对下一节点的跳转的方式 + + - ASK : 选择 + - DEFAULT: 跳转到默认节点 + - READY : 选择(只有一个选择) + """ + + READY = 1 + DEFAULT = 0 + ASK = 2 + + +class InteractiveVariable: + """ + 互动节点的变量 + """ + + def __init__( + self, + name: str, + var_id: str, + var_value: int, + show: bool = False, + random: bool = False, + ): + """ + Args: + name (str) : 变量名 + + var_id (str) : 变量 id + + var_value (int) : 变量的值 + + show (bool) : 是否显示 + + random (bool) : 是否为随机值(1-100) + """ + self.__var_id = var_id + self.__var_value = var_value + self.__name = name + self.__is_show = show + self.__random = random + + def get_id(self) -> str: + return self.__var_id + + def refresh_value(self) -> None: + """ + 刷新变量数值 + """ + if self.is_random(): + self.__var_value = int(rand(0, 100)) + + def get_value(self) -> int: + return self.__var_value + + def is_show(self) -> bool: + return self.__is_show + + def is_random(self) -> bool: + return self.__random + + def get_name(self) -> str: + return self.__name + + def __str__(self): + return f"{self.__name} {self.__var_value}" + + +class InteractiveButton: + """ + 互动视频节点按钮类 + """ + + def __init__( + self, + text: str, + x: int, + y: int, + align: Union[InteractiveButtonAlign, int] = InteractiveButtonAlign.DEFAULT, + ): + """ + Args: + text (str) : 文字 + + x (int) : x 轴 + + y (int) : y 轴 + + align (InteractiveButtonAlign | int): 按钮的文字在按钮中的位置 + """ + self.__text = text + self.__pos = (x, y) + if isinstance(align, InteractiveButtonAlign): + align = align.value + self.__align = align + + def get_text(self) -> str: + return self.__text + + def get_align(self) -> int: + return self.__align # type: ignore + + def get_pos(self) -> Tuple[int, int]: + return self.__pos + + def __str__(self): + return f"{self.__text} {self.__pos}" + + +class InteractiveJumpingCondition: + """ + 节点跳转的公式,只有公式成立才会跳转 + """ + + def __init__(self, var: List[InteractiveVariable] = [], condition: str = "True"): + """ + Args: + var (List[InteractiveVariable]): 所有变量 + + condition (str) : 公式 + """ + self.__vars = var + self.__command = condition + + def get_result(self) -> bool: + """ + 计算公式获得结果 + + Returns: + bool: 是否成立 + """ + if self.__command == "": + return True + command = copy.copy(self.__command) + for var in self.__vars: + var_name = var.get_id() + var_value = var.get_value() + command = command.replace(var_name, str(var_value)) + command = command.replace("&&", " and ") + command = command.replace("||", " or ") + command = command.replace("!", " not ") + command = command.replace("===", "==") + command = command.replace("!==", "!=") + command = command.replace("true", "True") + command = command.replace("false", "False") + return eval(command) + + def __str__(self): + return f"{self.__command}" + + +class InteractiveJumpingCommand: + """ + 节点跳转对变量的操作 + """ + + def __init__(self, var: List[InteractiveVariable] = [], command: str = ""): + """ + Args: + var (List[InteractiveVariable]): 所有变量 + + command (str) : 公式 + """ + self.__vars = var + self.__command = command + + def run_command(self) -> List["InteractiveVariable"]: + """ + 执行操作 + + Returns: + List[InteractiveVariable] + """ + if self.__command == "": + return self.__vars + for code in self.__command.split(";"): + var_name_ = code.split("=")[0] + var_new_value = code.split("=")[1] + for var in self.__vars: + var_name = var.get_id() + var_value = var.get_value() + var_new_value = var_new_value.replace(var_name, str(var_value)) + var_new_value_calc = eval(var_new_value) + for var in self.__vars: + if var.get_id() == var_name_: + var._InteractiveVariable__var_value = var_new_value_calc # type: ignore + return self.__vars + + +class InteractiveNode: + """ + 互动视频节点类 + """ + + def __init__( + self, + video: "InteractiveVideo", + node_id: int, + cid: int, + vars: List[InteractiveVariable], + button: Union[InteractiveButton, None] = None, + condition: InteractiveJumpingCondition = InteractiveJumpingCondition(), + native_command: InteractiveJumpingCommand = InteractiveJumpingCommand(), + is_default: bool = False, + ): + """ + Args: + video (InteractiveVideo) : 视频类 + + node_id (int) : 节点 id + + cid (int) : CID + + vars (List[InteractiveVariable]) : 变量 + + button (InteractiveButton) : 对应的按钮 + + condition (InteractiveJumpingCondition): 跳转公式 + + native_command (InteractiveJumpingCommand) : 跳转时变量操作 + + is_default (bool) : 是不是默认的跳转的节点 + """ + self.__parent = video + self.__id = node_id + self.__cid = cid + self.__button = button + self.__jumping_command = condition + self.__is_default = is_default + self.__vars = vars + self.__command = native_command + self.__vars = self.__command.run_command() + + async def get_vars(self) -> List[InteractiveVariable]: + """ + 获取节点的所有变量 + + Returns: + List[InteractiveVariable]: 节点的所有变量 + """ + return self.__vars + + async def get_children(self) -> List["InteractiveNode"]: + """ + 获取节点的所有子节点 + + Returns: + List[InteractiveNode]: 所有子节点 + """ + edge_info = await self.__parent.get_edge_info(self.__id) + nodes = [] + if edge_info["edges"].get("questions") == None: + return [] + for node in edge_info["edges"]["questions"][0]["choices"]: + node_id = node["id"] + node_cid = node["cid"] + if "text_align" in node.keys(): + text_align = node["text_align"] + else: + text_align = 0 + if "option" in node.keys(): + node_button = InteractiveButton( + node["option"], node.get("x"), node.get("y"), text_align + ) + else: + node_button = None + node_condition = InteractiveJumpingCondition( + await self.get_vars(), node["condition"] + ) + node_command = InteractiveJumpingCommand( + await self.get_vars(), node["native_action"] + ) + if "is_default" in node.keys(): + node_is_default = node["is_default"] + else: + node_is_default = False + node_vars = copy.deepcopy(await self.get_vars()) + nodes.append( + InteractiveNode( + self.__parent, + node_id, + node_cid, + node_vars, + node_button, + node_condition, + node_command, + node_is_default, + ) + ) + return nodes + + def is_default(self) -> bool: + return self.__is_default + + async def get_jumping_type(self) -> int: + """ + 获取子节点跳转方式 (参考 InteractiveNodeJumpingType) + """ + edge_info = await self.__parent.get_edge_info(self.__id) + return edge_info["edges"]["questions"][0]["type"] + + def get_node_id(self) -> int: + return self.__id + + def get_cid(self) -> int: + return self.__cid + + def get_self_button(self) -> "InteractiveButton": + if self.__button == None: + return InteractiveButton("", -1, -1) + return self.__button + + def get_jumping_condition(self) -> "InteractiveJumpingCondition": + return self.__jumping_command + + def get_video(self) -> "InteractiveVideo": + return self.__parent + + async def get_info(self) -> dict: + """ + 获取节点的简介 + + Returns: + dict: 调用 API 返回的结果 + """ + return await self.__parent.get_edge_info(self.__id) + + def __str__(self): + return f"{self.get_node_id()}" + + +class InteractiveGraph: + """ + 情节树类 + """ + + def __init__(self, video: "InteractiveVideo", skin: dict, root_cid: int): + """ + Args: + video (InteractiveVideo): 互动视频类 + skin (dict) : 样式 + root_cid (int) : 根节点 CID + """ + self.__parent = video + self.__skin = skin + self.__node = InteractiveNode(self.__parent, 1, root_cid, []) + + def get_video(self) -> "InteractiveVideo": + return self.__parent + + def get_skin(self) -> dict: + return self.__skin + + async def get_root_node(self) -> "InteractiveNode": + """ + 获取根节点 + + Returns: + InteractiveNode: 根节点 + """ + edge_info = await self.__parent.get_edge_info(None) + if "hidden_vars" in edge_info.keys(): + node_vars = edge_info["hidden_vars"] + else: + node_vars = [] + var_list = [] + for var in node_vars: + var_value = var["value"] + var_name = var["name"] + var_show = var["is_show"] + var_id = var["id_v2"] + if var["type"] == 2: + random = True + else: + random = False + var_list.append( + InteractiveVariable(var_name, var_id, var_value, var_show, random) + ) + self.__node._InteractiveNode__command = InteractiveJumpingCommand( # type: ignore + var_list + ) + self.__node._InteractiveNode__vars = var_list # type: ignore + return self.__node + + async def get_children(self) -> List["InteractiveNode"]: + """ + 获取子节点 + + Returns: + List[InteractiveNode]: 子节点 + """ + return await self.__node.get_children() + + +class InteractiveVideo(Video): + """ + 互动视频类 + """ + + def __init__(self, bvid=None, aid=None, credential=None): + super().__init__(bvid, aid, credential) + + async def up_get_ivideo_pages(self) -> dict: + """ + 获取交互视频的分 P 信息。up 主需要拥有视频所有权。 + + Returns: + dict: 调用 API 返回的结果 + """ + credential = self.credential if self.credential else Credential() + api = API["info"]["videolist"] + params = {"bvid": self.get_bvid()} + return await Api(**api, credential=credential).update_params(**params).result + + async def up_submit_story_tree(self, story_tree: str) -> dict: + """ + 上传交互视频的情节树。up 主需要拥有视频所有权。 + + Args: + story_tree (str): 情节树的描述,参考 bilibili_storytree.StoryGraph, 需要 Serialize 这个结构 + + Returns: + dict: 调用 API 返回的结果 + """ + credential = self.credential if self.credential else Credential() + api = API["operate"]["savestory"] + form_data = {"preview": "0", "data": story_tree, "csrf": credential.bili_jct} + headers = { + "User-Agent": "Mozilla/5.0", + "Referer": "https://member.bilibili.com", + "Content-Encoding": "gzip, deflate, br", + "Content-Type": "application/x-www-form-urlencoded", + "Accept": "application/json, text/plain, */*", + } + data = parse.urlencode(form_data) + return ( + await Api(**api, credential=credential, no_csrf=True) + .update_data(**data) + .update_headers(**headers) + .result + ) + + async def get_graph_version(self) -> int: + """ + 获取剧情图版本号,仅供 `get_edge_info()` 使用。 + + Returns: + int: 剧情图版本号 + """ + # 取得初始顶点 cid + bvid = self.get_bvid() + credential = self.credential if self.credential else Credential() + v = Video(bvid=bvid, credential=credential) + page_list = await v.get_pages() + cid = page_list[0]["cid"] + + # 获取剧情图版本号 + url = "https://api.bilibili.com/x/player/v2" + params = {"bvid": bvid, "cid": cid} + + resp = ( + await Api(method="GET", url=url, credential=credential) + .update_params(**params) + .result + ) + return resp["interaction"]["graph_version"] + + async def get_edge_info(self, edge_id: Union[int, None] = None): + """ + 获取剧情图节点信息 + + Args: + edge_id (int, optional) : 节点 ID,为 None 时获取根节点信息. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + bvid = self.get_bvid() + credential = self.credential if self.credential is not None else Credential() + + api = API["info"]["edge_info"] + params = {"bvid": bvid, "graph_version": (await self.get_graph_version())} + + if edge_id is not None: + params["edge_id"] = edge_id + + return await Api(**api, credential=credential).update_params(**params).result + + async def mark_score(self, score: int = 5): + """ + 为互动视频打分 + + Args: + score (int): 互动视频分数. Defaults to 5. + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + api = API["operate"]["mark_score"] + data = {"mark": score, "bvid": self.get_bvid()} + return await Api(**api, credential=self.credential).update_data(**data).result + + async def get_cid(self) -> int: + """ + 获取稿件 cid + """ + return await super().get_cid(0) + + async def get_graph(self): + """ + 获取稿件情节树 + + Returns: + InteractiveGraph: 情节树 + """ + edge_info = await self.get_edge_info(1) + cid = await self.get_cid() + return InteractiveGraph(self, edge_info["edges"]["skin"], cid) + + +class InteractiveVideoDownloaderEvents(enum.Enum): + """ + 互动视频下载器事件枚举 + + | event | meaning | IVI mode | NODE_VIDEOS mode | DOT_GRAPH mode | NO_PACKAGING mode | Is Built-In downloader event | + | ----- | ------- | -------- | ---------------- | -------------- | ----------------- | ------------------------- | + | START | 开始下载 | [x] | [x] | [x] | [x] | [ ] | + | GET | 获取到节点信息 | [x] | [x] | [x] | [x] | [ ] | + | PREPARE_DOWNLOAD | 准备下载单个节点 | [x] | [x] | [ ] | [x] | [ ] | + | DOWNLOAD_START | 开始下载单个文件 | Unknown | Unknown | [ ] | Unknown | [x] | + | DOWNLOAD_PART | 文件分块部分完成 | Unknown | Unknown | [ ] | Unknown | [x] | + | DOWNLOAD_SUCCESS | 完成下载 | Unknown | Unknown | [ ] | Unknown | [x] | + | PACKAGING | 正在打包 | [x] | [ ] | [ ] | [ ] | [ ] | + | SUCCESS | 下载成功 | [x] | [x] | [x] | [x] | [ ] | + | ABORTED | 用户暂停 | [x] | [x] | [x] | [x] | [ ] | + | FAILED | 下载失败 | [x] | [x] | [x] | [x] | [ ] | + """ + + START = "START" + GET = "GET" + DOWNLOAD_START = "DOWNLOAD_START" + DOWNLOAD_PART = "DOWNLOAD_PART" + DOWNLOAD_SUCCESS = "DOWNLOAD_SUCCESS" + PACKAGING = "PACKAGING" + SUCCESS = "SUCCESS" + ABORTED = "ABORTED" + FAILED = "FAILED" + + +class InteractiveVideoDownloaderMode(enum.Enum): + """ + 互动视频下载模式 + + - IVI: 下载可播放的 ivi 文件 + - NODE_VIDEOS: 下载所有节点的所有视频并存放在某个文件夹,每一个节点的视频命名为 `{节点 id} {节点标题 (自动去除敏感字符)}.mp4` + - DOT_GRAPH: 下载 dot 格式的情节树图表 + - NO_PACKAGING: 前面按照 ivi 文件下载步骤进行下载,但是最终不会打包成为 ivi 文件,所有文件将存放于一个文件夹中。互动视频数据将存放在一个文件夹中,里面的文件命名/含义与拆包后的 ivi 文件完全相同。 + """ + + IVI = "ivi" + NODE_VIDEOS = "videos" + DOT_GRAPH = "dot" + NO_PACKAGING = "no_pack" + + +class InteractiveVideoDownloader(AsyncEvent): + """ + 互动视频下载类 + """ + + def __init__( + self, + video: InteractiveVideo, + out: str = "", + self_download_func: Union[Coroutine, None] = None, + downloader_mode: InteractiveVideoDownloaderMode = InteractiveVideoDownloaderMode.IVI, + ): + """ + Args: + video (InteractiveVideo) : 互动视频类 + + out (str) : 输出文件地址 (如果模式为 NODE_VIDEOS/NO_PACKAGING 则此参数表示所有节点视频的存放目录) + + self_download_func (Coroutine | None) : 自定义下载函数(需 async 函数) + + downloader_mode (InteractiveVideoDownloaderMode): 下载模式 + + `self_download_func` 函数应接受两个参数(第一个是下载 URL,第二个是输出地址(精确至文件名)) + """ + super().__init__() + self.__video = video + if self_download_func == None: + self.__download_func = self.__download + else: + self.__download_func = self_download_func + self.__task = None + self.__out = out + self.__mode = downloader_mode + + async def __download(self, url: str, out: str) -> None: + resp = httpx.get( + url, + headers={ + "User-Agent": "Mozilla/5.0", + "Referer": "https://www.bilibili.com", + }, + proxies={"all://": settings.proxy}, + stream=True, + ) + resp.raise_for_status() + + if os.path.exists(out): + os.remove(out) + + parent = os.path.dirname(out) + if not os.path.exists(parent): + os.mkdir(parent) + + self.dispatch("DOWNLOAD_START", {"url": url, "out": out}) + + all_length = int(resp.headers["Content-Length"]) + parts = all_length // 1024 + (1 if all_length % 1024 != 0 else 0) + cnt = 0 + start_time = time.perf_counter() + + with open(out, "wb") as f: + for chunk in resp.iter_bytes(1024): + cnt += 1 + self.dispatch( + "DOWNLOAD_PART", + { + "done": cnt, + "total": parts, + "time": int(time.perf_counter() - start_time), + }, + ) + f.write(chunk) + + self.dispatch("DOWNLOAD_SUCCESS") + + async def __main(self) -> None: + # 初始化 + self.dispatch("START") + if self.__out == "": + self.__out = self.__video.get_bvid() + ".ivi" + if self.__out.endswith(".ivi"): + self.__out = self.__out.rstrip(".ivi") + if os.path.exists(self.__out + ".ivi"): + os.remove(self.__out + ".ivi") + tmp_dir_name = self.__out + ".tmp" + if not os.path.exists(tmp_dir_name): + os.mkdir(tmp_dir_name) + + def createEdge(edge_id: int): + """ + 创建节点信息到 edges_info + """ + edges_info[edge_id] = { + "title": None, + "cid": None, + "button": None, + "condition": None, + "jump_type": None, + "is_default": None, + "command": None, + "sub": [], + } + + def var2dict(var: InteractiveVariable): + return { + "name": var.get_name(), + "id": var.get_id(), + "value": var.get_value(), + "show": var.is_show(), + "random": var.is_random(), + } + + # 存储顶点信息 + edges_info = {} + + # 使用队列来遍历剧情图,初始为 None 是为了从初始顶点开始 + queue: List[InteractiveNode] = [ + await (await self.__video.get_graph()).get_root_node() + ] + + # 设置初始顶点 + n = await (await self.__video.get_graph()).get_root_node() + if n.get_node_id() not in edges_info: + createEdge(n.get_node_id()) + edges_info[n.get_node_id()]["cid"] = n.get_cid() + edges_info[n.get_node_id()]["button"] = { + "text": n.get_self_button().get_text(), + "align": n.get_self_button().get_align(), + "pos": (n.get_self_button().get_pos()), + } + edges_info[n.get_node_id()]["vars"] = [ + var2dict(var) for var in (await n.get_vars()) + ] + edges_info[n.get_node_id()]["condition"] = (n.get_jumping_condition()._InteractiveJumpingCondition__command,) # type: ignore + edges_info[n.get_node_id()]["jump_type"] = 0 + edges_info[n.get_node_id()]["is_default"] = True + edges_info[n.get_node_id()]["command"] = n._InteractiveNode__command._InteractiveJumpingCommand__command # type: ignore + + while queue: + # 出队 + now_node = queue.pop() + + if ( + now_node.get_node_id() in edges_info + and edges_info[now_node.get_node_id()]["title"] is not None + and edges_info[now_node.get_node_id()]["cid"] is not None + ): + # 该情况为已获取到所有信息,说明是跳转到之前已处理的顶点,不作处理 + continue + + # 获取顶点信息,最大重试 3 次 + retry = 3 + while True: + try: + node = await now_node.get_info() + subs = await now_node.get_children() + self.dispatch( + "GET", + {"title": node["title"], "node_id": now_node.get_node_id()}, + ) + break + except Exception as e: + retry -= 1 + if retry < 0: + raise e + + # 检查节顶点是否在 edges_info 中,本次步骤得到 title 信息 + if node["edge_id"] not in edges_info: + # 不在,新建 + createEdge(node["edge_id"]) + + # 设置 title + edges_info[node["edge_id"]]["title"] = node["title"] + + # 无可达顶点,即不能再往下走了,类似树的叶子节点 + if "questions" not in node["edges"]: + continue + + # 遍历所有可达顶点 + for n in subs: + # 该步骤获取顶点的 cid(视频分 P 的 ID) + if n.get_node_id() not in edges_info: + createEdge(n.get_node_id()) + edges_info[n.get_node_id()]["cid"] = n.get_cid() + edges_info[n.get_node_id()]["button"] = { + "text": n.get_self_button().get_text(), + "align": n.get_self_button().get_align(), + "pos": n.get_self_button().get_pos(), + } + + def var2dict(var: InteractiveVariable): + return { + "name": var.get_name(), + "id": var.get_id(), + "value": var.get_value(), + "show": var.is_show(), + "random": var.is_random(), + } + + edges_info[n.get_node_id()]["condition"] = n.get_jumping_condition()._InteractiveJumpingCondition__command # type: ignore + edges_info[n.get_node_id()][ + "jump_type" + ] = await now_node.get_jumping_type() + edges_info[n.get_node_id()]["is_default"] = n.is_default() + edges_info[n.get_node_id()]["command"] = n._InteractiveNode__command._InteractiveJumpingCommand__command # type: ignore + edges_info[now_node.get_node_id()]["sub"] = [ + n.get_node_id() for n in subs + ] + # 所有可达顶点 ID 入队 + queue.insert(0, n) + + json.dump( + edges_info, + open(tmp_dir_name + "/ivideo.json", "w+", encoding="utf-8"), + indent=2, + ) + json.dump( + { + "bvid": self.__video.get_bvid(), + "title": (await self.__video.get_info())["title"], + }, + open(tmp_dir_name + "/bilivideo.json", "w+", encoding="utf-8"), + indent=2, + ) + + cid_set = set() + for key, item in edges_info.items(): + cid = item["cid"] + if not cid in cid_set: + self.dispatch("PREPARE_DOWNLOAD", {"cid": item["cid"]}) + cid_set.add(cid) + url = await self.__video.get_download_url(cid=cid, html5=True) + await self.__download_func( + url["durl"][0]["url"], + tmp_dir_name + "/" + str(cid) + ".mp4", + ) # type: ignore + + root_cid = await self.__video.get_cid() + if not root_cid in cid_set: + self.dispatch("PREPARE_DOWNLOAD", {"cid": root_cid}) + cid = await self.__video.get_cid() + url = await self.__video.get_download_url(cid=cid, html5=True) + title = (await self.__video.get_info())["title"] + await self.__download_func( + url["durl"][0]["url"], tmp_dir_name + "/" + str(cid) + ".mp4" + ) # type: ignore + + self.dispatch("PACKAGING") + zip = zipfile.ZipFile( + open(self.__out + ".ivi", "wb+"), mode="w", compression=zipfile.ZIP_DEFLATED + ) # outFullName为压缩文件的完整路径 + for path, dirnames, filenames in os.walk(tmp_dir_name): + # 去掉目标跟路径,只对目标文件夹下边的文件及文件夹进行压缩 + fpath = path.replace(tmp_dir_name, "") + + for filename in filenames: + zip.write(os.path.join(path, filename), os.path.join(fpath, filename)) + zip.close() + shutil.rmtree(tmp_dir_name) + self.dispatch("SUCCESS") + + async def __node_videos_main(self) -> None: + # 初始化 + self.dispatch("START") + tmp_dir_name = self.__out + if not os.path.exists(tmp_dir_name): + os.mkdir(tmp_dir_name) + + def createEdge(edge_id: int): + """ + 创建节点信息到 edges_info + """ + edges_info[edge_id] = { + "title": None, + "cid": None, + "button": None, + "condition": None, + "jump_type": None, + "is_default": None, + "command": None, + "sub": [], + } + + def var2dict(var: InteractiveVariable): + return { + "name": var.get_name(), + "id": var.get_id(), + "value": var.get_value(), + "show": var.is_show(), + "random": var.is_random(), + } + + # 存储顶点信息 + edges_info = {} + + # 使用队列来遍历剧情图,初始为 None 是为了从初始顶点开始 + queue: List[InteractiveNode] = [ + await (await self.__video.get_graph()).get_root_node() + ] + + # 设置初始顶点 + n = await (await self.__video.get_graph()).get_root_node() + if n.get_node_id() not in edges_info: + createEdge(n.get_node_id()) + edges_info[n.get_node_id()]["cid"] = n.get_cid() + edges_info[n.get_node_id()]["button"] = { + "text": n.get_self_button().get_text(), + "align": n.get_self_button().get_align(), + "pos": (n.get_self_button().get_pos()), + } + edges_info[n.get_node_id()]["vars"] = [ + var2dict(var) for var in (await n.get_vars()) + ] + edges_info[n.get_node_id()]["condition"] = (n.get_jumping_condition()._InteractiveJumpingCondition__command,) # type: ignore + edges_info[n.get_node_id()]["jump_type"] = 0 + edges_info[n.get_node_id()]["is_default"] = True + edges_info[n.get_node_id()]["command"] = n._InteractiveNode__command._InteractiveJumpingCommand__command # type: ignore + + while queue: + # 出队 + now_node = queue.pop() + + if ( + now_node.get_node_id() in edges_info + and edges_info[now_node.get_node_id()]["title"] is not None + and edges_info[now_node.get_node_id()]["cid"] is not None + ): + # 该情况为已获取到所有信息,说明是跳转到之前已处理的顶点,不作处理 + continue + + # 获取顶点信息,最大重试 3 次 + retry = 3 + while True: + try: + node = await now_node.get_info() + subs = await now_node.get_children() + self.dispatch( + "GET", + {"title": node["title"], "node_id": now_node.get_node_id()}, + ) + break + except Exception as e: + retry -= 1 + if retry < 0: + raise e + + # 检查节顶点是否在 edges_info 中,本次步骤得到 title 信息 + if node["edge_id"] not in edges_info: + # 不在,新建 + createEdge(node["edge_id"]) + + # 设置 title + edges_info[node["edge_id"]]["title"] = node["title"] + + # 无可达顶点,即不能再往下走了,类似树的叶子节点 + if "questions" not in node["edges"]: + continue + + # 遍历所有可达顶点 + for n in subs: + # 该步骤获取顶点的 cid(视频分 P 的 ID) + if n.get_node_id() not in edges_info: + createEdge(n.get_node_id()) + edges_info[n.get_node_id()]["cid"] = n.get_cid() + edges_info[n.get_node_id()]["button"] = { + "text": n.get_self_button().get_text(), + "align": n.get_self_button().get_align(), + "pos": n.get_self_button().get_pos(), + } + + def var2dict(var: InteractiveVariable): + return { + "name": var.get_name(), + "id": var.get_id(), + "value": var.get_value(), + "show": var.is_show(), + "random": var.is_random(), + } + + edges_info[n.get_node_id()]["condition"] = n.get_jumping_condition()._InteractiveJumpingCondition__command # type: ignore + edges_info[n.get_node_id()][ + "jump_type" + ] = await now_node.get_jumping_type() + edges_info[n.get_node_id()]["is_default"] = n.is_default() + edges_info[n.get_node_id()]["command"] = n._InteractiveNode__command._InteractiveJumpingCommand__command # type: ignore + edges_info[now_node.get_node_id()]["sub"] = [ + n.get_node_id() for n in subs + ] + # 所有可达顶点 ID 入队 + queue.insert(0, n) + + cid_set = set() + for key, item in edges_info.items(): + cid = item["cid"] + if not cid in cid_set: + self.dispatch("PREPARE_DOWNLOAD", {"cid": item["cid"]}) + cid_set.add(cid) + url = await self.__video.get_download_url(cid=cid, html5=True) + await self.__download_func( + url["durl"][0]["url"], + tmp_dir_name + "/" + str(key) + " " + item["title"] + ".mp4", + ) # type: ignore + + root_cid = await self.__video.get_cid() + if not root_cid in cid_set: + self.dispatch("PREPARE_DOWNLOAD", {"cid": root_cid}) + cid = await self.__video.get_cid() + url = await self.__video.get_download_url(cid=cid, html5=True) + title = (await self.__video.get_info())["title"] + await self.__download_func( + url["durl"][0]["url"], tmp_dir_name + "/1 " + title + ".mp4" + ) # type: ignore + self.dispatch("SUCCESS") + + async def __dot_graph_main(self) -> None: + self.dispatch("START") + if not self.__out.endswith(".dot"): + self.__out += ".dot" + + class node_info: + node_id: int + subs: List[int] + cid: int + title: str + + def __eq__(self, info: "node_info"): + self.subs.sort() + info.subs.sort() + return ( + (info.subs == self.subs) + and (info.title == self.title) + and (info.cid == self.cid) + ) + + def __lt__(self, info: "node_info"): + return self.cid < info.cid + + def __gt__(self, info: "node_info"): + return self.cid > info.cid + + fetched_nodes_info: List[node_info] = [] + node_info_dict = {} + scripts = [] + graph = await self.__video.get_graph() + queue: List[InteractiveNode] = [await graph.get_root_node()] + while queue: + queue_backup = copy.copy(queue) + queue = [] + for cur_node in queue_backup: + cur_node_info = await cur_node.get_info() + cur_node_children = await cur_node.get_children() + self.dispatch( + "GET", + { + "title": cur_node_info["title"], + "node_id": cur_node.get_node_id(), + }, + ) + cur_node_info_class = node_info() + cur_node_info_class.node_id = cur_node.get_node_id() + cur_node_info_class.cid = cur_node.get_cid() + cur_node_info_class.subs = [n.get_node_id() for n in cur_node_children] + cur_node_info_class.title = cur_node_info["title"] + back_to_pre = False + back_to_node_title = -1 + for fetched_info in fetched_nodes_info: + if fetched_info == cur_node_info_class: + back_to_pre = True + back_to_node_title = fetched_info.title + if not back_to_pre: + node_info_dict[cur_node.get_node_id()] = cur_node_info_class + for cur_node_child in cur_node_children: + script_label = "" + if cur_node_child.get_jumping_condition()._InteractiveJumpingCondition__command != "": # type: ignore + script_label = script_label + "Condition: [" + cur_node_child.get_jumping_condition()._InteractiveJumpingCondition__command + "]" # type: ignore + if cur_node_child._InteractiveNode__command._InteractiveJumpingCommand__command != "": # type: ignore + script_label = script_label + "\nNative Command: [" + cur_node_child._InteractiveNode__command._InteractiveJumpingCommand__command + "]" # type: ignore + elif cur_node_child._InteractiveNode__command._InteractiveJumpingCommand__command != "": # type: ignore + script_label = script_label + "\nNative Command: [" + cur_node_child._InteractiveNode__command._InteractiveJumpingCommand__command + "]" # type: ignore + scripts.append( + { + "from": cur_node.get_node_id(), + "to": cur_node_child.get_node_id(), + "label": script_label, + } + ) + queue.append(cur_node_child) + fetched_nodes_info.append(cur_node_info_class) + else: + node_info_dict[cur_node.get_node_id()] = f"跳转至 {back_to_node_title}" + graph_content = "digraph {\nfontname=FangSong\nnode [fontname=FangSong]\n" + for script in scripts: + graph_content += f'\t{script["from"]} -> {script["to"]}' + if script["label"] != "": + graph_content += f' [label="{script["label"]}"]\n' + else: + graph_content += "\n" + for node_info_key, node_info_item in node_info_dict.items(): + if isinstance(node_info_item, node_info): + graph_content += f'\t{node_info_key} [label="{node_info_item.title}"]\n' + else: + graph_content += f'\t{node_info_key} [label="{node_info_item}"]\n' + vars_string = "Variables: " + for var in await (await graph.get_root_node()).get_vars(): + var_attribute = "" + if var.is_random(): + var_attribute = "Random" + else: + if var.is_show(): + var_attribute = "Normal" + else: + var_attribute = "Hide" + vars_string += f"[{var.get_id()} -> {var.get_name()} = {var.get_value()}, {var_attribute}]\n" + graph_content += f'\tlabel="{vars_string}"' + graph_content += "}" + with open(self.__out, "w+", encoding="utf-8") as dot_file: + dot_file.write(graph_content) + self.dispatch("SUCCESS") + + async def __no_packaging_main(self) -> None: + # 初始化 + self.dispatch("START") + tmp_dir_name = self.__out + if not os.path.exists(tmp_dir_name): + os.mkdir(tmp_dir_name) + + def createEdge(edge_id: int): + """ + 创建节点信息到 edges_info + """ + edges_info[edge_id] = { + "title": None, + "cid": None, + "button": None, + "condition": None, + "jump_type": None, + "is_default": None, + "command": None, + "sub": [], + } + + def var2dict(var: InteractiveVariable): + return { + "name": var.get_name(), + "id": var.get_id(), + "value": var.get_value(), + "show": var.is_show(), + "random": var.is_random(), + } + + # 存储顶点信息 + edges_info = {} + + # 使用队列来遍历剧情图,初始为 None 是为了从初始顶点开始 + queue: List[InteractiveNode] = [ + await (await self.__video.get_graph()).get_root_node() + ] + + # 设置初始顶点 + n = await (await self.__video.get_graph()).get_root_node() + if n.get_node_id() not in edges_info: + createEdge(n.get_node_id()) + edges_info[n.get_node_id()]["cid"] = n.get_cid() + edges_info[n.get_node_id()]["button"] = { + "text": n.get_self_button().get_text(), + "align": n.get_self_button().get_align(), + "pos": (n.get_self_button().get_pos()), + } + edges_info[n.get_node_id()]["vars"] = [ + var2dict(var) for var in (await n.get_vars()) + ] + edges_info[n.get_node_id()]["condition"] = (n.get_jumping_condition()._InteractiveJumpingCondition__command,) # type: ignore + edges_info[n.get_node_id()]["jump_type"] = 0 + edges_info[n.get_node_id()]["is_default"] = True + edges_info[n.get_node_id()]["command"] = n._InteractiveNode__command._InteractiveJumpingCommand__command # type: ignore + + while queue: + # 出队 + now_node = queue.pop() + + if ( + now_node.get_node_id() in edges_info + and edges_info[now_node.get_node_id()]["title"] is not None + and edges_info[now_node.get_node_id()]["cid"] is not None + ): + # 该情况为已获取到所有信息,说明是跳转到之前已处理的顶点,不作处理 + continue + + # 获取顶点信息,最大重试 3 次 + retry = 3 + while True: + try: + node = await now_node.get_info() + subs = await now_node.get_children() + self.dispatch( + "GET", + {"title": node["title"], "node_id": now_node.get_node_id()}, + ) + break + except Exception as e: + retry -= 1 + if retry < 0: + raise e + + # 检查节顶点是否在 edges_info 中,本次步骤得到 title 信息 + if node["edge_id"] not in edges_info: + # 不在,新建 + createEdge(node["edge_id"]) + + # 设置 title + edges_info[node["edge_id"]]["title"] = node["title"] + + # 无可达顶点,即不能再往下走了,类似树的叶子节点 + if "questions" not in node["edges"]: + continue + + # 遍历所有可达顶点 + for n in subs: + # 该步骤获取顶点的 cid(视频分 P 的 ID) + if n.get_node_id() not in edges_info: + createEdge(n.get_node_id()) + edges_info[n.get_node_id()]["cid"] = n.get_cid() + edges_info[n.get_node_id()]["button"] = { + "text": n.get_self_button().get_text(), + "align": n.get_self_button().get_align(), + "pos": n.get_self_button().get_pos(), + } + + def var2dict(var: InteractiveVariable): + return { + "name": var.get_name(), + "id": var.get_id(), + "value": var.get_value(), + "show": var.is_show(), + "random": var.is_random(), + } + + edges_info[n.get_node_id()]["condition"] = n.get_jumping_condition()._InteractiveJumpingCondition__command # type: ignore + edges_info[n.get_node_id()][ + "jump_type" + ] = await now_node.get_jumping_type() + edges_info[n.get_node_id()]["is_default"] = n.is_default() + edges_info[n.get_node_id()]["command"] = n._InteractiveNode__command._InteractiveJumpingCommand__command # type: ignore + edges_info[now_node.get_node_id()]["sub"] = [ + n.get_node_id() for n in subs + ] + # 所有可达顶点 ID 入队 + queue.insert(0, n) + + json.dump( + edges_info, + open(tmp_dir_name + "/ivideo.json", "w+", encoding="utf-8"), + indent=2, + ) + json.dump( + { + "bvid": self.__video.get_bvid(), + "title": (await self.__video.get_info())["title"], + }, + open(tmp_dir_name + "/bilivideo.json", "w+", encoding="utf-8"), + indent=2, + ) + + cid_set = set() + for key, item in edges_info.items(): + cid = item["cid"] + if not cid in cid_set: + self.dispatch("PREPARE_DOWNLOAD", {"cid": item["cid"]}) + cid_set.add(cid) + url = await self.__video.get_download_url(cid=cid, html5=True) + await self.__download_func( + url["durl"][0]["url"], + tmp_dir_name + "/" + str(key) + " " + item["title"] + ".mp4", + ) # type: ignore + + root_cid = await self.__video.get_cid() + if not root_cid in cid_set: + self.dispatch("PREPARE_DOWNLOAD", {"cid": root_cid}) + cid = await self.__video.get_cid() + url = await self.__video.get_download_url(cid=cid, html5=True) + title = (await self.__video.get_info())["title"] + await self.__download_func( + url["durl"][0]["url"], tmp_dir_name + "/1 " + title + ".mp4" + ) # type: ignore + + self.dispatch("SUCCESS") + + async def start(self) -> None: + """ + 开始下载 + """ + if self.__mode.value == "ivi": + task = create_task(self.__main()) + elif self.__mode.value == "dot": + task = create_task(self.__dot_graph_main()) + elif self.__mode.value == "no_pack": + task = create_task(self.__no_packaging_main()) + else: + task = create_task(self.__node_videos_main()) + self.__task = task + + try: + result = await task + self.__task = None + return result + except CancelledError: + # 忽略 task 取消异常 + pass + except Exception as e: + self.dispatch("FAILED", {"err": e}) + raise e + + async def abort(self) -> None: + """ + 中断下载 + """ + if self.__task: + self.__task.cancel("用户手动取消") + + self.dispatch("ABORTED", None) + + +def get_ivi_file_meta(path: str) -> dict: + """ + 获取 ivi 文件信息 + + Args: + path (str): 文件地址 + + Returns: + dict: 文件信息 + """ + ivi = zipfile.ZipFile(open(path, "rb")) + info = ivi.open("bilivideo.json").read() + return json.loads(info) diff --git a/bilibili_api/live.py b/bilibili_api/live.py new file mode 100644 index 0000000000000000000000000000000000000000..e7ec537e26111f9e082fa3f9e7992088cd3939a6 --- /dev/null +++ b/bilibili_api/live.py @@ -0,0 +1,1434 @@ +r""" +bilibili_api.live + +直播相关 +""" +import json +import time +import base64 +import struct +import asyncio +import logging +from enum import Enum +from typing import Any, List, Union + +import brotli +import aiohttp +from aiohttp.client_ws import ClientWebSocketResponse + +from .utils.utils import get_api, raise_for_statement +from .utils.danmaku import Danmaku +from .utils.network import get_aiohttp_session, Api, HEADERS +from .utils.AsyncEvent import AsyncEvent +from .utils.credential import Credential +from .exceptions.LiveException import LiveException + +API = get_api("live") + + +class ScreenResolution(Enum): + """ + 直播源清晰度。 + + 清晰度编号,4K 20000,原画 10000,蓝光(杜比)401,蓝光 400,超清 250,高清 150,流畅 80 + + FOUR_K : 4K。 + + ORIGINAL : 原画。 + + BLU_RAY_DOLBY : 蓝光(杜比)。 + + BLU_RAY : 蓝光。 + + ULTRA_HD : 超清。 + + HD : 高清。 + + FLUENCY : 流畅。 + """ + + FOUR_K = 20000 + ORIGINAL = 10000 + BLU_RAY_DOLBY = 401 + BLU_RAY = 400 + ULTRA_HD = 250 + HD = 150 + FLUENCY = 80 + + +class LiveProtocol(Enum): + """ + 直播源流协议。 + + 流协议,0 为 FLV 流,1 为 HLS 流。默认:0,1 + + FLV : 0。 + + HLS : 1。 + + DEFAULT : 0,1 + """ + + FLV = 0 + HLS = 1 + DEFAULT = "0,1" + + +class LiveFormat(Enum): + """ + 直播源容器格式 + + 容器格式,0 为 flv 格式;1 为 ts 格式(仅限 hls 流);2 为 fmp4 格式(仅限 hls 流)。默认:0,2 + + FLV : 0。 + + TS : 1。 + + FMP4 : 2。 + + DEFAULT : 2。 + """ + + FLV = 0 + TS = 1 + FMP4 = 2 + DEFAULT = "0,1,2" + + +class LiveCodec(Enum): + """ + 直播源视频编码 + + 视频编码,0 为 avc 编码,1 为 hevc 编码。默认:0,1 + + AVC : 0。 + + HEVC : 1。 + + DEFAULT : 0,1。 + """ + + AVC = 0 + HEVC = 1 + DEFAULT = "0,1" + + +class LiveRoom: + """ + 直播类,获取各种直播间的操作均在里边。 + + AttributesL + credential (Credential): 凭据类 + + room_display_id (int) : 房间展示 id + """ + + def __init__( + self, room_display_id: int, credential: Union[Credential, None] = None + ): + """ + Args: + room_display_id (int) : 房间展示 ID(即 URL 中的 ID) + + credential (Credential, optional): 凭据. Defaults to None. + """ + self.room_display_id = room_display_id + + if credential is None: + self.credential = Credential() + else: + self.credential = credential + + self.__ruid = None + + async def start(self, area_id: int) -> dict: + """ + 开始直播 + + Args: + area_id (int): 直播分区id(子分区id)。可使用 live_area 模块查询。 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["start"] + params = { + "area_v2": area_id, + "room_id": self.room_display_id, + "platform": "pc", + } + resp = ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + return resp + + async def stop(self) -> dict: + """ + 关闭直播 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["stop"] + params = { + "room_id": self.room_display_id, + } + resp = ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + return resp + + async def get_room_play_info(self) -> dict: + """ + 获取房间信息(真实房间号,封禁情况等) + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["room_play_info"] + params = { + "room_id": self.room_display_id, + } + resp = ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + # 缓存真实房间 ID + self.__ruid = resp["uid"] + return resp + + async def get_room_id(self) -> int: + return (await self.get_room_play_info())["room_id"] + + async def __get_ruid(self) -> int: + """ + 获取真实房间 ID,若有缓存则使用缓存 + """ + if self.__ruid is None: + await self.get_room_play_info() + + return self.__ruid # type: ignore + + async def get_ruid(self) -> int: + return await self.__get_ruid() + + async def get_danmu_info(self) -> dict: + """ + 获取聊天弹幕服务器配置信息(websocket) + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["danmu_info"] + params = {"id": self.room_display_id} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_room_info(self) -> dict: + """ + 获取直播间信息(标题,简介等) + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["room_info"] + params = {"room_id": self.room_display_id} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_fan_model( + self, + page_num: int = 1, + target_id: Union[int, None] = None, + roomId: Union[int, None] = None, + ) -> dict: + """ + 获取自己的粉丝勋章信息 + + 如果带有房间号就返回是否具有的判断 has_medal + + 如果带有主播 id ,就返回主播的粉丝牌,没有就返回 null + + Args: + roomId (int, optional) : 指定房间,查询是否拥有此房间的粉丝牌 + + target_id (int | None, optional): 指定返回一个主播的粉丝牌,留空就不返回 + + page_num (int | None, optional): 粉丝牌列表,默认 1 + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + + api = API["info"]["live_info"] + params = { + "pageSize": 10, + "page": page_num, + } + if roomId: + params["roomId"] = roomId + if target_id: + params["target_id"] = target_id + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_user_info_in_room(self) -> dict: + """ + 获取自己在直播间的信息(粉丝勋章等级,直播用户等级等) + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + + api = API["info"]["user_info_in_room"] + params = {"room_id": self.room_display_id} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_popular_ticket_num(self) -> dict: + """ + 获取自己在直播间的人气票数量(付费人气票已赠送的量,免费人气票的持有量) + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + + api = API["info"]["popular_ticket"] + params = { + "ruid": await self.__get_ruid(), + "surce": 0, + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def send_popular_ticket(self) -> dict: + """ + 赠送自己在直播间的所有免费人气票 + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["operate"]["send_popular_ticket"] + params = { + "ruid": await self.__get_ruid(), + "visit_id": "", + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_dahanghai(self, page: int = 1) -> dict: + """ + 获取大航海列表 + + Args: + page (int, optional): 页码. Defaults to 1. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["dahanghai"] + params = { + "roomid": self.room_display_id, + "ruid": await self.__get_ruid(), + "page_size": 30, + "page": page, + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_gaonengbang(self, page: int = 1) -> dict: + """ + 获取高能榜列表 + + Args: + page (int, optional): 页码. Defaults to 1 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["gaonengbang"] + params = { + "roomId": self.room_display_id, + "ruid": await self.__get_ruid(), + "pageSize": 50, + "page": page, + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_seven_rank(self) -> dict: + """ + 获取七日榜 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["seven_rank"] + params = { + "roomid": self.room_display_id, + "ruid": await self.__get_ruid(), + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_fans_medal_rank(self) -> dict: + """ + 获取粉丝勋章排行 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["fans_medal_rank"] + params = {"roomid": self.room_display_id, "ruid": await self.__get_ruid()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_black_list(self, page: int = 1) -> dict: + """ + 获取黑名单列表 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["black_list"] + params = {"room_id": self.room_display_id, "ps": page} + + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_room_play_url( + self, screen_resolution: ScreenResolution = ScreenResolution.ORIGINAL + ) -> dict: + """ + 获取房间直播流列表 + + Args: + screen_resolution (ScreenResolution, optional): 清晰度. Defaults to ScreenResolution.ORIGINAL + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["room_play_url"] + params = { + "cid": self.room_display_id, + "platform": "web", + "qn": screen_resolution.value, + "https_url_req": "1", + "ptype": "16", + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_room_play_info_v2( + self, + live_protocol: LiveProtocol = LiveProtocol.DEFAULT, + live_format: LiveFormat = LiveFormat.DEFAULT, + live_codec: LiveCodec = LiveCodec.DEFAULT, + live_qn: ScreenResolution = ScreenResolution.ORIGINAL, + ) -> dict: + """ + 获取房间信息及可用清晰度列表 + + Args: + live_protocol (LiveProtocol, optional) : 直播源流协议. Defaults to LiveProtocol.DEFAULT. + + live_format (LiveFormat, optional) : 直播源容器格式. Defaults to LiveFormat.DEFAULT. + + live_codec (LiveCodec, optional) : 直播源视频编码. Defaults to LiveCodec.DEFAULT. + + live_qn (ScreenResolution, optional): 直播源清晰度. Defaults to ScreenResolution.ORIGINAL. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["room_play_info_v2"] + params = { + "room_id": self.room_display_id, + "platform": "web", + "ptype": "16", + "protocol": live_protocol.value, + "format": live_format.value, + "codec": live_codec.value, + "qn": live_qn.value, + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def ban_user(self, uid: int) -> dict: + """ + 封禁用户 + + Args: + uid (int): 用户 UID + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + + api = API["operate"]["add_block"] + data = { + "room_id": self.room_display_id, + "tuid": uid, + "mobile_app": "web", + "visit_id": "", + } + return await Api(**api, credential=self.credential).update_data(**data).result + + async def unban_user(self, uid: int) -> dict: + """ + 解封用户 + + Args: + uid (int): 用户 UID + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + api = API["operate"]["del_block"] + data = { + "room_id": self.room_display_id, + "tuid": uid, + } + return await Api(**api, credential=self.credential).update_data(**data).result + + async def send_danmaku(self, danmaku: Danmaku, reply_mid: int = None) -> dict: + """ + 直播间发送弹幕 + + Args: + danmaku (Danmaku): 弹幕类 + + reply_mid (int, optional): @的 UID. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + + api = API["operate"]["send_danmaku"] + room_id = (await self.get_room_play_info())["room_id"] + + data = { + "mode": danmaku.mode, + "msg": danmaku.text, + "roomid": room_id, + "bubble": 0, + "rnd": int(time.time()), + "color": int(danmaku.color, 16), + "fontsize": danmaku.font_size, + } + if reply_mid: data["reply_mid"] = reply_mid + return await Api(**api, credential=self.credential).update_data(**data).result + + async def sign_up_dahanghai(self, task_id: int = 1447) -> dict: + """ + 大航海签到 + + Args: + task_id (int, optional): 签到任务 ID. Defaults to 1447 + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["operate"]["sign_up_dahanghai"] + data = { + "task_id": task_id, + "uid": await self.__get_ruid(), + } + return await Api(**api, credential=self.credential).update_data(**data).result + + async def send_gift_from_bag( + self, + uid: int, + bag_id: int, + gift_id: int, + gift_num: int, + storm_beat_id: int = 0, + price: int = 0, + ) -> dict: + """ + 赠送包裹中的礼物,获取包裹信息可以使用 get_self_bag 方法 + + Args: + uid (int) : 赠送用户的 UID + + bag_id (int) : 礼物背包 ID + + gift_id (int) : 礼物 ID + + gift_num (int) : 礼物数量 + + storm_beat_id (int, optional) : 未知, Defaults to 0 + + price (int, optional) : 礼物单价,Defaults to 0 + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["operate"]["send_gift_from_bag"] + data = { + "uid": uid, + "bag_id": bag_id, + "gift_id": gift_id, + "gift_num": gift_num, + "platform": "pc", + "send_ruid": 0, + "storm_beat_id": storm_beat_id, + "price": price, + "biz_code": "live", + "biz_id": self.room_display_id, + "ruid": await self.__get_ruid(), + } + return await Api(**api, credential=self.credential).update_data(**data).result + + async def receive_reward(self, receive_type: int = 2) -> dict: + """ + 领取自己在某个直播间的航海日志奖励 + + Args: + receive_type (int) : 领取类型,Defaults to 2. + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + + api = API["operate"]["receive_reward"] + data = { + "ruid": await self.__get_ruid(), + "receive_type": receive_type, + } + return await Api(**api, credential=self.credential).update_data(**data).result + + async def get_general_info(self, act_id: int = 100061) -> dict: + """ + 获取自己在该房间的大航海信息, 比如是否开通, 等级等 + + Args: + act_id (int, optional) : 未知,Defaults to 100061 + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + + api = API["info"]["general_info"] + params = { + "actId": act_id, + "roomId": self.room_display_id, + "uid": await self.__get_ruid(), + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def update_news(self, content: str) -> dict: + """ + 更新公告 + + Args: + content: 最多60字符 + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + + api = API["info"]["update_news"] + params = { + "content": content, + "roomId": self.room_display_id, + "uid": await self.__get_ruid(), + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_gift_common(self) -> dict: + """ + 获取当前直播间内的普通礼物列表 + + Returns: + dict: 调用 API 返回的结果 + """ + api_room_info = API["info"]["room_info"] + params_room_info = { + "room_id": self.room_display_id, + } + res_room_info = ( + await Api(**api_room_info, credential=self.credential) + .update_params(**params_room_info) + .result + ) + area_id, area_parent_id = ( + res_room_info["room_info"]["area_id"], + res_room_info["room_info"]["parent_area_id"], + ) + + api = API["info"]["gift_common"] + params = { + "room_id": self.room_display_id, + "area_id": area_id, + "area_parent_id": area_parent_id, + "platform": "pc", + "source": "live", + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_gift_special(self, tab_id: int) -> dict: + """ + 注:此 API 已失效,请使用 live.get_gift_config + + 获取当前直播间内的特殊礼物列表 + + Args: + tab_id (int) : 2:特权礼物,3:定制礼物 + + Returns: + dict: 调用 API 返回的结果 + """ + api_room_info = API["info"]["room_info"] + params_room_info = { + "room_id": self.room_display_id, + } + res_room_info = ( + await Api(**api_room_info, credential=self.credential) + .update_params(**params_room_info) + .result + ) + area_id, area_parent_id = ( + res_room_info["room_info"]["area_id"], + res_room_info["room_info"]["parent_area_id"], + ) + + api = API["info"]["gift_special"] + + params = { + "tab_id": tab_id, + "area_id": area_id, + "area_parent_id": area_parent_id, + "room_id": await self.__get_ruid(), + "source": "live", + "platform": "pc", + "build": 1, + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def send_gift_gold( + self, uid: int, gift_id: int, gift_num: int, price: int, storm_beat_id: int = 0 + ) -> dict: + """ + 赠送金瓜子礼物 + + Args: + uid (int) : 赠送用户的 UID + + gift_id (int) : 礼物 ID (可以通过 get_gift_common 或 get_gift_special 或 get_gift_config 获取) + + gift_num (int) : 赠送礼物数量 + + price (int) : 礼物单价 + + storm_beat_id (int, Optional): 未知,Defaults to 0 + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["operate"]["send_gift_gold"] + data = { + "uid": uid, + "gift_id": gift_id, + "gift_num": gift_num, + "price": price, + "ruid": await self.__get_ruid(), + "biz_code": "live", + "biz_id": self.room_display_id, + "platform": "pc", + "storm_beat_id": storm_beat_id, + "send_ruid": 0, + "coin_type": "gold", + "bag_id": "0", + "rnd": int(time.time()), + "visit_id": "", + } + return await Api(**api, credential=self.credential).update_data(**data).result + + async def send_gift_silver( + self, + uid: int, + gift_id: int, + gift_num: int, + price: int, + storm_beat_id: int = 0, + ) -> dict: + """ + 赠送银瓜子礼物 + + Args: + uid (int) : 赠送用户的 UID + + gift_id (int) : 礼物 ID (可以通过 get_gift_common 或 get_gift_special 或 get_gift_config 获取) + + gift_num (int) : 赠送礼物数量 + + price (int) : 礼物单价 + + storm_beat_id (int, Optional): 未知, Defaults to 0 + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["operate"]["send_gift_silver"] + data = { + "uid": uid, + "gift_id": gift_id, + "gift_num": gift_num, + "price": price, + "ruid": await self.__get_ruid(), + "biz_code": "live", + "biz_id": self.room_display_id, + "platform": "pc", + "storm_beat_id": storm_beat_id, + "send_ruid": 0, + "coin_type": "silver", + "bag_id": 0, + "rnd": int(time.time()), + "visit_id": "", + } + return await Api(**api, credential=self.credential).update_data(**data).result + + +class LiveDanmaku(AsyncEvent): + """ + Websocket 实时获取直播弹幕 + + Events: + + DANMU_MSG: 用户发送弹幕 + + SEND_GIFT: 礼物 + + COMBO_SEND:礼物连击 + + GUARD_BUY:续费大航海 + + SUPER_CHAT_MESSAGE:醒目留言(SC) + + SUPER_CHAT_MESSAGE_JPN:醒目留言(带日语翻译?) + + WELCOME: 老爷进入房间 + + WELCOME_GUARD: 房管进入房间 + + NOTICE_MSG: 系统通知(全频道广播之类的) + + PREPARING: 直播准备中 + + LIVE: 直播开始 + + ROOM_REAL_TIME_MESSAGE_UPDATE: 粉丝数等更新 + + ENTRY_EFFECT: 进场特效 + + ROOM_RANK: 房间排名更新 + + INTERACT_WORD: 用户进入直播间 + + ACTIVITY_BANNER_UPDATE_V2: 好像是房间名旁边那个 xx 小时榜 + + =========================== + + 本模块自定义事件: + + ========================== + + VIEW: 直播间人气更新 + + ALL: 所有事件 + + DISCONNECT: 断开连接(传入连接状态码参数) + + TIMEOUT: 心跳响应超时 + + VERIFICATION_SUCCESSFUL: 认证成功 + """ + + PROTOCOL_VERSION_RAW_JSON = 0 + PROTOCOL_VERSION_HEARTBEAT = 1 + PROTOCOL_VERSION_BROTLI_JSON = 3 + + DATAPACK_TYPE_HEARTBEAT = 2 + DATAPACK_TYPE_HEARTBEAT_RESPONSE = 3 + DATAPACK_TYPE_NOTICE = 5 + DATAPACK_TYPE_VERIFY = 7 + DATAPACK_TYPE_VERIFY_SUCCESS_RESPONSE = 8 + + STATUS_INIT = 0 + STATUS_CONNECTING = 1 + STATUS_ESTABLISHED = 2 + STATUS_CLOSING = 3 + STATUS_CLOSED = 4 + STATUS_ERROR = 5 + + def __init__( + self, + room_display_id: int, + debug: bool = False, + credential: Union[Credential, None] = None, + max_retry: int = 5, + retry_after: float = 1, + ): + """ + Args: + room_display_id (int) : 房间展示 ID + debug (bool, optional) : 调试模式,将输出更多信息。. Defaults to False. + credential (Credential | None, optional): 凭据. Defaults to None. + max_retry (int, optional) : 连接出错后最大重试次数. Defaults to 5 + retry_after (int, optional) : 连接出错后重试间隔时间(秒). Defaults to 1 + """ + super().__init__() + + self.credential = credential if credential is not None else Credential() + self.room_display_id = room_display_id + self.max_retry = max_retry + self.retry_after = retry_after + self.__room_real_id = None + self.__status = 0 + self.__ws = None + self.__tasks = [] + self.__debug = debug + self.__heartbeat_timer = 60.0 + self.err_reason = "" + + # logging + self.logger = logging.getLogger(f"LiveDanmaku_{self.room_display_id}") + self.logger.setLevel(logging.DEBUG if debug else logging.INFO) + if not self.logger.handlers: + handler = logging.StreamHandler() + handler.setFormatter( + logging.Formatter( + "[" + + str(room_display_id) + + "][%(asctime)s][%(levelname)s] %(message)s" + ) + ) + self.logger.addHandler(handler) + + def get_status(self) -> int: + """ + 获取连接状态 + + Returns: + int: 0 初始化,1 连接建立中,2 已连接,3 断开连接中,4 已断开,5 错误 + """ + return self.__status + + async def connect(self) -> None: + """ + 连接直播间 + """ + if self.get_status() == self.STATUS_CONNECTING: + raise LiveException("正在建立连接中") + + if self.get_status() == self.STATUS_ESTABLISHED: + raise LiveException("连接已建立,不可重复调用") + + if self.get_status() == self.STATUS_CLOSING: + raise LiveException("正在关闭连接,不可调用") + + await self.__main() + + async def disconnect(self) -> None: + """ + 断开连接 + """ + if self.get_status() != self.STATUS_ESTABLISHED: + raise LiveException("尚未连接服务器") + + self.__status = self.STATUS_CLOSING + self.logger.info("连接正在关闭") + + # 取消所有任务 + while len(self.__tasks) > 0: + self.__tasks.pop().cancel() + + self.__status = self.STATUS_CLOSED + await self.__ws.close() # type: ignore + + self.logger.info("连接已关闭") + + async def __main(self) -> None: + """ + 入口 + """ + self.__status = self.STATUS_CONNECTING + + room = LiveRoom(self.room_display_id, self.credential) + self.logger.info(f"准备连接直播间 {self.room_display_id}") + # 获取真实房间号 + self.logger.debug("正在获取真实房间号") + info = await room.get_room_play_info() + self.__room_real_id = info["room_id"] + self.logger.debug(f"获取成功,真实房间号:{self.__room_real_id}") + + # 获取直播服务器配置 + self.logger.debug("正在获取聊天服务器配置") + conf = await room.get_danmu_info() + self.logger.debug("聊天服务器配置获取成功") + + # 连接直播间 + self.logger.debug("准备连接直播间") + session = get_aiohttp_session() + available_hosts: List[dict] = conf["host_list"] + retry = self.max_retry + host = None + + @self.on("TIMEOUT") + async def on_timeout(ev): + # 连接超时 + self.err_reason = "心跳响应超时" + await self.__ws.close() # type: ignore + + while True: + self.err_reason = "" + # 重置心跳计时器 + self.__heartbeat_timer = 0 + if not available_hosts: + self.err_reason = "已尝试所有主机但仍无法连接" + break + + if host is None or retry <= 0: + host = available_hosts.pop() + retry = self.max_retry + + port = host["wss_port"] + protocol = "wss" + uri = f"{protocol}://{host['host']}:{port}/sub" + self.__status = self.STATUS_CONNECTING + self.logger.info(f"正在尝试连接主机: {uri}") + + try: + async with session.ws_connect(uri, headers=HEADERS.copy()) as ws: + + @self.on("VERIFICATION_SUCCESSFUL") + async def on_verification_successful(data): + # 新建心跳任务 + while len(self.__tasks) > 0: + self.__tasks.pop().cancel() + self.__tasks.append(asyncio.create_task(self.__heartbeat(ws))) + + self.__ws = ws + self.logger.debug("连接主机成功, 准备发送认证信息") + await self.__send_verify_data(ws, conf["token"]) + + async for msg in ws: + if msg.type == aiohttp.WSMsgType.BINARY: + self.logger.debug(f"收到原始数据:{msg.data}") + await self.__handle_data(msg.data) + + elif msg.type == aiohttp.WSMsgType.ERROR: + self.__status = self.STATUS_ERROR + self.logger.error("出现错误") + + elif msg.type == aiohttp.WSMsgType.CLOSING: + self.logger.debug("连接正在关闭") + self.__status = self.STATUS_CLOSING + + elif msg.type == aiohttp.WSMsgType.CLOSED: + self.logger.info("连接已关闭") + self.__status = self.STATUS_CLOSED + + # 正常断开情况下跳出循环 + if self.__status != self.STATUS_CLOSED or self.err_reason: + # 非用户手动调用关闭,触发重连 + self.logger.warning( + "非正常关闭连接" if not self.err_reason else self.err_reason + ) + else: + break + + except Exception as e: + await ws.close() + self.logger.warning(e) + if retry <= 0 or len(available_hosts) == 0: + self.logger.error("无法连接服务器") + self.err_reason = "无法连接服务器" + break + + self.logger.warning(f"将在 {self.retry_after} 秒后重新连接...") + self.__status = self.STATUS_ERROR + retry -= 1 + await asyncio.sleep(self.retry_after) + + async def __handle_data(self, data) -> None: + """ + 处理数据 + """ + data = self.__unpack(data) + self.logger.debug(f"收到信息:{data}") + + for info in data: + callback_info = { + "room_display_id": self.room_display_id, + "room_real_id": self.__room_real_id, + } + # 依次处理并调用用户指定函数 + if ( + info["datapack_type"] + == LiveDanmaku.DATAPACK_TYPE_VERIFY_SUCCESS_RESPONSE + ): + # 认证反馈 + if info["data"]["code"] == 0: + # 认证成功反馈 + self.logger.info("连接服务器并认证成功") + self.__status = self.STATUS_ESTABLISHED + callback_info["type"] = "VERIFICATION_SUCCESSFUL" + callback_info["data"] = None + self.dispatch("VERIFICATION_SUCCESSFUL", callback_info) + self.dispatch("ALL", callback_info) + + elif info["datapack_type"] == LiveDanmaku.DATAPACK_TYPE_HEARTBEAT_RESPONSE: + # 心跳包反馈,返回直播间人气 + self.logger.debug("收到心跳包反馈") + # 重置心跳计时器 + self.__heartbeat_timer = 30.0 + callback_info["type"] = "VIEW" + callback_info["data"] = info["data"]["view"] + self.dispatch("VIEW", callback_info) + self.dispatch("ALL", callback_info) + + elif info["datapack_type"] == LiveDanmaku.DATAPACK_TYPE_NOTICE: + # 直播间弹幕、礼物等信息 + callback_info["type"] = info["data"]["cmd"] + + # DANMU_MSG 事件名特殊:DANMU_MSG:4:0:2:2:2:0,需取出事件名,暂不知格式 + if callback_info["type"].find("DANMU_MSG") > -1: + callback_info["type"] = "DANMU_MSG" + info["data"]["cmd"] = "DANMU_MSG" + + callback_info["data"] = info["data"] + self.dispatch(callback_info["type"], callback_info) + self.dispatch("ALL", callback_info) + + else: + self.logger.warning("检测到未知的数据包类型,无法处理") + + async def __send_verify_data(self, ws: ClientWebSocketResponse, token: str) -> None: + self.credential.raise_for_no_buvid3() + # 没传入 dedeuserid 可以试图 live.get_self_info + if not self.credential.has_dedeuserid(): + try: + info = await get_self_info(self.credential) + self.credential.dedeuserid = str(info["uid"]) + except: + pass # 留到下面一起抛出错误 + self.credential.raise_for_no_dedeuserid() + verifyData = { + "uid": int(self.credential.dedeuserid), + "roomid": self.__room_real_id, + "protover": 3, + "buvid": self.credential.buvid3, + "platform": "web", + "type": 2, + "key": token, + } + data = json.dumps(verifyData).encode() + await self.__send( + data, self.PROTOCOL_VERSION_HEARTBEAT, self.DATAPACK_TYPE_VERIFY, ws + ) + + async def __heartbeat(self, ws: ClientWebSocketResponse) -> None: + """ + 定时发送心跳包 + """ + HEARTBEAT = self.__pack( + b"[object Object]", + self.PROTOCOL_VERSION_HEARTBEAT, + self.DATAPACK_TYPE_HEARTBEAT, + ) + while True: + if self.__heartbeat_timer == 0: + self.logger.debug("发送心跳包") + await ws.send_bytes(HEARTBEAT) + heartbeat_url = "https://live-trace.bilibili.com/xlive/rdata-interface/v1/heartbeat/webHeartBeat?pf=web&hb=" + hb = str( + base64.b64encode(f"60|{self.room_display_id}|1|0".encode("utf-8")), + "utf-8", + ) + await Api( + method="GET", url=heartbeat_url, json_body=True + ).update_params(**{"hb": hb, "pf": "web"}).result + elif self.__heartbeat_timer <= -30: + # 视为已异常断开连接,发布 TIMEOUT 事件 + self.dispatch("TIMEOUT") + break + + await asyncio.sleep(1.0) + self.__heartbeat_timer -= 1 + + async def __send( + self, + data: bytes, + protocol_version: int, + datapack_type: int, + ws: ClientWebSocketResponse, + ) -> None: + """ + 自动打包并发送数据 + """ + data = self.__pack(data, protocol_version, datapack_type) + self.logger.debug(f"发送原始数据:{data}") + await ws.send_bytes(data) + + @staticmethod + def __pack(data: bytes, protocol_version: int, datapack_type: int) -> bytes: + """ + 打包数据 + """ + sendData = bytearray() + sendData += struct.pack(">H", 16) + raise_for_statement(0 <= protocol_version <= 2, LiveException("数据包协议版本错误,范围 0~2")) + sendData += struct.pack(">H", protocol_version) + raise_for_statement(datapack_type in [2, 7], LiveException("数据包类型错误,可用类型:2, 7")) + sendData += struct.pack(">I", datapack_type) + sendData += struct.pack(">I", 1) + sendData += data + sendData = struct.pack(">I", len(sendData) + 4) + sendData + return bytes(sendData) + + @staticmethod + def __unpack(data: bytes) -> List[Any]: + """ + 解包数据 + """ + ret = [] + offset = 0 + header = struct.unpack(">IHHII", data[:16]) + if header[2] == LiveDanmaku.PROTOCOL_VERSION_BROTLI_JSON: + realData = brotli.decompress(data[16:]) + else: + realData = data + + if ( + header[2] == LiveDanmaku.PROTOCOL_VERSION_HEARTBEAT + and header[3] == LiveDanmaku.DATAPACK_TYPE_HEARTBEAT_RESPONSE + ): + realData = realData[16:] + # 心跳包协议特殊处理 + recvData = { + "protocol_version": header[2], + "datapack_type": header[3], + "data": {"view": struct.unpack(">I", realData[0:4])[0]}, + } + ret.append(recvData) + return ret + + while offset < len(realData): + header = struct.unpack(">IHHII", realData[offset: offset + 16]) + length = header[0] + recvData = { + "protocol_version": header[2], + "datapack_type": header[3], + "data": None, + } + chunkData = realData[(offset + 16): (offset + length)] + if header[2] == 0: + recvData["data"] = json.loads(chunkData.decode()) + elif header[2] == 2: + recvData["data"] = json.loads(chunkData.decode()) + elif header[2] == 1: + if header[3] == LiveDanmaku.DATAPACK_TYPE_HEARTBEAT_RESPONSE: + recvData["data"] = {"view": struct.unpack(">I", chunkData)[0]} + elif header[3] == LiveDanmaku.DATAPACK_TYPE_VERIFY_SUCCESS_RESPONSE: + recvData["data"] = json.loads(chunkData.decode()) + ret.append(recvData) + offset += length + return ret + + +async def get_self_info(credential: Credential) -> dict: + """ + 获取自己直播等级、排行等信息 + + Returns: + dict: 调用 API 返回的结果 + """ + credential.raise_for_no_sessdata() + + api = API["info"]["user_info"] + return await Api(**api, credential=credential).result + + +async def get_self_live_info(credential: Credential) -> dict: + """ + 获取自己的粉丝牌、大航海等信息 + + Returns: + dict: 调用 API 返回的结果 + """ + + credential.raise_for_no_sessdata() + + api = API["info"]["live_info"] + return await Api(**api, credential=credential).result + + +async def get_self_dahanghai_info( + page: int = 1, page_size: int = 10, credential: Union[Credential, None] = None +) -> dict: + """ + 获取自己开通的大航海信息 + + Args: + page (int, optional): 页数. Defaults to 1. + + page_size (int, optional): 每页数量. Defaults to 10. + + 总页数取得方法: + + ```python + import math + + info = live.get_self_live_info(credential) + pages = math.ceil(info['data']['guards'] / 10) + ``` + + Returns: + dict: 调用 API 返回的结果 + """ + if credential is None: + credential = Credential() + + credential.raise_for_no_sessdata() + + api = API["info"]["user_guards"] + params = {"page": page, "page_size": page_size} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_self_bag(credential: Credential) -> dict: + """ + 获取自己的直播礼物包裹信息 + + Returns: + dict: 调用 API 返回的结果 + """ + + credential.raise_for_no_sessdata() + + api = API["info"]["bag_list"] + return await Api(**api, credential=credential).result + + +async def get_gift_config( + room_id: Union[int, None] = None, + area_id: Union[int, None] = None, + area_parent_id: Union[int, None] = None, +): + """ + 获取所有礼物的信息,包括礼物 id、名称、价格、等级等。 + + 同时填了 room_id、area_id、area_parent_id,则返回一个较小的 json,只包含该房间、该子区域、父区域的礼物。 + + 但即使限定了三个条件,仍然会返回约 1.5w 行的 json。不加限定则是 2.8w 行。 + + Args: + room_id (int, optional) : 房间显示 ID. Defaults to None. + area_id (int, optional) : 子分区 ID. Defaults to None. + area_parent_id (int, optional) : 父分区 ID. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["gift_config"] + params = { + "platform": "pc", + "source": "live", + "room_id": room_id if room_id is not None else "", + "area_id": area_id if area_id is not None else "", + "area_parent_id": area_parent_id if area_parent_id is not None else "", + } + return await Api(**api).update_params(**params).result + + +async def get_area_info() -> dict: + """ + 获取所有分区信息 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["area_info"] + return await Api(**api).result + + +async def get_live_followers_info( + need_recommend: bool = True, credential: Union[Credential, None] = None +) -> dict: + """ + 获取关注列表中正在直播的直播间信息,包括房间直播热度,房间名称及标题,清晰度,是否官方认证等信息。 + + Args: + need_recommend (bool, optional): 是否接受推荐直播间,Defaults to True + + Returns: + dict: 调用 API 返回的结果 + """ + if credential is None: + credential = Credential() + + credential.raise_for_no_sessdata() + + api = API["info"]["followers_live_info"] + params = {"need_recommend": int(need_recommend), "filterRule": 0} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_unlive_followers_info( + page: int = 1, page_size: int = 30, credential: Union[Credential, None] = None +) -> dict: + """ + 获取关注列表中未在直播的直播间信息,包括上次开播时间,上次开播的类别,直播间公告,是否有录播等。 + + Args: + page (int, optional): 页码, Defaults to 1. + + page_size (int, optional): 每页数量 Defaults to 30. + + Returns: + dict: 调用 API 返回的结果 + """ + if credential is None: + credential = Credential() + + credential.raise_for_no_sessdata() + + api = API["info"]["followers_unlive_info"] + params = { + "page": page, + "pagesize": page_size, + } + return await Api(**api, credential=credential).update_params(**params).result + + +async def create_live_reserve( + title: str, start_time: int, credential: Credential +) -> dict: + """ + 创建直播预约 + + Args: + title (str) : 直播间标题 + + start_time (int) : 开播时间戳 + + Returns: + dict: 调用 API 返回的结果 + """ + credential.raise_for_no_sessdata() + + api = API["operate"]["create_reserve"] + data = { + "title": title, + "type": 2, + "live_plan_start_time": start_time, + "stime": None, + "from": 1, + } + return await Api(**api, credential=credential).update_data(**data).result diff --git a/bilibili_api/live_area.py b/bilibili_api/live_area.py new file mode 100644 index 0000000000000000000000000000000000000000..2d30a321a337acd76afe59d53acc4be61e060053 --- /dev/null +++ b/bilibili_api/live_area.py @@ -0,0 +1,152 @@ +""" +bilibili_api.live_area + +直播间分区相关操作。 +""" + +import os +import copy +import json +from enum import Enum +from typing import Dict, List, Tuple, Union + +from .utils.utils import get_api +from .utils.network import Api + +API = get_api("live-area") + + +class LiveRoomOrder(Enum): + """ + 直播间排序方式 + + - RECOMMEND: 综合 + - NEW: 最新 + """ + + RECOMMEND = "" + NEW = "live_time" + + +def get_area_info_by_id(id: int) -> Tuple[Union[dict, None], Union[dict, None]]: + """ + 根据 id 获取分区信息。 + + Args: + id (int): 分区的 id。 + + Returns: + `Tuple[dict | None, dict | None]`: 第一个是主分区,第二个是子分区,没有时返回 None。 + """ + with open( + os.path.join(os.path.dirname(__file__), "data/live_area.json"), encoding="utf8" + ) as f: + channel = json.loads(f.read()) + + for main_ch in channel: + if "id" not in main_ch: + continue + if id == int(main_ch["id"]): + return main_ch, None + + # 搜索子分区 + if "list" in main_ch.keys(): + for sub_ch in main_ch["list"]: + if "id" not in sub_ch: + continue + if str(id) == sub_ch["id"]: + return main_ch, sub_ch + else: + return None, None + + +def get_area_info_by_name(name: str) -> Tuple[Union[dict, None], Union[dict, None]]: + """ + 根据频道名称获取频道信息。 + + Args: + name (str): 分区的名称。 + + Returns: + Tuple[dict | None, dict | None]: 第一个是主分区,第二个是子分区,没有时返回 None。 + """ + with open( + os.path.join(os.path.dirname(__file__), "data/live_area.json"), encoding="utf8" + ) as f: + channel = json.loads(f.read()) + + for main_ch in channel: + if name in main_ch["name"]: + return main_ch, None + if "list" in main_ch.keys(): + for sub_ch in main_ch["list"]: + if name in sub_ch["name"]: + return main_ch, sub_ch + else: + return None, None + + +def get_area_list() -> List[Dict]: + """ + 获取所有分区的数据 + + Returns: + List[dict]: 所有分区的数据 + """ + with open( + os.path.join(os.path.dirname(__file__), "data/live_area.json"), encoding="utf8" + ) as f: + channel = json.loads(f.read()) + channel_list = [] + for channel_big in channel: + channel_big_copy = copy.copy(channel_big) + channel_list.append(channel_big_copy) + if "list" in channel_big.keys(): + channel_big_copy.pop("list") + for channel_sub in channel_big["list"]: + channel_sub_copy = copy.copy(channel_sub) + channel_sub_copy["father"] = channel_big_copy + channel_list.append(channel_sub_copy) + return channel_list + + +def get_area_list_sub() -> dict: + """ + 获取所有分区的数据 + 含父子关系(即一层次只有主分区) + + Returns: + dict: 所有分区的数据 + """ + with open( + os.path.join(os.path.dirname(__file__), "data/live_area.json"), encoding="utf8" + ) as f: + channel = json.loads(f.read()) + return channel + + +async def get_list_by_area( + area_id: int, page: int = 1, order: LiveRoomOrder = LiveRoomOrder.RECOMMEND +) -> dict: + """ + 根据分区获取直播间列表 + + Args: + area_id (int) : 分区 id + + page (int) : 第几页. Defaults to 1. + + order (LiveRoomOrder): 直播间排序方式. Defaults to LiveRoomOrder.RECOMMEND. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["list"] + params = { + "platform": "web", + "parent_area_id": get_area_info_by_id(area_id)[0]["id"], + "area_id": 0 if (get_area_info_by_id(area_id)[1] == None) else area_id, + "page": page, + "sort_type": order.value, + } + return await Api(**api).update_params(**params).result diff --git a/bilibili_api/login.py b/bilibili_api/login.py new file mode 100644 index 0000000000000000000000000000000000000000..1205b1db4fe24970ea8fcf5f5ef811608a4674f2 --- /dev/null +++ b/bilibili_api/login.py @@ -0,0 +1,703 @@ +""" +bilibili_api.login + +登录 + +**虽然可能有其他函数,但是请忽略他们,这些并不重要** + +**login_with_qrcode 用到了 tkinter,linux 的小伙伴请注意安装** +""" + +import os +import sys +import json +import time +import uuid +import base64 +import hashlib +import tempfile +import webbrowser +from typing import Dict, List, Union + +import rsa +import httpx +import urllib.parse +import qrcode +from yarl import URL + +from . import settings +from .utils.sync import sync +from .utils.utils import get_api +from .utils.credential import Credential +from .exceptions.LoginError import LoginError +from .utils.network import to_form_urlencoded, Api +from .utils.network import ( + HEADERS, + get_session, + get_spi_buvid_sync, + get_httpx_sync_session, +) +from .utils.captcha import get_result, close_server, start_server +from .utils.safecenter_captcha import get_result as safecenter_get_result +from .utils.safecenter_captcha import close_server as safecenter_close_server +from .utils.safecenter_captcha import start_server as safecenter_start_server + +API = get_api("login") + +# ---------------------------------------------------------------- +# 二维码登录 +# ---------------------------------------------------------------- + +photo = None # 图片的全局变量 + +start = time.perf_counter() +login_key = "" +qrcode_image = None +credential = Credential() +is_destroy = False +id_ = 0 # 事件 id,用于取消 after 绑定 + + +def parse_credential_url(events: dict) -> Credential: + url = events["url"] + cookies_list = url.split("?")[1].split("&") + sessdata = "" + bili_jct = "" + dedeuserid = "" + for cookie in cookies_list: + if cookie[:8] == "SESSDATA": + sessdata = cookie[9:] + if cookie[:8] == "bili_jct": + bili_jct = cookie[9:] + if cookie[:11].upper() == "DEDEUSERID=": + dedeuserid = cookie[11:] + ac_time_value = events["refresh_token"] + buvid3 = get_spi_buvid_sync()["b_3"] + return Credential( + sessdata=sessdata, + bili_jct=bili_jct, + buvid3=buvid3, + dedeuserid=dedeuserid, + ac_time_value=ac_time_value, + ) + + +def make_qrcode(url) -> str: + qr = qrcode.QRCode() + qr.add_data(url) + img = qr.make_image() + img.save(os.path.join(tempfile.gettempdir(), "qrcode.png")) + print("二维码已保存至", os.path.join(tempfile.gettempdir(), "qrcode.png")) + return os.path.join(tempfile.gettempdir(), "qrcode.png") + + +def update_qrcode_data() -> dict: + api = API["qrcode"]["web"]["get_qrcode_and_token"] + qrcode_data = Api(credential=credential, **api).result_sync + return qrcode_data + + +def login_with_qrcode(root=None) -> Credential: + """ + 扫描二维码登录 + + Args: + root (tkinter.Tk | tkinter.Toplevel, optional): 根窗口,默认为 tkinter.Tk(),如果有需要可以换成 tkinter.Toplevel(). Defaults to None. + + Returns: + Credential: 凭据 + """ + global start + global photo + global login_key, qrcode_image + global credential + global id_ + import tkinter + import tkinter.font + + from PIL.ImageTk import PhotoImage + + if root == None: + root = tkinter.Tk() + root.title("扫码登录") + qrcode_data = update_qrcode_data() + login_key = qrcode_data["qrcode_key"] + qrcode_image = make_qrcode(qrcode_data["url"]) + photo = PhotoImage(file=qrcode_image) + qrcode_label = tkinter.Label(root, image=photo, width=600, height=600) + qrcode_label.pack() + big_font = tkinter.font.Font(root, size=25) + log = tkinter.Label(root, text="请扫描二维码↑", font=big_font, fg="red") + log.pack() + + def update_events(): + global id_ + global start, credential, is_destroy, login_key + events = login_with_key(login_key) + + if events["code"] == 86101: + log.configure(text="请扫描二维码↑", fg="red", font=big_font) + elif events["code"] == 86090: + log.configure(text="点下确认啊!", fg="orange", font=big_font) + elif events["code"] == 86038: + raise LoginError("二维码过期,请扫新二维码!") + elif events["code"] == 0: + log.configure(text="成功!", fg="green", font=big_font) + credential = parse_credential_url(events) + root.after(1000, destroy) + return 0 + id_ = root.after(500, update_events) + if time.perf_counter() - start > 120: # 刷新 + qrcode_data = update_qrcode_data() + login_key = qrcode_data["qrcode_key"] + qrcode_image = make_qrcode(qrcode_data["url"]) + photo = PhotoImage(file=qrcode_image) + qrcode_label = tkinter.Label(root, image=photo, width=600, height=600) + qrcode_label.pack() + start = time.perf_counter() + + root.update() + + def destroy(): + global id_ + root.after_cancel(id_) # type: ignore + root.destroy() + + root.after(500, update_events) + root.mainloop() + root.after_cancel(id_) # type: ignore + return credential + + +def login_with_qrcode_term() -> Credential: + """ + 终端扫描二维码登录 + + Args: + + Returns: + Credential: 凭据 + """ + import qrcode_terminal + + qrcode_data = update_qrcode_data() + qrcode_url = qrcode_data["url"] + login_key = qrcode_data["qrcode_key"] + print(qrcode_terminal.qr_terminal_str(qrcode_url) + "\n") + while True: + events = login_with_key(login_key) + if events["code"] == 86101: + sys.stdout.write("\r 请扫描二维码↑") + sys.stdout.flush() + elif events["code"] == 86090: + sys.stdout.write("\r 点下确认啊!") + sys.stdout.flush() + elif events["code"] == 86038: + print("二维码过期,请扫新二维码!") + qrcode_data = update_qrcode_data() + qrcode_url = qrcode_data["url"] + print(qrcode_terminal.qr_terminal_str(qrcode_url) + "\n") + elif events["code"] == 0: + sys.stdout.write("\r 成功!") + sys.stdout.flush() + return parse_credential_url(events) + elif "code" in events.keys(): + raise LoginError(events["message"]) + time.sleep(0.5) + + +def login_with_key(key: str) -> dict: + params = {"qrcode_key": key} + events_api = API["qrcode"]["web"]["get_events"] + events = ( + Api(credential=credential, **events_api).update_params(**params).result_sync + ) + return events + + +# ---------------------------------------------------------------- +# TV 二维码登录 +# ---------------------------------------------------------------- + + +def app_signature(params: dict) -> dict: + # 这个 APP 签名应该是放在 network 才对的,但暂时没空做 APP 签名,先凑合,咕咕咕 + appkey = "4409e2ce8ffd12b8" + appsec = "59b43e04ad6965f34319062b478f83dd" + params["appkey"] = appkey + params = dict(sorted(params.items())) + params["sign"] = hashlib.md5( + (urllib.parse.urlencode(params) + appsec).encode("utf-8") + ).hexdigest() + return params + + +def update_tv_qrcode_data(): + api = API["qrcode"]["tv"]["get_qrcode_and_auth_code"] + data = app_signature( + { + "local_id": "0", + "ts": int(time.time()), + } + ) + qrcode_data = ( + Api(credential=credential, no_csrf=True, **api).update_data(**data).result_sync + ) + return qrcode_data + + +def verify_tv_login_status(auth_code: str) -> dict: + data = app_signature( + { + "auth_code": auth_code, + "ts": int(time.time()), + "local_id": "0", + } + ) + events_api = API["qrcode"]["tv"]["get_events"] + events = ( + Api(credential=credential, no_csrf=True, **events_api) + .update_data(**data) + .request_sync(raw=True) + ) + return events + + +def parse_tv_resp(events: dict) -> Credential: + cookies = {} + for cookie in events["cookie_info"]["cookies"]: + if cookie["name"] == "SESSDATA": + cookies["sessdata"] = cookie["value"] + elif cookie["name"] == "bili_jct": + cookies["bili_jct"] = cookie["value"] + elif cookie["name"] == "DedeUserID": + cookies["dedeuserid"] = cookie["value"] + + return Credential(**cookies) + + +def login_with_tv_qrcode_term() -> Credential: + """ + 终端扫描 TV 二维码登录 + + Args: + + Returns: + Credential: 凭据 + """ + import qrcode_terminal + + qrcode_data = update_tv_qrcode_data() + qrcode_url = qrcode_data["url"] + auth_code = qrcode_data["auth_code"] + print(qrcode_terminal.qr_terminal_str(qrcode_url) + "\n") + while True: + events = verify_tv_login_status(auth_code=auth_code) + if events["code"] == 86039: + sys.stdout.write("\r 请扫描二维码↑") + sys.stdout.flush() + # elif events["code"] == 86090: # 根本没捕捉到这个 code + # sys.stdout.write("\r 点下确认啊!") + # sys.stdout.flush() + elif events["code"] == 86038: + print("二维码过期,请扫新二维码!") + qrcode_data = update_tv_qrcode_data() + qrcode_url = qrcode_data["url"] + auth_code = qrcode_data["auth_code"] + print(qrcode_terminal.qr_terminal_str(qrcode_url) + "\n") + elif events["code"] == 0: + sys.stdout.write("\r 成功!") + sys.stdout.flush() + return parse_tv_resp(events["data"]) + elif "code" in events.keys(): + raise LoginError(events["message"]) + time.sleep(0.5) + + +# ---------------------------------------------------------------- +# 密码登录 +# ---------------------------------------------------------------- + + +def encrypt(_hash, key, password) -> str: + rsa_key = rsa.PublicKey.load_pkcs1_openssl_pem(key.encode("utf-8")) + data = str( + base64.b64encode(rsa.encrypt(bytes(_hash + password, "utf-8"), rsa_key)), + "utf-8", + ) + return data + + +def get_geetest() -> object: + if get_result() != -1: + return get_result() + thread = start_server() + if settings.geetest_auto_open: + webbrowser.open(thread.url) # type: ignore + try: + while True: + result = get_result() + if result != -1: + close_server() + return result + except KeyboardInterrupt: + close_server() + exit() + + +def login_with_password(username: str, password: str) -> Union[Credential, "Check"]: + """ + 密码登录。 + + Args: + username (str): 用户手机号、邮箱 + + password (str): 密码 + + Returns: + Union[Credential, Check]: 如果需要验证,会返回 `Check` 类,否则返回 `Credential` 类。 + """ + api_token = API["password"]["get_token"] + geetest_data = get_geetest() + sess = get_httpx_sync_session() + token_data = json.loads(sess.get(api_token["url"], headers=HEADERS).text) + hash_ = token_data["data"]["hash"] + key = token_data["data"]["key"] + final_password = encrypt(hash_, key, password) + login_api = API["password"]["login"] + data = { + "username": username, + "password": final_password, + "keep": True, + "token": geetest_data["token"], # type: ignore + "challenge": geetest_data["challenge"], # type: ignore + "validate": geetest_data["validate"], # type: ignore + "seccode": geetest_data["seccode"], # type: ignore + } + resp = sess.request( + "POST", + login_api["url"], + data=data, + headers={ + "Content-Type": "application/x-www-form-urlencoded", + "User-Agent": "Mozilla/5.0", + "Referer": "https://passport.bilibili.com/login", + }, + cookies={"buvid3": str(uuid.uuid1())}, + ) + login_data = resp.json() + if login_data["code"] == 0: + if login_data["data"]["status"] == 1: + return Check(login_data["data"]["url"]) + elif login_data["data"]["status"] == 2: + raise LoginError("需要手机号进一步验证码验证,请直接通过验证码登录") + return Credential( + sessdata=resp.cookies.get("SESSDATA"), + bili_jct=resp.cookies.get("bili_jct"), + dedeuserid=resp.cookies.get("DedeUserID"), + ac_time_value=login_data["data"]["refresh_token"], + ) + else: + raise LoginError(login_data["message"]) + + +# ---------------------------------------------------------------- +# 验证码登录 +# ---------------------------------------------------------------- + +captcha_id = None + + +def get_countries_list() -> List[Dict]: + """ + 获取国际地区代码列表 + + Returns: + List[dict]: 地区列表 + """ + with open( + os.path.join(os.path.dirname(__file__), "data/countries_codes.json"), + encoding="utf8", + ) as f: + codes_list = json.loads(f.read()) + countries = [] + for country in codes_list: + name = country["cname"] + id_ = country["country_id"] + code = country["id"] + countries.append({"name": name, "id": code, "code": int(id_)}) + return countries + + +def search_countries(keyword: str) -> List[Dict]: + """ + 搜索一个地区及其国际地区代码 + + Args: + keyword (str): 关键词 + + Returns: + List[dict]: 地区列表 + """ + list_ = get_countries_list() + countries = [] + for country in list_: + if keyword in country["name"] or keyword.lstrip("+") in country["code"]: + countries.append(country) + return countries + + +def have_country(keyword: str) -> bool: + """ + 是否有地区 + + Args: + keyword (str): 关键词 + + Returns: + bool: 是否存在 + """ + list_ = get_countries_list() + for country in list_: + if country["name"] == keyword: + return True + return False + + +def have_code(code: Union[str, int]) -> bool: + """ + 是否存在地区代码 + + Args: + code(Union[str, int]): 代码 + + Returns: + bool: 是否存在 + """ + list_ = get_countries_list() + if isinstance(code, str): + code = code.lstrip("+") + try: + int_code = int(code) + except ValueError: + raise ValueError("地区代码参数错误") + elif isinstance(code, int): + int_code = code + else: + return False + for country in list_: + if country["code"] == int_code: + return True + return False + + +def get_code_by_country(country: str) -> int: + """ + 获取地区对应代码 + + Args: + country(str): 地区名 + + Returns: + int: 对应的代码,没有返回 -1 + """ + list_ = get_countries_list() + for country_ in list_: + if country_["name"] == country: + return country_["code"] + return -1 + + +def get_id_by_code(code: int) -> int: + """ + 获取地区码对应的地区 id + + Args: + code(int): 地区吗 + + Returns: + int: 对应的代码,没有返回 -1 + """ + list_ = get_countries_list() + for country_ in list_: + if country_["code"] == code: + return country_["id"] + return -1 + + +class PhoneNumber: + """ + 手机号类 + """ + + def __init__(self, number: str, country: Union[str, int] = "+86"): + """ + Args: + number(str): 手机号 + + country(str): 地区/地区码,如 +86 + """ + number = number.replace("-", "") + if not have_country(country): # type: ignore + if not have_code(country): + raise ValueError("地区代码或地区名错误") + else: + code = country if isinstance(country, int) else int(country.lstrip("+")) + else: + code = get_code_by_country(country) # type: ignore + self.number = number + self.code = code + self.id_ = get_id_by_code(self.code) + + def __str__(self): + return f"+{self.code} {self.number} (bilibili 地区 id {self.id_})" + + +def send_sms(phonenumber: PhoneNumber) -> None: + """ + 发送验证码 + + Args: + phonenumber (PhoneNumber): 手机号类 + """ + global captcha_id + api = API["sms"]["send"] + code = phonenumber.code + tell = phonenumber.number + geetest_data = get_geetest() + sess = get_httpx_sync_session() + return_data = json.loads( + sess.post( + url=api["url"], + data=to_form_urlencoded( + { + "source": "main-fe-header", + "tel": tell, + "cid": code, + "validate": geetest_data["validate"], # type: ignore + "token": geetest_data["token"], # type: ignore + "seccode": geetest_data["seccode"], # type: ignore + "challenge": geetest_data["challenge"], # type: ignore + } + ), + headers={ + "User-Agent": "Mozilla/5.0", + "Referer": "https://www.bilibili.com", + "Content-Type": "application/x-www-form-urlencoded", + }, + cookies={"buvid3": "E9BAB99E-FE1E-981E-F772-958B7F572FF487330infoc"}, + ).text + ) + if return_data["code"] == 0: + captcha_id = return_data["data"]["captcha_key"] + else: + raise LoginError(return_data["message"]) + + +def login_with_sms(phonenumber: PhoneNumber, code: str) -> Credential: + """ + 验证码登录 + + Args: + phonenumber (str): 手机号类 + code (str): 验证码 + + Returns: + Credential: 凭据类 + """ + global captcha_id + sess = get_httpx_sync_session() + api = API["sms"]["login"] + if captcha_id == None: + raise LoginError("请申请或重新申请发送验证码") + return_data = json.loads( + sess.request( + "POST", + url=api["url"], + data={ + "tel": phonenumber.number, + "cid": phonenumber.code, + "code": code, + "source": "main_web", + "captcha_key": captcha_id, + "keep": "true", + }, + headers=HEADERS, + ).text + ) + # return_data["status"] 已改为 return_data["data"]["status"] + # {'code': 0, 'message': '0', 'ttl': 1, 'data': {'is_new': False, 'status': 0, 'message': '', 'url': '', 'hint': '登录成功', 'in_reg_audit': 0, 'refresh_token': '', 'timestamp': }} + if return_data["code"] == 0 and return_data["data"]["status"] != 5: + captcha_id = None + url = return_data["data"]["url"] + cookies_list = url.split("?")[1].split("&") + sessdata = "" + bili_jct = "" + dede = "" + for cookie in cookies_list: + if cookie[:8] == "SESSDATA": + sessdata = cookie[9:] + if cookie[:8] == "bili_jct": + bili_jct = cookie[9:] + if cookie[:11].upper() == "DEDEUSERID=": + dede = cookie[11:] + c = Credential( + sessdata=sessdata, + bili_jct=bili_jct, + dedeuserid=dede, + ac_time_value=return_data["data"]["refresh_token"], + ) + return c + elif return_data["data"]["status"] == 5: + return Check(return_data["data"]["url"]) # type: ignore + else: + raise LoginError(return_data["message"]) + + +# 验证类 + + +def get_safecenter_geetest() -> object: + if safecenter_get_result() != -1: + return safecenter_get_result() + thread = safecenter_start_server() + if settings.geetest_auto_open: + webbrowser.open(thread.url) # type: ignore + try: + while True: + result = safecenter_get_result() + if result != -1: + safecenter_close_server() + return result + except KeyboardInterrupt: + safecenter_close_server() + exit() + + +class Check: + """ + 验证类,如果密码登录需要验证会返回此类 + + Attributes: + check_url (str): 验证 url + tmp_token (str): 验证 token + """ + + def __init__(self, check_url): + self.check_url = check_url + self.yarl_url = URL(self.check_url) + self.tmp_token = self.yarl_url.query.get("tmp_token") + self.geetest_result = None + self.captcha_key = None + + def fetch_info(self) -> dict: + """ + 获取验证信息 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["safecenter"]["check_info"] + self.tmp_token = self.check_url.split("?")[1].split("&")[0][10:] + params = {"tmp_code": self.tmp_token} + return Api(credential=credential, **api, params=params).result_sync diff --git a/bilibili_api/login_func.py b/bilibili_api/login_func.py new file mode 100644 index 0000000000000000000000000000000000000000..bdf8893cfdbf69f8be6d052c96fc43cfe8657e78 --- /dev/null +++ b/bilibili_api/login_func.py @@ -0,0 +1,225 @@ +""" +bilibili_api.login_func + +登录功能相关 +""" + +import enum +import threading +from typing import Tuple, Union + +from . import login +from .utils.utils import get_api +from .exceptions import LoginError +import urllib.parse +from .utils.picture import Picture +from .utils.credential import Credential + +API = get_api("login") + + +class QrCodeLoginEvents(enum.Enum): + """ + 二维码登录状态枚举 + + + SCAN: 未扫描二维码 + + CONF: 未确认登录 + + TIMEOUT: 二维码过期 + + DONE: 成功 + """ + + SCAN = "scan" + CONF = "confirm" + TIMEOUT = "timeout" + DONE = "done" + + +def get_qrcode() -> Tuple[Picture, str]: + """ + 获取二维码及登录密钥(后面有用) + + Returns: + Tuple[Picture, str]: 第一项是二维码图片地址(本地缓存)和登录密钥。登录密钥需要保存。 + """ + login_data = login.update_qrcode_data() + login_key = login_data["qrcode_key"] + img = login.make_qrcode(login_data["url"]) + return (Picture.from_file(img), login_key) + + +def check_qrcode_events(login_key: str) -> Tuple[QrCodeLoginEvents, Union[str, Credential]]: + """ + 检查登录状态。(建议频率 1s,这个 API 也有风控!) + + Args: + login_key (str): 登录密钥(get_qrcode 的返回值第二项) + + Returns: + Tuple[QrCodeLoginEvents, str|Credential]: 状态(第一项)和信息(第二项)(如果成功登录信息为凭据类) + """ + events = login.login_with_key(login_key) + + if events["code"] == 86101: + return QrCodeLoginEvents.SCAN, events["message"] + elif events["code"] == 86090: + return QrCodeLoginEvents.CONF, events["message"] + elif events["code"] == 86038: + return QrCodeLoginEvents.TIMEOUT, events["message"] + elif events["code"] == 0: + url: str = events["data"]["url"] + cookies_list = url.split("?")[1].split("&") + sessdata = "" + bili_jct = "" + dede = "" + for cookie in cookies_list: + if cookie[:8] == "SESSDATA": + sessdata = cookie[9:] + if cookie[:8] == "bili_jct": + bili_jct = cookie[9:] + if cookie[:11].upper() == "DEDEUSERID=": + dede = cookie[11:] + c = Credential(sessdata, bili_jct, dedeuserid=dede) + return QrCodeLoginEvents.DONE, c + else: + raise LoginError(events["message"]) + + +def get_tv_qrcode() -> Tuple[Picture, str]: + """ + 获取 TV 端登录二维码及登录密钥(后面有用) + + Returns: + Tuple[Picture, str]: 第一项是二维码图片地址(本地缓存)和登录密钥。登录密钥需要保存。 + """ + qrcode_data = login.update_tv_qrcode_data() + qrcode_url = qrcode_data["url"] + auth_code = qrcode_data["auth_code"] + img = login.make_qrcode(qrcode_url) + return (Picture.from_file(img), auth_code) + + +def check_tv_qrcode_events(auth_code: str) -> Tuple[QrCodeLoginEvents, Union[str, Credential]]: + """ + 检查登录状态。 + + Args: + auth_code (str): 登录密钥 + + Returns: + Tuple[QrCodeLoginEvents, str|Credential]: 状态(第一项)和信息(第二项)(如果成功登录信息为凭据类) + """ + events = login.verify_tv_login_status(auth_code=auth_code) + + if events["code"] == 86039: + return QrCodeLoginEvents.SCAN, events["message"] + elif events["code"] == 86038: + return QrCodeLoginEvents.TIMEOUT, events["message"] + elif events["code"] == 0: + c = login.parse_tv_resp(events["data"]) + return QrCodeLoginEvents.DONE, c + else: + raise LoginError(events["message"]) + + +def start_geetest_server() -> "ServerThreadModel": + """ + 验证码服务打开服务器 + + Returns: + ServerThread: 服务进程,将自动开启 + + 返回值内函数及属性: + (继承:threading.Thread) + - url (str) : 验证码服务地址 + - start (Callable): 开启进程 + - stop (Callable): 结束进程 + + ``` python + print(start_geetest_server().url) + ``` + """ + return login.start_server() # type: ignore + + +def close_geetest_server() -> None: + """ + 关闭极验验证服务(打开极验验证服务后务必关闭掉它,否则会卡住) + """ + return login.close_server() + + +def done_geetest() -> bool: + """ + 检查是否完成了极验验证。 + + 如果没有完成极验验证码就开始短信登录发送短信,那么可能会让你的项目卡住。 + + Returns: + bool: 是否完成极验验证 + """ + result = login.get_result() + if result != -1: + return True + else: + return False + + +def safecenter_start_geetest_server() -> "ServerThreadModel": + """ + 登录验证专用函数:验证码服务打开服务器 + + Returns: + ServerThread: 服务进程,将自动开启 + + 返回值内函数及属性: + (继承:threading.Thread) + - url (str) : 验证码服务地址 + - start (Callable): 开启进程 + - stop (Callable): 结束进程 + + ``` python + print(start_geetest_server().url) + ``` + """ + return login.safecenter_start_server() # type: ignore + + +def safecenter_close_geetest_server() -> None: + """ + 登录验证专用函数:关闭极验验证服务(打开极验验证服务后务必关闭掉它,否则会卡住) + """ + return login.safecenter_close_server() + + +def safecenter_done_geetest() -> bool: + """ + 登录验证专用函数:检查是否完成了极验验证。 + + 如果没有完成极验验证码就开始短信登录发送短信,那么可能会让你的项目卡住。 + + Returns: + bool: 是否完成极验验证 + """ + result = login.safecenter_get_result() + if result != -1: + return True + else: + return False + + +COUNTRIES_LIST = login.get_countries_list() +countries_list = COUNTRIES_LIST + + +class ServerThreadModel(threading.Thread): + """ + A simple model for bilibili_api.utils.captcha._start_server.ServerThread. + """ + + url: str + + def __init__(self, *args, **kwargs): + ... + + def stop(self): + """Stop the server and this thread nicely""" diff --git a/bilibili_api/manga.py b/bilibili_api/manga.py new file mode 100644 index 0000000000000000000000000000000000000000..496fcfe28a9f9363d8653a4b18f9f628855c156b --- /dev/null +++ b/bilibili_api/manga.py @@ -0,0 +1,555 @@ +""" +bilibili_api.manga + +漫画相关操作 +""" + +import datetime +from enum import Enum +from urllib.parse import urlparse +from typing import Dict, List, Union, Optional + +import httpx + +from bilibili_api.utils.utils import get_api +from bilibili_api.errors import ArgsException +from bilibili_api.utils.picture import Picture +from bilibili_api.utils.credential import Credential +from bilibili_api.utils.network import HEADERS, Api + +API = get_api("manga") + + +class MangaIndexFilter: + """ + 漫画索引筛选器类。 + """ + + class Area(Enum): + """ + 漫画索引筛选器的地区枚举类。 + + - ALL: 全部 + - CHINA: 大陆 + - JAPAN: 日本 + - SOUTHKOREA: 韩国 + - OTHER: 其他 + """ + + ALL = -1 + CHINA = 1 + JAPAN = 2 + SOUTHKOREA = 6 + OTHER = 5 + + class Order(Enum): + """ + 漫画索引筛选器的排序枚举类。 + + - HOT: 人气推荐 + - UPDATE: 更新时间 + - RELEASE_DATE: 上架时间 + """ + + HOT = 0 + UPDATE = 1 + RELEASE_DATE = 3 + + class Status(Enum): + """ + 漫画索引筛选器的状态枚举类。 + + - ALL: 全部 + - FINISHED: 完结 + - UNFINISHED: 连载 + """ + + ALL = -1 + FINISHED = 1 + UNFINISHED = 0 + + class Payment(Enum): + """ + 漫画索引筛选器的付费枚举类。 + + - ALL: 全部 + - FREE: 免费 + - PAID: 付费 + - WILL_BE_FREE: 等就免费 + """ + + ALL = -1 + FREE = 1 + PAID = 2 + WILL_BE_FREE = 3 + + class Style(Enum): + """ + 漫画索引筛选器的风格枚举类。 + + - ALL: 全部 + - WARM: 热血 + - ANCIENT: 古风 + - FANTASY: 玄幻 + - IMAGING: 奇幻 + - SUSPENSE: 悬疑 + - CITY: 都市 + - HISTORY: 历史 + - WUXIA: 武侠仙侠 + - GAME: 游戏竞技 + - PARANORMAL: 悬疑灵异 + - ALTERNATE: 架空 + - YOUTH: 青春 + - WEST_MAGIC: 西幻 + - MORDEN: 现代 + - POSITIVE: 正能量 + - SCIENCE_FICTION: 科幻 + """ + + ALL = -1 + WARM = 999 + ANCIENT = 997 + FANTASY = 1016 + IMAGING = 998 + SUSPENSE = 1023 + CITY = 1002 + HISTORY = 1096 + WUXIA = 1092 + GAME = 1088 + PARANORMAL = 1081 + ALTERNATE = 1063 + YOUTH = 1060 + WEST_MAGIC = 1054 + MORDEN = 1048 + POSITIVE = 1028 + SCIENCE_FICTION = 1027 + + +class Manga: + """ + 漫画类 + + Attributes: + credential (Credential): 凭据类。 + """ + + def __init__(self, manga_id: int, credential: Optional[Credential] = None): + """ + Args: + manga_id (int) : 漫画 id + + credential (Credential | None): 凭据类. Defaults to None. + """ + credential = credential if credential else Credential() + self.__manga_id = manga_id + self.credential = credential + self.__info: Optional[Dict] = None + + def get_manga_id(self) -> int: + return self.__manga_id + + async def get_info(self) -> dict: + """ + 获取漫画信息 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["detail"] + params = {"comic_id": self.__manga_id} + return ( + await Api( + **api, + credential=self.credential, + no_csrf=( + False + if ( + self.credential.has_sessdata() + and self.credential.has_bili_jct() + ) + else True + ), + ) + .update_params(**params) + .result + ) + + async def __get_info_cached(self) -> dict: + """ + 获取漫画信息,如果有缓存则使用缓存。 + """ + if self.__info == None: + self.__info = await self.get_info() + return self.__info + + async def get_episode_info( + self, + episode_count: Optional[Union[int, float]] = None, + episode_id: Optional[int] = None, + ) -> dict: + """ + 获取某一话的详细信息 + + Args: + episode_count (int | float | None): 第几话. + + episode_id (int | None) : 对应的话的 id. 可以通过 `get_episode_id` 获取。 + + **注意:episode_count 和 episode_id 中必须提供一个参数。** + + Returns: + dict: 对应的话的详细信息 + """ + info = await self.__get_info_cached() + for ep in info["ep_list"]: + if episode_count == None: + if ep["id"] == episode_id: + return ep + elif episode_id == None: + if ep["ord"] == episode_count: + return ep + else: + raise ArgsException("episode_count 和 episode_id 中必须提供一个参数。") + raise ArgsException("未找到对应的话") + + async def get_episode_id( + self, episode_count: Optional[Union[int, float]] = None + ) -> int: + """ + 获取某一话的 id + + Args: + episode_count (int | float | None): 第几话. + + Returns: + int: 对应的话的 id + """ + return (await self.get_episode_info(episode_count=episode_count))["id"] + + async def get_images_url( + self, + episode_count: Optional[Union[int, float]] = None, + episode_id: Optional[int] = None, + ) -> dict: + """ + 获取某一话的图片链接。(未经过处理,所有的链接无法直接访问) + + 获取的图片 url 请传入 `manga.manga_image_url_turn_to_Picture` 函数以转换为 `Picture` 类。 + + Args: + episode_count (int | float | None): 第几话. + + episode_id (int | None) : 对应的话的 id. 可以通过 `get_episode_id` 获取。 + + **注意:episode_count 和 episode_id 中必须提供一个参数。** + + Returns: + dict: 调用 API 返回的结果 + """ + if episode_id == None: + if episode_count == None: + raise ArgsException("episode_count 和 episode_id 中必须提供一个参数。") + episode_id = await self.get_episode_id(episode_count) + api = API["info"]["episode_images"] + params = {"ep_id": episode_id} + return ( + await Api( + **api, + credential=self.credential, + no_csrf=( + False + if ( + self.credential.has_sessdata() + and self.credential.has_bili_jct() + ) + else True + ), + ) + .update_params(**params) + .result + ) + + async def get_images( + self, + episode_count: Optional[Union[int, float]] = None, + episode_id: Optional[int] = None, + ) -> List[Dict]: + """ + 获取某一话的所有图片 + + # 注意事项:此函数速度非常慢并且失败率高 + + Args: + episode_count (int | float | None): 第几话. + + episode_id (int | None) : 对应的话的 id. 可以通过 `get_episode_id` 获取。 + + **注意:episode_count 和 episode_id 中必须提供一个参数。** + + Returns: + List[Picture]: 所有的图片 + """ + data = await self.get_images_url( + episode_count=episode_count, episode_id=episode_id + ) + pictures: List[Dict] = [] + + async def get_real_image_url(url: str) -> str: + token_api = API["info"]["image_token"] + datas = {"urls": f'["{url}"]'} + token_data = ( + await Api( + **token_api, + credential=self.credential, + no_csrf=( + False + if ( + self.credential.has_sessdata() + and self.credential.has_bili_jct() + ) + else True + ), + ) + .update_data(**datas) + .result + ) + return token_data[0]["url"] + "?token=" + token_data[0]["token"] + + for img in data["images"]: + url = await get_real_image_url(img["path"]) + pictures.append( + { + "x": img["x"], + "y": img["y"], + "picture": Picture.from_content( + (await httpx.AsyncClient().get(url, headers=HEADERS)).content, + "jpg", + ), + } + ) + return pictures + + +async def manga_image_url_turn_to_Picture( + url: str, credential: Optional[Credential] = None +) -> Picture: + """ + 将 Manga.get_images_url 函数获得的图片 url 转换为 Picture 类。 + + Args: + url (str) : 未经处理的漫画图片链接。 + + credential (Credential | None): 凭据类. Defaults to None. + + Returns: + Picture: 图片类。 + """ + url = urlparse(url).path + credential = credential if credential else Credential() + + async def get_real_image_url(url: str) -> str: + token_api = API["info"]["image_token"] + datas = {"urls": f'["{url}"]'} + token_data = ( + await Api( + **token_api, + credential=credential, + no_csrf=( + False + if (credential.has_sessdata() and credential.has_bili_jct()) + else True + ), + ) + .update_data(**datas) + .result + ) + return f'{token_data[0]["url"]}?token={token_data[0]["token"]}' + + url = await get_real_image_url(url) + return await Picture.async_load_url(url) + + +async def set_follow_manga( + manga: Manga, status: bool = True, credential: Optional[Credential] = None +) -> dict: + """ + 设置追漫 + + Args: + manga (Manga) : 漫画类。 + + status (bool) : 设置是否追漫。是为 True,否为 False。Defaults to True. + + credential (Credential): 凭据类。 + """ + if credential == None: + if manga.credential.has_sessdata() and manga.credential.has_bili_jct(): + credential = manga.credential + else: + credential = Credential() + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + if status == True: + api = API["operate"]["add_favorite"] + else: + api = API["operate"]["del_favorite"] + data = {"comic_ids": str(manga.get_manga_id())} + return await Api(**api, credential=credential).update_data(**data).result + + +async def get_raw_manga_index( + area: MangaIndexFilter.Area = MangaIndexFilter.Area.ALL, + order: MangaIndexFilter.Order = MangaIndexFilter.Order.HOT, + status: MangaIndexFilter.Status = MangaIndexFilter.Status.ALL, + payment: MangaIndexFilter.Payment = MangaIndexFilter.Payment.ALL, + style: MangaIndexFilter.Style = MangaIndexFilter.Style.ALL, + pn: int = 1, + ps: int = 18, + credential: Credential = None, +) -> list: + """ + 获取漫画索引 + + Args: + area (MangaIndexFilter.Area) : 地区。Defaults to MangaIndexFilter.Area.ALL. + + order (MangaIndexFilter.Order) : 排序。Defaults to MangaIndexFilter.Order.HOT. + + status (MangaIndexFilter.Status) : 状态。Defaults to MangaIndexFilter.Status.ALL. + + payment (MangaIndexFilter.Payment): 支付。Defaults to MangaIndexFilter.Payment.ALL. + + style (MangaIndexFilter.Style) : 风格。Defaults to MangaIndexFilter.Style.ALL. + + pn (int) : 页码。Defaults to 1. + + ps (int) : 每页数量。Defaults to 18. + + credential (Credential) : 凭据类. Defaults to None. + + Returns: + list: 调用 API 返回的结果 + """ + credential = credential if credential else Credential() + api = API["info"]["index"] + params = {"device": "pc", "platform": "web"} + data = { + "area_id": area.value, + "order": order.value, + "is_finish": status.value, + "is_free": payment.value, + "style_id": style.value, + "page_num": pn, + "page_size": ps, + } + return ( + await Api(**api, credential=credential, no_csrf=True) + .update_data(**data) + .update_params(**params) + .result + ) + + +async def get_manga_index( + area: MangaIndexFilter.Area = MangaIndexFilter.Area.ALL, + order: MangaIndexFilter.Order = MangaIndexFilter.Order.HOT, + status: MangaIndexFilter.Status = MangaIndexFilter.Status.ALL, + payment: MangaIndexFilter.Payment = MangaIndexFilter.Payment.ALL, + style: MangaIndexFilter.Style = MangaIndexFilter.Style.ALL, + pn: int = 1, + ps: int = 18, + credential: Credential = None, +) -> List[Manga]: + """ + 获取漫画索引 + + Args: + + area (MangaIndexFilter.Area) : 地区。Defaults to MangaIndexFilter.Area.ALL. + + order (MangaIndexFilter.Order) : 排序。Defaults to MangaIndexFilter.Order.HOT. + + status (MangaIndexFilter.Status) : 状态。Defaults to MangaIndexFilter.Status.ALL. + + payment (MangaIndexFilter.Payment): 支付。Defaults to MangaIndexFilter.Payment.ALL. + + style (MangaIndexFilter.Style) : 风格。Defaults to MangaIndexFilter.Style.ALL. + + pn (int) : 页码。Defaults to 1. + + ps (int) : 每页数量。Defaults to 18. + + credential (Credential) : 凭据类. Defaults to None. + + Returns: + List[Manga]: 漫画索引 + """ + data = await get_raw_manga_index( + area, order, status, payment, style, pn, ps, credential + ) + return [Manga(manga_data["season_id"]) for manga_data in data] + + +async def get_manga_update( + date: Union[str, datetime.datetime] = datetime.datetime.now(), + pn: int = 1, + ps: int = 8, + credential: Credential = None, +) -> List[Manga]: + """ + 获取更新推荐的漫画 + + Args: + date (Union[str, datetime.datetime]): 日期,默认为今日。 + + pn (int) : 页码。Defaults to 1. + + ps (int) : 每页数量。Defaults to 8. + + credential (Credential) : 凭据类. Defaults to None. + + Returns: + List[Manga]: 漫画列表 + """ + credential = credential if credential else Credential() + api = API["info"]["update"] + params = {"device": "pc", "platform": "web"} + if isinstance(date, datetime.datetime): + date = date.strftime("%Y-%m-%d") + data = {"date": date, "page_num": pn, "page_size": ps} + manga_data = ( + await Api(**api, credential=credential, no_csrf=True) + .update_data(**data) + .update_params(**params) + .result + ) + return [Manga(manga["comic_id"]) for manga in manga_data["list"]] + + +async def get_manga_home_recommend( + pn: int = 1, seed: Optional[str] = "0", credential: Credential = None +) -> List[Manga]: + """ + 获取首页推荐的漫画 + + Args: + pn (int) : 页码。Defaults to 1. + + seed (Optional[str]) : Unknown param,无需传入. + + credential (Credential) : 凭据类. Defaults to None. + + Returns: + List[Manga]: 漫画列表 + """ + credential = credential if credential else Credential() + api = API["info"]["home_recommend"] + params = {"device": "pc", "platform": "web"} + data = {"page_num": pn, "seed": seed} + manga_data = ( + await Api(**api, credential=credential, no_csrf=True) + .update_data(**data) + .update_params(**params) + .result + ) + return [Manga(manga["comic_id"]) for manga in manga_data["list"]] diff --git a/bilibili_api/music.py b/bilibili_api/music.py new file mode 100644 index 0000000000000000000000000000000000000000..2d959e81bf3818fc2051e097363405bf689d2319 --- /dev/null +++ b/bilibili_api/music.py @@ -0,0 +1,199 @@ +""" +bilibili_api.music + +音乐相关 API + +注意: 目前 B 站的音频并不和 B 站的音乐相关信息互通。这里的 Music 类的数据来源于视频下面的 bgm 标签和全站音乐榜中的每一个 bgm/音乐。get_homepage_recommend 和 get_music_index_info 来源于 https://www.bilibili.com/v/musicplus/ +""" + +from enum import Enum +from typing import Optional + +from .utils.utils import get_api +from .utils.credential import Credential +from .utils.network import Api + +API_audio = get_api("audio") +API = get_api("music") + + +class MusicOrder(Enum): + """ + 音乐排序类型 + + + NEW: 最新 + + HOT: 最热 + """ + + NEW = 1 + HOT = 2 + + +class MusicIndexTags: + """ + 音乐索引信息查找可以用的标签,有语言和类型两种标签,每种标签选一个 + + - Lang: 语言标签枚举类 + - Genre: 类型标签枚举类 + """ + + class Lang(Enum): + """ + - ALL: 全部 + - CHINESE: 华语 + - EUROPE_AMERICA: 欧美 + - JAPAN: 日语 + - KOREA: 韩语 + - OTHER: 其他 + """ + + ALL = "" + CHINESE = 3 + EUROPE_AMERICA = 6 + JAPAN = 7 + KOREA = 61 + OTHER = 1 + + class Genre(Enum): + """ + - ALL: 全部 + - POPULAR: 流行 + - ROCK: 摇滚 + - ELECTRONIC: 电子音乐 + - COUNTRYSIDE: 乡村 + - FOLK: 民谣 + - LIVE: 轻音乐 + - CLASSICAL: 古典 + - NEW_CENTURY: 新世纪 + - REGGAE: 雷鬼 + - BLUES: 布鲁斯 + - RHYTHM_BLUES: 节奏与布鲁斯 + - ORIGINAL: 原声 + - WORLD: 世界音乐 + - CHILDREN: 儿童音乐 + - LATIN: 拉丁 + - PUNK: 朋克 + - MEDAL: 金属 + - JAZZ: 爵士乐 + - HIP_HOP: 嘻哈 + - SINGER_SONGWRITER: 唱作人 + - AMUSEMENT: 娱乐/舞台 + - OTHER: 其他 + """ + + ALL = "" + POPULAR = 1 + ROCK = 2 + ELECTRONIC = 3 + COUNTRYSIDE = 4 + FOLK = 5 + LIVE = 6 + CLASSICAL = 7 + NEW_CENTURY = 8 + REGGAE = 9 + BLUES = 10 + RHYTHM_BLUES = 12 + ORIGINAL = 13 + WORLD = 14 + CHILDREN = 15 + LATIN = 16 + PUNK = 17 + MEDAL = 18 + JAZZ = 19 + HIP_HOP = 20 + SINGER_SONGWRITER = 21 + AMUSEMENT = 22 + OTHER = 23 + + +async def get_homepage_recommend(credential: Optional[Credential] = None): + """ + 获取音频首页推荐 + + Args: + credential (Credential | None): 凭据类. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + credential = credential if credential else Credential() + api = API_audio["audio_info"]["homepage_recommend"] + return await Api(**api, credential=credential).result + + +async def get_music_index_info( + keyword: str = "", + lang: MusicIndexTags.Lang = MusicIndexTags.Lang.ALL, + genre: MusicIndexTags.Genre = MusicIndexTags.Genre.ALL, + order: MusicOrder = MusicOrder.NEW, + page_num: int = 1, + page_size: int = 10, +) -> dict: + """ + 获取首页的音乐视频列表 + + Args: + keyword (str) : 关键词. Defaults to None. + + lang (MusicIndexTags.Lang) : 语言. Defaults to MusicIndexTags.Lang.ALL + + genre (MusicIndexTags.Genre): 类型. Defaults to MusicIndexTags.Genre.ALL + + order (MusicOrder) : 排序方式. Defaults to OrderAudio.NEW + + page_num (int) : 页码. Defaults to 1. + + page_size (int) : 每页的数据大小. Defaults to 10. + """ + api = API_audio["audio_info"]["audio_list"] + params = { + "type": order.value, + "lang": lang.value, + "genre": genre.value, + "keyword": keyword, + "pn": page_num, + "ps": page_size, + } + return await Api(**api).update_params(**params).result + + +class Music: + """ + 音乐类。 + + 此处的“音乐”定义:部分视频的标签中有里面出现过的音乐的标签, 可以点击音乐标签查看音乐信息。此类将提供查询音乐信息的接口。 + + 其中音乐的 ID 为 `video.get_tags` 返回值数据中的 `music_id` 键值 + """ + + def __init__(self, music_id: str): + """ + Args: + music_id (str): 音乐 id,例如 MA436038343856245020 + """ + self.__music_id = music_id + + def get_music_id(self): + return self.__music_id + + async def get_info(self): + """ + 获取音乐信息 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["detail"] + params = {"music_id": self.__music_id} + return await Api(**api).update_params(**params).result + + async def get_music_videos(self): + """ + 获取音乐的音乐视频 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["video_recommend_list"] + params = {"music_id": self.__music_id} + return await Api(**api).update_params(**params).result diff --git a/bilibili_api/note.py b/bilibili_api/note.py new file mode 100644 index 0000000000000000000000000000000000000000..cc3beb676447f2d91db296d84a49026c73ff16f4 --- /dev/null +++ b/bilibili_api/note.py @@ -0,0 +1,757 @@ +""" +bilibili_api.note + +笔记相关 +""" + +import re +import json +from enum import Enum +from html import unescape +from typing import List, Union, overload + +import yaml +import httpx +from yarl import URL + +from .utils.initial_state import get_initial_state + +from .utils.utils import get_api, raise_for_statement +from .utils.picture import Picture +from .utils.credential import Credential +from .exceptions import ApiException, ArgsException +from .utils.network import Api, get_session +from .video import get_cid_info_sync +from . import article + +API = get_api("note") +API_ARTICLE = get_api("article") + + +class NoteType(Enum): + PUBLIC = "public" + PRIVATE = "private" + + +class Note: + """ + 笔记相关 + """ + + def __init__( + self, + cvid: Union[int, None] = None, + aid: Union[int, None] = None, + note_id: Union[int, None] = None, + note_type: NoteType = NoteType.PUBLIC, + credential: Union[Credential, None] = None, + ): + """ + Args: + cvid (int) : 公开笔记 ID (对应专栏的 cvid) (公开笔记必要) + + aid (int) : 稿件 ID(oid_type 为 0 时是 avid) (私有笔记必要) + + note_id (int) : 私有笔记 ID (私有笔记必要) + + note_type (str) : 笔记类型 (private, public) + + credential (Credential, optional) : Credential. Defaults to None. + """ + self.__oid = -1 + self.__note_id = -1 + self.__cvid = -1 + # ID 和 type 检查 + if note_type == NoteType.PRIVATE: + if not aid or not note_id: + raise ArgsException("私有笔记需要 oid 和 note_id") + self.__oid = aid + self.__note_id = note_id + elif note_type == NoteType.PUBLIC: + if not cvid: + raise ArgsException("公开笔记需要 cvid") + self.__cvid = cvid + else: + raise ArgsException("type_ 只能是 public 或 private") + + self.__type = note_type + + # 未提供 credential 时初始化该类 + # 私有笔记需要 credential + self.credential: Credential = Credential() if credential is None else credential + + # 用于存储视频信息,避免接口依赖视频信息时重复调用 + self.__info: Union[dict, None] = None + + # 用于存储正文的节点 + self.__children: List[Node] = [] + # 用于存储是否解析 + self.__has_parsed: bool = False + # 用于存储转换为 markdown 和 json 时使用的信息 + self.__meta: dict = {} + + def get_cvid(self) -> int: + return self.__cvid + + def get_aid(self) -> int: + return self.__oid + + def get_note_id(self) -> int: + return self.__note_id + + def turn_to_article(self) -> "article.Article": + """ + 将笔记类转为专栏类。需要保证笔记是公开笔记。 + + Returns: + Note: 专栏类 + """ + raise_for_statement(self.__type == NoteType.PUBLIC) + return article.Article(cvid=self.get_cvid(), credential=self.credential) + + async def get_info(self) -> dict: + """ + 获取笔记信息 + + Returns: + dict: 笔记信息 + """ + if self.__type == NoteType.PRIVATE: + return await self.get_private_note_info() + else: + return await self.get_public_note_info() + + async def __get_info_cached(self) -> dict: + """ + 获取视频信息,如果已获取过则使用之前获取的信息,没有则重新获取。 + + Returns: + dict: 调用 API 返回的结果。 + """ + if self.__info is None: + return await self.get_info() + return self.__info + + async def get_private_note_info(self) -> dict: + """ + 获取私有笔记信息。 + + Returns: + dict: 调用 API 返回的结果。 + """ + raise_for_statement(self.__type == NoteType.PRIVATE) + + api = API["private"]["detail"] + # oid 为 0 时指 avid + params = {"oid": self.get_aid(), "note_id": self.get_note_id(), "oid_type": 0} + resp = ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + # 存入 self.__info 中以备后续调用 + self.__info = resp + return resp + + async def get_public_note_info(self) -> dict: + """ + 获取公有笔记信息。 + + Returns: + dict: 调用 API 返回的结果。 + """ + + raise_for_statement(self.__type == NoteType.PUBLIC) + + api = API["public"]["detail"] + params = {"cvid": self.get_cvid()} + resp = ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + # 存入 self.__info 中以备后续调用 + self.__info = resp + return resp + + async def get_images_raw_info(self) -> List["dict"]: + """ + 获取笔记所有图片原始信息 + + Returns: + list: 图片信息 + """ + + result = [] + content = (await self.__get_info_cached())["content"] + for line in content: + if type(line["insert"]) == dict: + if "imageUpload" in line["insert"]: + img_info = line["insert"]["imageUpload"] + result.append(img_info) + return result + + async def get_images(self) -> List["Picture"]: + """ + 获取笔记所有图片并转为 Picture 类 + + Returns: + list: 图片信息 + """ + + result = [] + images_raw_info = await self.get_images_raw_info() + for image in images_raw_info: + result.append(Picture().from_url(url=f'https:{image["url"]}')) + return result + + async def get_all(self) -> dict: + """ + (仅供公开笔记) + + 一次性获取专栏尽可能详细数据,包括原始内容、标签、发布时间、标题、相关专栏推荐等 + + Returns: + dict: 调用 API 返回的结果 + """ + raise_for_statement(self.__type == NoteType.PUBLIC) + return await get_initial_state(f"https://www.bilibili.com/read/cv{self.__cvid}") + + async def set_like(self, status: bool = True) -> dict: + """ + (仅供公开笔记) + + 设置专栏点赞状态 + + Args: + status (bool, optional): 点赞状态. Defaults to True + + Returns: + dict: 调用 API 返回的结果 + """ + raise_for_statement(self.__type == NoteType.PUBLIC) + + self.credential.raise_for_no_sessdata() + + api = API_ARTICLE["operate"]["like"] + data = {"id": self.__cvid, "type": 1 if status else 2} + return await Api(**api, credential=self.credential).update_data(**data).result + + async def set_favorite(self, status: bool = True) -> dict: + """ + (仅供公开笔记) + + 设置专栏收藏状态 + + Args: + status (bool, optional): 收藏状态. Defaults to True + + Returns: + dict: 调用 API 返回的结果 + """ + raise_for_statement(self.__type == NoteType.PUBLIC) + + self.credential.raise_for_no_sessdata() + + api = ( + API_ARTICLE["operate"]["add_favorite"] + if status + else API_ARTICLE["operate"]["del_favorite"] + ) + + data = {"id": self.__cvid} + return await Api(**api, credential=self.credential).update_data(**data).result + + async def add_coins(self) -> dict: + """ + (仅供公开笔记) + + 给笔记投币,目前只能投一个。 + + Returns: + dict: 调用 API 返回的结果 + """ + raise_for_statement(self.__type == NoteType.PUBLIC) + + self.credential.raise_for_no_sessdata() + + upid = (await self.get_info())["mid"] + api = API_ARTICLE["operate"]["coin"] + data = {"aid": self.__cvid, "multiply": 1, "upid": upid, "avtype": 2} + return await Api(**api, credential=self.credential).update_data(**data).result + + async def fetch_content(self) -> None: + """ + 获取并解析笔记内容 + + 该返回不会返回任何值,调用该方法后请再调用 `self.markdown()` 或 `self.json()` 来获取你需要的值。 + """ + + def parse_note(data: List[dict]): + for field in data: + if not isinstance(field["insert"], str): + if "tag" in field["insert"].keys(): + node = VideoCardNode() + node.aid = get_cid_info_sync(field["insert"]["tag"]["cid"])[ + "cid" + ] + self.__children.append(node) + elif "imageUpload" in field["insert"].keys(): + node = ImageNode() + node.url = field["insert"]["imageUpload"]["url"] + self.__children.append(node) + elif "cut-off" in field["insert"].keys(): + node = ImageNode() + node.url = field["insert"]["cut-off"]["url"] + self.__children.append(node) + else: + raise Exception() + else: + node = TextNode(field["insert"]) + if "attributes" in field.keys(): + if field["attributes"].get("bold") == True: + bold = BoldNode() + bold.children = [node] + node = bold + if field["attributes"].get("strike") == True: + delete = DelNode() + delete.children = [node] + node = delete + if field["attributes"].get("underline") == True: + underline = UnderlineNode() + underline.children = [node] + node = underline + if field["attributes"].get("background") == True: + # FIXME: 暂不支持背景颜色 + pass + if field["attributes"].get("color") != None: + color = ColorNode() + color.color = field["attributes"]["color"].replace("#", "") + color.children = [node] + node = color + if field["attributes"].get("size") != None: + size = FontSizeNode() + size.size = field["attributes"]["size"] + size.children = [node] + node = size + else: + pass + self.__children.append(node) + + info = await self.get_info() + content = info["content"] + content = unescape(content) + parse_note(json.loads(content)) + self.__has_parsed = True + self.__meta = await self.__get_info_cached() + del self.__meta["content"] + + def markdown(self) -> str: + """ + 转换为 Markdown + + 请先调用 fetch_content() + + Returns: + str: Markdown 内容 + """ + if not self.__has_parsed: + raise ApiException("请先调用 fetch_content()") + + content = "" + + for node in self.__children: + try: + markdown_text = node.markdown() + except Exception as e: + pass + else: + content += markdown_text + + meta_yaml = yaml.safe_dump(self.__meta, allow_unicode=True) + content = f"---\n{meta_yaml}\n---\n\n{content}" + return content + + def json(self) -> dict: + """ + 转换为 JSON 数据 + + 请先调用 fetch_content() + + Returns: + dict: JSON 数据 + """ + if not self.__has_parsed: + raise ApiException("请先调用 fetch_content()") + + return { + "type": "Note", + "meta": self.__meta, + "children": list(map(lambda x: x.json(), self.__children)), + } + + # TODO: 笔记上传/编辑/删除 + + +class Node: + def __init__(self): + pass + + @overload + def markdown(self) -> str: # type: ignore + pass + + @overload + def json(self) -> dict: # type: ignore + pass + + +class ParagraphNode(Node): + def __init__(self): + self.children = [] + self.align = "left" + + def markdown(self): + content = "".join([node.markdown() for node in self.children]) + return content + "\n\n" + + def json(self): + return { + "type": "ParagraphNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class HeadingNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + text = "".join([node.markdown() for node in self.children]) + if len(text) == 0: + return "" + return f"## {text}\n\n" + + def json(self): + return { + "type": "HeadingNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class BlockquoteNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + t = "".join([node.markdown() for node in self.children]) + # 填补空白行的 > 并加上标识符 + t = "\n".join(["> " + line for line in t.split("\n")]) + "\n\n" + + return t + + def json(self): + return { + "type": "BlockquoteNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class ItalicNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + text = "".join([node.markdown() for node in self.children]) + if len(text) == 0: + return "" + return f" *{text}*" + + def json(self): + return { + "type": "ItalicNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class BoldNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + t = "".join([node.markdown() for node in self.children]) + if len(t) == 0: + return "" + return f" **{t.lstrip().rstrip()}** " + + def json(self): + return { + "type": "BoldNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class DelNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + text = "".join([node.markdown() for node in self.children]) + if len(text) == 0: + return "" + return f" ~~{text}~~" + + def json(self): + return { + "type": "DelNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class UnderlineNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + text = "".join([node.markdown() for node in self.children]) + if len(text) == 0: + return "" + return " $\\underline{" + text + "}$ " + + +class UlNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + return "\n".join(["- " + node.markdown() for node in self.children]) + + def json(self): + return { + "type": "UlNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class OlNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + t = [] + for i, node in enumerate(self.children): + t.append(f"{i + 1}. {node.markdown()}") + return "\n".join(t) + + def json(self): + return { + "type": "OlNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class LiNode(Node): + def __init__(self): + self.children = [] + + def markdown(self): + return "".join([node.markdown() for node in self.children]) + + def json(self): + return { + "type": "LiNode", + "children": list(map(lambda x: x.json(), self.children)), + } + + +class ColorNode(Node): + def __init__(self): + self.color = "000000" + self.children = [] + + def markdown(self): + return "".join([node.markdown() for node in self.children]) + + def json(self): + return { + "type": "ColorNode", + "color": self.color, + "children": list(map(lambda x: x.json(), self.children)), + } + + +class FontSizeNode(Node): + def __init__(self): + self.size = 16 + self.children = [] + + def markdown(self): + return "".join([node.markdown() for node in self.children]) + + def json(self): + return { + "type": "FontSizeNode", + "size": self.size, + "children": list(map(lambda x: x.json(), self.children)), + } + + +# 特殊节点,即无子节点 + + +class TextNode(Node): + def __init__(self, text: str): + self.text = text + + def markdown(self): + return self.text + + def json(self): + return {"type": "TextNode", "text": self.text} + + +class ImageNode(Node): + def __init__(self): + self.url = "" + self.alt = "" + + def markdown(self): + if URL(self.url).scheme == "": + self.url = "https:" + self.url + alt = self.alt.replace("[", "\\[") + return f"![{alt}]({self.url})\n\n" + + def json(self): + if URL(self.url).scheme == "": + self.url = "https:" + self.url + return {"type": "ImageNode", "url": self.url, "alt": self.alt} + + +class LatexNode(Node): + def __init__(self): + self.code = "" + + def markdown(self): + if "\n" in self.code: + # 块级公式 + return f"$$\n{self.code}\n$$" + else: + # 行内公式 + return f"${self.code}$" + + def json(self): + return {"type": "LatexNode", "code": self.code} + + +class CodeNode(Node): + def __init__(self): + self.code = "" + self.lang = "" + + def markdown(self): + return f"```{self.lang if self.lang else ''}\n{self.code}\n```\n\n" + + def json(self): + return {"type": "CodeNode", "code": self.code, "lang": self.lang} + + +# 卡片 + + +class VideoCardNode(Node): + def __init__(self): + self.aid = 0 + + def markdown(self): + return f"[视频 av{self.aid}](https://www.bilibili.com/av{self.aid})\n\n" + + def json(self): + return {"type": "VideoCardNode", "aid": self.aid} + + +class ArticleCardNode(Node): + def __init__(self): + self.cvid = 0 + + def markdown(self): + return f"[文章 cv{self.cvid}](https://www.bilibili.com/read/cv{self.cvid})\n\n" + + def json(self): + return {"type": "ArticleCardNode", "cvid": self.cvid} + + +class BangumiCardNode(Node): + def __init__(self): + self.epid = 0 + + def markdown(self): + return f"[番剧 ep{self.epid}](https://www.bilibili.com/bangumi/play/ep{self.epid})\n\n" + + def json(self): + return {"type": "BangumiCardNode", "epid": self.epid} + + +class MusicCardNode(Node): + def __init__(self): + self.auid = 0 + + def markdown(self): + return f"[音乐 au{self.auid}](https://www.bilibili.com/audio/au{self.auid})\n\n" + + def json(self): + return {"type": "MusicCardNode", "auid": self.auid} + + +class ShopCardNode(Node): + def __init__(self): + self.pwid = 0 + + def markdown(self): + return f"[会员购 {self.pwid}](https://show.bilibili.com/platform/detail.html?id={self.pwid})\n\n" + + def json(self): + return {"type": "ShopCardNode", "pwid": self.pwid} + + +class ComicCardNode(Node): + def __init__(self): + self.mcid = 0 + + def markdown(self): + return ( + f"[漫画 mc{self.mcid}](https://manga.bilibili.com/m/detail/mc{self.mcid})\n\n" + ) + + def json(self): + return {"type": "ComicCardNode", "mcid": self.mcid} + + +class LiveCardNode(Node): + def __init__(self): + self.room_id = 0 + + def markdown(self): + return f"[直播 {self.room_id}](https://live.bilibili.com/{self.room_id})\n\n" + + def json(self): + return {"type": "LiveCardNode", "room_id": self.room_id} + + +class AnchorNode(Node): + def __init__(self): + self.url = "" + self.text = "" + + def markdown(self): + text = self.text.replace("[", "\\[") + return f"[{text}]({self.url})" + + def json(self): + return {"type": "AnchorNode", "url": self.url, "text": self.text} + + +class SeparatorNode(Node): + def __init__(self): + pass + + def markdown(self): + return "\n------\n" + + def json(self): + return {"type": "SeparatorNode"} diff --git a/bilibili_api/opus.py b/bilibili_api/opus.py new file mode 100644 index 0000000000000000000000000000000000000000..62b7bd14da1b97b88db3e2ab6b133f7f897d0b07 --- /dev/null +++ b/bilibili_api/opus.py @@ -0,0 +1,130 @@ +""" +bilibili_api.opus + +图文相关 +""" + +import enum +import yaml +from typing import Optional +from . import article +from . import dynamic +from .utils.credential import Credential +from .utils.network import Api +from .utils.utils import get_api, raise_for_statement +from .utils import cache_pool + + +API = get_api("opus") + + +class OpusType(enum.Enum): + """ + 图文类型 + + + ARTICLE: 专栏 + + DYNAMIC: 动态 + """ + + ARTICLE = 1 + DYNAMIC = 0 + + +class Opus: + """ + 图文类。 + + Attributes: + credential (Credential): 凭据类 + """ + + def __init__(self, opus_id: int, credential: Optional[Credential] = None): + self.__id = opus_id + self.credential = credential if credential else Credential() + + if cache_pool.opus_type.get(self.__id): + self.__info = cache_pool.opus_info[self.__id] + self.__type = OpusType(cache_pool.opus_type[self.__id]) + else: + api = API["info"]["detail"] + params = {"timezone_offset": -480, "id": self.__id} + self.__info = ( + Api(**api, credential=self.credential) + .update_params(**params) + .result_sync + )["item"] + self.__type = OpusType(self.__info["type"]) + cache_pool.opus_info[self.__id] = self.__info + cache_pool.opus_type[self.__id] = self.__type.value + + def get_opus_id(self): + return self.__id + + def get_type(self): + """ + 获取图文类型(专栏/动态) + + Returns: + OpusType: 图文类型 + """ + return self.__type + + def turn_to_article(self) -> "article.Article": + """ + 对专栏图文,转换为专栏 + """ + raise_for_statement(self.__type == OpusType.ARTICLE, "仅支持专栏图文") + cvid = int(self.__info["basic"]["rid_str"]) + cache_pool.article_is_opus[cvid] = 1 + cache_pool.article_dyn_id[cvid] = self.__id + return article.Article(cvid=cvid, credential=self.credential) + + def turn_to_dynamic(self) -> "dynamic.Dynamic": + """ + 转为动态 + """ + cache_pool.dynamic_is_opus[self.__id] = 1 + return dynamic.Dynamic(dynamic_id=self.__id, credential=self.credential) + + async def get_info(self): + """ + 获取图文基本信息 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["detail"] + params = {"timezone_offset": -480, "id": self.__id} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + def markdown(self) -> str: + """ + 将图文转为 markdown + + Returns: + str: markdown 内容 + """ + title, author, content = self.__info["modules"][:3] + + markdown = f'# {title["module_title"]["text"]}\n\n' + + for para in content["module_content"]["paragraphs"]: + para_raw = "" + if para["para_type"] == 1: + for node in para["text"]["nodes"]: + raw = node["word"]["words"] + if node["word"].get("style"): + if node["word"]["style"].get("bold"): + if node["word"]["style"]["bold"]: + raw = f" **{raw}**" + para_raw += raw + else: + for pic in para["pic"]["pics"]: + para_raw += f'![]({pic["url"]})\n' + markdown += f"{para_raw}\n\n" + + meta_yaml = yaml.safe_dump(self.__info, allow_unicode=True) + content = f"---\n{meta_yaml}\n---\n\n{markdown}" + return content diff --git a/bilibili_api/py.typed b/bilibili_api/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bilibili_api/rank.py b/bilibili_api/rank.py new file mode 100644 index 0000000000000000000000000000000000000000..0e225acb338496f7abcafbc4c1aa64217e60d0dc --- /dev/null +++ b/bilibili_api/rank.py @@ -0,0 +1,418 @@ +""" +bilibili_api.rank + +和哔哩哔哩视频排行榜相关的 API +""" + +from enum import Enum +from typing import Union + +from .utils.utils import get_api +from .utils.credential import Credential +from .utils.network import Api + +API = get_api("rank") + + +class RankAPIType(Enum): + """ + 排行榜 API 接口类型 + + - PGC: https://api.bilibili.com/pgc/web/rank/list + - V2: https://api.bilibili.com/x/web-interface/ranking/v2 + """ + + PGC = "pgc" + V2 = "x" + + +class RankDayType(Enum): + """ + RankAPIType.PGC 排行榜时间类型 + + - THREE_DAY: 三日排行 + - WEEK: 周排行 + """ + + THREE_DAY = 3 + WEEK = 7 + + +class RankType(Enum): + """ + 排行榜类型 + + - All: 全部 + - Bangumi: 番剧 + - GuochuangAnime: 国产动画 + - Guochuang: 国创相关 + - Documentary: 纪录片 + - Douga: 动画 + - Music: 音乐 + - Dance: 舞蹈 + - Game: 游戏 + - Knowledge: 知识 + - Technology: 科技 + - Sports: 运动 + - Car: 汽车 + - Life: 生活 + - Food: 美食 + - Animal: 动物圈 + - Kitchen: 鬼畜 + - Fashion: 时尚 + - Ent: 娱乐 + - Cinephile: 影视 + - Movie: 电影 + - TV: 电视剧 + - Variety: 综艺 + - Original: 原创 + - Rookie: 新人 + """ + + All = {"api_type": "x", "rid": 0, "type": "all"} + Bangumi = {"api_type": "pgc", "season_type": 1} + GuochuangAnime = {"api_type": "pgc", "season_type": 4} + Guochuang = {"api_type": "x", "rid": 168, "type": "all"} + Documentary = {"api_type": "pgc", "season_type": 3} + Douga = {"api_type": "x", "rid": 1, "type": "all"} + Music = {"api_type": "x", "rid": 3, "type": "all"} + Dance = {"api_type": "x", "rid": 129, "type": "all"} + Game = {"api_type": "x", "rid": 4, "type": "all"} + Knowledge = {"api_type": "x", "rid": 36, "type": "all"} + Technology = {"api_type": "x", "rid": 188, "type": "all"} + Sports = {"api_type": "x", "rid": 234, "type": "all"} + Car = {"api_type": "x", "rid": 223, "type": "all"} + Life = {"api_type": "x", "rid": 160, "type": "all"} + Food = {"api_type": "x", "rid": 211, "type": "all"} + Animal = {"api_type": "x", "rid": 217, "type": "all"} + Kichiku = {"api_type": "x", "rid": 119, "type": "all"} + Fashion = {"api_type": "x", "rid": 155, "type": "all"} + Ent = {"api_type": "x", "rid": 5, "type": "all"} + Cinephile = {"api_type": "x", "rid": 181, "type": "all"} + Movie = {"api_type": "pgc", "season_type": 2} + TV = {"api_type": "pgc", "season_type": 5} + Variety = {"api_type": "pgc", "season_type": 7} + Original = {"api_type": "x", "rid": 0, "type": "origin"} + Rookie = {"api_type": "x", "rid": 0, "type": "rookie"} + + +class VIPRankType(Enum): + """ + 大会员中心热播榜单类型,即 rank_id + + - VIP: 会员 + - BANGUMI: 番剧 + - GUOCHUANG: 国创 + - MOVIE: 电影 + - DOCUMENTARY: 纪录片 + - TV: 电视剧 + - VARIETY: 综艺 + """ + + VIP = 279 + BANGUMI = 118 + GUOCHUANG = 119 + MOVIE = 174 + DOCUMENTARY = 175 + TV = 176 + VARIETY = 177 + + +class MangeRankType(Enum): + """ + 漫画排行榜类型 + + - NEW: 新作 + - BOY: 男生 + - GRIL: 女生 + - GUOCHUANG: 国漫 + - JAPAN: 日漫 + - SOUTHKOREA: 韩漫 + - OFFICAL: 宝藏 + - FINISH: 完结 + """ + + NEW = 7 + BOY = 11 + GRIL = 12 + GUOCHUANG = 1 + JAPAN = 0 + SOUTHKOREA = 2 + OFFICAL = 5 + FINISH = 13 + + +class LiveRankType(Enum): + """ + 直播通用榜类型 + + - SAIL_BOAT_VALUE: 主播舰队榜 + - SAIL_BOAT_TICKET: 船员价值榜 + - SAIL_BOAT_NUMBER: 舰船人数榜 + - MASTER_LEVEL: 主播等级榜 + - USER_LEVEL: 用户等级榜 + """ + + SAIL_BOAT_VALUE = "sail_boat_value" + SAIL_BOAT_TICKET = "sail_boat_ticket" + SAIL_BOAT_NUMBER = "sail_boat_number" + MASTER_LEVEL = "master_level" + USER_LEVEL = "user_level" + + +class LiveEnergyRankType(Enum): + """ + 直播超能用户榜类型 + + - MONTH: 本月 + - PRE_MONTH: 上月 + """ + + MONTH = "month" + PRE_MONTH = "pre_month" + + +async def get_rank( + type_: RankType = RankType.All, day: RankDayType = RankDayType.THREE_DAY +) -> dict: + """ + 获取视频排行榜 + + Args: + type_ (RankType): 排行榜类型. Defaults to RankType.All + + day (RankDayType): 排行榜时间. Defaults to RankDayType.THREE_DAY + 仅对 api_type 为 RankAPIType.PGC 有效 + + Returns: + dict: 调用 API 返回的结果 + """ + params = {} + + # 确定 API 接口类型 + if type_.value["api_type"] == RankAPIType.V2.value: + api = API["info"]["v2_ranking"] + params["rid"] = type_.value["rid"] + elif type_.value["api_type"] == RankAPIType.PGC.value: + api = API["info"]["pgc_ranking"] + params["season_type"] = type_.value["season_type"] + params["day"] = day.value + else: + raise Exception("Unknown RankType") + + return await Api(**api).update_params(**params).result + + +async def get_music_rank_list() -> dict: + """ + 获取全站音乐榜每周信息(不包括具体的音频列表) + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["music_weekly_series"] + params = {"list_type": 1} + return await Api(**api).update_params(**params).result + + +async def get_music_rank_weekly_detail(week: int = 1) -> dict: + """ + 获取全站音乐榜一周的详细信息(不包括具体的音频列表) + + Args: + week(int): 第几周. Defaults to 1. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["music_weekly_details"] + params = {"list_id": week} + return await Api(**api).update_params(**params).result + + +async def get_music_rank_weekly_musics(week: int = 1) -> dict: + """ + 获取全站音乐榜一周的音频列表(返回的音乐的 id 对应了 music.Music 类创建实例传入的 id) + + Args: + week(int): 第几周. Defaults to 1. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["music_weekly_content"] + params = {"list_id": week} + return await Api(**api).update_params(**params).result + + +async def get_vip_rank(type_: VIPRankType = VIPRankType.VIP) -> dict: + """ + 获取大会员中心的排行榜 + + Args: + type_ (VIPRankType): 排行榜类型. Defaults to VIPRankType.VIP + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["VIP_rank"] + params = {"rank_id": type_.value} + return await Api(**api).update_params(**params).result + + +async def get_manga_rank(type_: MangeRankType = MangeRankType.NEW, credential: Credential = None) -> dict: + """ + 获取漫画专属排行榜 + + Args: + credential (Credential): 凭据类 + + Returns: + dict: 调用 API 返回的结果 + """ + credential = credential if credential else Credential() + credential.raise_for_no_sessdata() + + api = API["info"]["manga_rank"] + params = {"device": "pc", "platform": "web"} + data = {"id": type_.value} + return ( + await Api(**api, no_csrf=True, credential=credential) + .update_data(**data) + .update_params(**params) + .result + ) + + +async def get_live_hot_rank() -> dict: + """ + 获取直播首页人气排行榜 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["live_hot_rank"] + return await Api(**api).result + + +async def get_live_sailing_rank() -> dict: + """ + 获取首页直播大航海排行榜 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["live_sailing_rank"] + return await Api(**api).update_params(**{}).result + + +async def get_live_energy_user_rank( + date: LiveEnergyRankType = LiveEnergyRankType.MONTH, pn: int = 1, ps: int = 20 +) -> dict: + """ + 获取直播超能用户榜 + + Args: + date (LiveEnergyRankType): 月份. Defaults to LiveEnergyRankType.MONTH + + pn (int): 页码. Defaults to 1 + + ps (int): 每页数量. Defaults to 20 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["live_energy_user_rank"] + params = {"date": date.value, "page": pn, "page_size": ps} + return await Api(**api).update_params(**params).result + + +async def get_live_rank( + _type: LiveRankType = LiveRankType.SAIL_BOAT_VALUE, pn: int = 1, ps: int = 20 +) -> dict: + """ + 获取直播通用榜单 + + Args: + _type (LiveRankType): 榜单类型. Defaults to LiveRankType.VALUE + + pn (int): 页码. Defaults to 1 + + ps (int): 每页数量. Defaults to 20 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["live_web_top"] + params = { + "type": _type.value, + "page": pn, + "page_size": ps, + "is_trend": 1, + "area_id": None, + } + return await Api(**api).update_params(**params).result + + +async def get_live_user_medal_rank(pn: int = 1, ps: int = 20) -> dict: + """ + 获取直播用户勋章榜 + + Args: + pn (int): 页码. Defaults to 1 + + ps (int): 每页数量. Defaults to 20 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["live_medal_level_rank"] + params = {"page": pn, "page_size": ps} + return await Api(**api).update_params(**params).result + + +async def subscribe_music_rank( + status: bool = True, credential: Union[Credential, None] = None +) -> dict: + """ + 设置关注全站音乐榜 + + Args: + status (bool) : 关注状态. Defaults to True. + + credential (Credential): 凭据类. Defaults to None. + """ + credential = credential if credential else Credential() + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + api = API["operate"]["subscribe"] + data = {"list_id": 1, "state": (1 if status else 2)} + return await Api(**api, credential=credential).update_data(**data).result + + +async def get_playlet_rank_phases() -> dict: + """ + 获取全站短剧榜期数 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["playlet_rank_phase"] + return await Api(**api, json_body=True, no_csrf=True).result + + +async def get_playlet_rank_info(phase_id: int) -> dict: + """ + 获取全站短剧榜 + + https://www.bilibili.com/v/popular/drama/ + + Args: + phase_id (int): 期数,从 get_playlet_rank_phase 获取 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["playlet_rank_info"] + data = {"phaseID": phase_id} + return await Api(**api, json_body=True, no_csrf=True).update_data(**data).result diff --git a/bilibili_api/search.py b/bilibili_api/search.py new file mode 100644 index 0000000000000000000000000000000000000000..c4702227912f030bd9e0ed3adbd0f508896bdcee --- /dev/null +++ b/bilibili_api/search.py @@ -0,0 +1,373 @@ +""" +bilibili_api.search + +搜索 +""" +import json +from enum import Enum +from typing import List, Union, Callable + +from .utils.utils import get_api +from .video_zone import VideoZoneTypes +from .utils.network import Api, get_session +from .utils.credential import Credential + +API = get_api("search") + + +class SearchObjectType(Enum): + """ + 搜索对象。 + + VIDEO : 视频 + + BANGUMI : 番剧 + + FT : 影视 + + LIVE : 直播 + + ARTICLE : 专栏 + + TOPIC : 话题 + + USER : 用户 + + LIVEUSER : 直播间用户 + """ + + VIDEO = "video" + BANGUMI = "media_bangumi" + FT = "media_ft" + LIVE = "live" + ARTICLE = "article" + TOPIC = "topic" + USER = "bili_user" + LIVEUSER = "live_user" + PHOTO = "photo" + + +class OrderVideo(Enum): + """ + 视频搜索类型 + + TOTALRANK : 综合排序 + + CLICK : 最多点击 + + PUBDATE : 最新发布 + + DM : 最多弹幕 + + STOW : 最多收藏 + + SCORES : 最多评论 + Ps: Api 中 的 order_sort 字段决定顺序还是倒序 + + """ + + TOTALRANK = "totalrank" + CLICK = "click" + PUBDATE = "pubdate" + DM = "dm" + STOW = "stow" + SCORES = "scores" + + +class OrderLiveRoom(Enum): + """ + 直播间搜索类型 + + NEWLIVE 最新开播 + + ONLINE 综合排序 + """ + + NEWLIVE = "live_time" + ONLINE = "online" + + +class OrderArticle(Enum): + """ + 文章的排序类型 + + TOTALRANK : 综合排序 + + CLICK : 最多点击 + + PUBDATE : 最新发布 + + ATTENTION : 最多喜欢 + + SCORES : 最多评论 + """ + + TOTALRANK = "totalrank" + PUBDATE = "pubdate" + CLICK = "click" + ATTENTION = "attention" + SCORES = "scores" + + +class OrderUser(Enum): + """ + 搜索用户的排序类型 + + FANS : 按照粉丝数量排序 + + LEVEL : 按照等级排序 + """ + + FANS = "fans" + LEVEL = "level" + + +class OrderCheese(Enum): + """ + 课程搜索排序类型 + + + RECOMMEND: 综合 + + SELL : 销量最高 + + NEW : 最新上架 + + CHEEP : 售价最低 + """ + + RECOMMEND = -1 + SELL = 1 + NEW = 2 + CHEEP = 3 + + +class CategoryTypePhoto(Enum): + """ + 相册分类 + + All 全部 + + DrawFriend 画友 + + PhotoFriend 摄影 + """ + + All = 0 + DrawFriend = 2 + PhotoFriend = 1 + + +class CategoryTypeArticle(Enum): + """ + 文章分类 + + All 全部 + + Anime 动画 + + Game 游戏 + + TV 电视 + + Life 生活 + + Hobby 兴趣 + + LightNovel 轻小说 + + Technology 科技 + """ + + All = 0 + Anime = 2 + Game = 1 + TV = 28 + Life = 3 + Hobby = 29 + LightNovel = 16 + Technology = 17 + + +async def search(keyword: str, page: int = 1) -> dict: + """ + 只指定关键字在 web 进行搜索,返回未经处理的字典 + + Args: + keyword (str): 搜索关键词 + + page (int): 页码. Defaults to 1. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["search"]["web_search"] + params = {"keyword": keyword, "page": page} + return await Api(**api).update_params(**params).result + + +async def search_by_type( + keyword: str, + search_type: Union[SearchObjectType, None] = None, + order_type: Union[OrderUser, OrderLiveRoom, OrderArticle, OrderVideo, None] = None, + time_range: int = -1, + video_zone_type: Union[int, VideoZoneTypes, None] = None, + order_sort: Union[int, None] = None, + category_id: Union[CategoryTypeArticle, CategoryTypePhoto, int, None] = None, + page: int = 1, + page_size: int = 42, + debug_param_func: Union[Callable, None] = None, +) -> dict: + """ + 指定分区,类型,视频长度等参数进行搜索,返回未经处理的字典 + + 类型:视频(video)、番剧(media_bangumi)、影视(media_ft)、直播(live)、直播用户(liveuser)、专栏(article)、话题(topic)、用户(bili_user) + + Args: + debug_param_func (Callable | None, optional) : 参数回调器,用来存储或者什么的 + + order_sort (int | None, optional) : 用户粉丝数及等级排序顺序 默认为0 由高到低:0 由低到高:1 + + category_id (CategoryTypeArticle | CategoryTypePhoto | int | None, optional) : 专栏/相簿分区筛选,指定分类,只在相册和专栏类型下生效 + + time_range (int, optional) : 指定时间,自动转换到指定区间,只在视频类型下生效 有四种:10分钟以下,10-30分钟,30-60分钟,60分钟以上 + + video_zone_type (int | ZoneTypes | None, optional) : 话题类型,指定 tid (可使用 channel 模块查询) + + order_type (OrderUser | OrderLiveRoom | OrderArticle | OrderVideo | None, optional): 排序分类类型 + + keyword (str) : 搜索关键词 + + search_type (SearchObjectType | None, optional) : 搜索类型 + + page (int, optional) : 页码 + + page_size (int, optional) : 每一页的数据大小 + + Returns: + dict: 调用 API 返回的结果 + """ + params = {"keyword": keyword, "page": page, "page_size": page_size} + if search_type: + params["search_type"] = search_type.value + else: + raise ValueError("Missing arg:search_type") + # params["search_type"] = SearchObjectType.VIDEO.value + # category_id + if ( + search_type.value == SearchObjectType.ARTICLE.value + or search_type.value == SearchObjectType.PHOTO.value + ): + if category_id: + if isinstance(category_id, int): + params["category_id"] = category_id + else: + params["category_id"] = category_id.value + # time_code + if search_type.value == SearchObjectType.VIDEO.value: + if time_range > 60: + time_code = 4 + elif 30 < time_range <= 60: + time_code = 3 + elif 10 < time_range <= 30: + time_code = 2 + elif 0 < time_range <= 10: + time_code = 1 + else: + time_code = 0 + params["duration"] = time_code + # zone_type + if video_zone_type: + if isinstance(video_zone_type, int): + params["tids"] = video_zone_type + elif isinstance(video_zone_type, VideoZoneTypes): + params["tids"] = video_zone_type.value + else: + params["tids"] = video_zone_type + # order_type + if order_type: + params["order"] = order_type.value + # order_sort + if search_type.value == SearchObjectType.USER.value: + params["order_sort"] = order_sort + if debug_param_func: + debug_param_func(params) + api = API["search"]["web_search_by_type"] + return await Api(**api).update_params(**params).result + + +async def get_default_search_keyword() -> dict: + """ + 获取默认的搜索内容 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["search"]["default_search_keyword"] + return await Api(**api).result + + +async def get_hot_search_keywords() -> dict: + """ + 获取热搜 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["search"]["hot_search_keywords"] + sess = get_session() + return json.loads((await sess.request("GET", api["url"])).text) + + +async def get_suggest_keywords(keyword: str) -> List[str]: + """ + 通过一些文字输入获取搜索建议。类似搜索词的联想。 + + Args: + keyword(str): 搜索关键词 + + Returns: + List[str]: 关键词列表 + """ + keywords = [] + sess = get_session() + api = API["search"]["suggest"] + params = {"term": keyword} + res = await Api(**api).update_params(**params).result + for key in res["tag"]: + keywords.append(key["value"]) + return keywords + + +async def search_games(keyword: str) -> dict: + """ + 搜索游戏特用函数 + + Args: + keyword (str): 搜索关键词 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["search"]["game"] + params = {"keyword": keyword} + return await Api(**api).update_params(**params).result + + +async def search_manga( + keyword: str, page_num: int = 1, page_size: int = 9, credential: Credential = None +): + """ + 搜索漫画特用函数 + + Args: + keyword (str): 搜索关键词 + + page_num (int): 页码. Defaults to 1. + + page_size (int): 每一页的数据大小. Defaults to 9. + + credential (Credential): 凭据类. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + credential = credential if credential else Credential() + api = API["search"]["manga"] + data = {"key_word": keyword, "page_num": page_num, "page_size": page_size} + return ( + await Api(**api, credential=credential, no_csrf=True).update_data(**data).result + ) + + +async def search_cheese( + keyword: str, + page_num: int = 1, + page_size: int = 30, + order: OrderCheese = OrderCheese.RECOMMEND, +): + """ + 搜索课程特用函数 + + Args: + keyword (str) : 搜索关键词 + + page_num (int) : 页码. Defaults to 1. + + page_size (int) : 每一页的数据大小. Defaults to 30. + + order (OrderCheese): 排序方式. Defaults to OrderCheese.RECOMMEND + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["search"]["cheese"] + params = { + "word": keyword, + "page": page_num, + "page_size": page_size, + "sort_type": order.value, + } + return await Api(**api).update_params(**params).result diff --git a/bilibili_api/session.py b/bilibili_api/session.py new file mode 100644 index 0000000000000000000000000000000000000000..acafea469ef1ee3fe23ed04c4d06394b2b72faa3 --- /dev/null +++ b/bilibili_api/session.py @@ -0,0 +1,594 @@ +""" +bilibili_api.session + +消息相关 +""" + +import json +import time +import asyncio +import logging +import datetime +from enum import Enum +from typing import Union, Optional + +from apscheduler.schedulers.asyncio import AsyncIOScheduler + +from bilibili_api.exceptions import ApiException + +from .video import Video +from .user import get_self_info +from .utils.utils import get_api, raise_for_statement +from .utils.picture import Picture +from .utils.AsyncEvent import AsyncEvent +from .utils.credential import Credential +from .utils.network import Api + +API = get_api("session") + + +async def fetch_session_msgs( + talker_id: int, credential: Credential, session_type: int = 1, begin_seqno: int = 0 +) -> dict: + """ + 获取指定用户的近三十条消息 + + Args: + talker_id (int) : 用户 UID + + credential (Credential): Credential + + session_type (int) : 会话类型 1 私聊 2 应援团 + + begin_seqno (int) : 起始 Seqno + + Returns: + dict: 调用 API 返回结果 + """ + + credential.raise_for_no_sessdata() + params = { + "talker_id": talker_id, + "session_type": session_type, + "begin_seqno": begin_seqno, + } + api = API["session"]["fetch"] + + return await Api(**api, credential=credential).update_params(**params).result + + +async def new_sessions( + credential: Credential, begin_ts: int = int(time.time() * 1000000) +) -> dict: + """ + 获取新消息 + + Args: + credential (Credential): Credential + + begin_ts (int) : 起始时间戳 + + Returns: + dict: 调用 API 返回结果 + """ + + credential.raise_for_no_sessdata() + params = {"begin_ts": begin_ts, "build": 0, "mobi_app": "web"} + api = API["session"]["new"] + + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_sessions(credential: Credential, session_type: int = 4) -> dict: + """ + 获取已有消息 + + Args: + credential (Credential): Credential + + session_type (int) : 会话类型 1: 私聊, 2: 通知, 3: 应援团, 4: 全部 + + Returns: + dict: 调用 API 返回结果 + """ + + credential.raise_for_no_sessdata() + params = { + "session_type": session_type, + "group_fold": 1, + "unfollow_fold": 0, + "sort_rule": 2, + "build": 0, + "mobi_app": "web", + } + api = API["session"]["get"] + + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_session_detail( + credential: Credential, talker_id: int, session_type: int = 1 +) -> dict: + """ + 获取会话详情 + + Args: + credential (Credential): Credential + + session_type (int) : 会话类型 + + talker_id (int) : 会话对象的UID + + Returns: + dict: 调用 API 返回结果 + """ + + credential.raise_for_no_sessdata() + params = {"talker_id": talker_id, "session_type": session_type} + api = API["session"]["get_session_detail"] + + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_replies( + credential: Credential, + last_reply_id: Optional[int] = None, + reply_time: Optional[int] = None, +) -> dict: + """ + 获取收到的回复 + + Args: + credential (Credential): 凭据类. + + last_reply_id (Optional, int) 最后一个评论的 ID + + reply_time (Optional, int) 最后一个评论发送时间 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["session"]["replies"] + params = {"id": last_reply_id, "reply_time": reply_time} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_likes( + credential: Credential, last_id: int = None, like_time: int = None +) -> dict: + """ + 获取收到的赞 + + Args: + credential (Credential): 凭据类. + + last_id (Optional, int) 最后一个 ID + + like_time (Optional, int) 最后一个点赞发送时间 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["session"]["likes"] + params = {"id": last_id, "like_time": like_time} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_at( + credential: Credential, last_uid: int = None, at_time: int = None +) -> dict: + """ + 获取收到的 AT + + Args: + credential (Credential): 凭据类. + + last_id (Optional, int) 最后一个 ID + + at_time (Optional, int) 最后一个点赞发送时间 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["session"]["at"] + params = {"id": last_uid, "at_time": at_time} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_unread_messages(credential: Credential) -> dict: + """ + 获取未读的信息 + + Args: + credential (Credential): 凭据类. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["session"]["unread"] + return await Api(**api, credential=credential).result + + +async def get_system_messages(credential: Credential) -> dict: + """ + 获取系统信息 + + Args: + credential (Credential): 凭据类. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["session"]["system_msg"] + return await Api(**api, credential=credential).result + + +async def get_session_settings(credential: Credential) -> dict: + """ + 获取消息设置 + + Args: + credential (Credential): 凭据类. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["session"]["session_settings"] + return await Api(**api, credential=credential).result + + +class EventType(Enum): + """ + 事件类型 + + - TEXT: 纯文字消息 + - PICTURE: 图片消息 + - WITHDRAW: 撤回消息 + - GROUPS_PICTURE: 应援团图片,但似乎不常触发,一般使用 PICTURE 即可 + - SHARE_VIDEO: 分享视频 + - NOTICE: 系统通知 + - PUSHED_VIDEO: UP主推送的视频 + - WELCOME: 新成员加入应援团欢迎 + """ + + TEXT = 1 + PICTURE = 2 + WITHDRAW = 5 + GROUPS_PICTURE = 6 + SHARE_VIDEO = 7 + NOTICE = 10 + PUSHED_VIDEO = 11 + WELCOME = 306 + + +class Event: + """ + 事件参数: + + receiver_id: 收信人 UID + + receiver_type: 收信人类型,1: 私聊, 2: 应援团通知, 3: 应援团 + + sender_uid: 发送人 UID + + talker_id: 对话人 UID + + msg_seqno: 事件 Seqno + + msg_type: 事件类型 + + msg_key: 事件唯一编号 + + timestamp: 事件时间戳 + + content: 事件内容 + """ + + receiver_id: int + receiver_type: int + sender_uid: int + talker_id: int + msg_seqno: int + msg_type: int + msg_key: int + timestamp: int + content: Union[str, int, Picture, Video] + + def __init__(self, data: dict, self_uid: int): + """ + 信息事件类型 + + Args: + data: 接收到的事件详细信息 + + self_uid: 用户自身 UID + """ + self.__dict__.update(data) + self.uid = self_uid + + try: + self.__content() + except AttributeError: + logging.error(f"解析消息错误:{data}") + + def __str__(self): + if self.receiver_type == 1: + if self.receiver_id == self.uid: + msg_type = "收到" + user_id = self.sender_uid + elif self.sender_uid == self.uid: + msg_type = "发送" + user_id = self.receiver_id + + elif self.receiver_type == 2: + user_id = self.receiver_id + if self.sender_uid == self.uid: + msg_type = "发送应援团" + elif self.sender_uid == 0: + msg_type = "系统提示" + else: + msg_type = "收到应援团" + + return f"{msg_type} {user_id} 信息 {self.content}({self.timestamp})" # type: ignore + + def __content(self) -> None: + """ + 更新消息内容 + """ + content: dict = json.loads(self.content) # type: ignore + mt = self.msg_type + + if mt == EventType.TEXT.value: + self.content = content.get("content") # type: ignore + + elif mt == EventType.WELCOME.value: + self.content = content.get("content") + str(content.get("group_id")) # type: ignore + + elif mt == EventType.WITHDRAW.value: + self.content = str(content) + + elif mt == EventType.PICTURE.value or mt == EventType.GROUPS_PICTURE.value: + content.pop("original") + self.content = Picture(**content) + + elif mt == EventType.SHARE_VIDEO.value or mt == EventType.PUSHED_VIDEO.value: + self.content = Video(bvid=content.get("bvid"), aid=content.get("id")) + + elif mt == EventType.NOTICE.value: + self.content = content["title"] + " " + content["text"] + + else: + logging.error(f"未知消息类型:{mt},消息内容:{content}") + + +async def send_msg( + credential: Credential, + receiver_id: int, + msg_type: EventType, + content: Union[str, Picture], +) -> dict: + """ + 给用户发送私聊信息。目前仅支持纯文本。 + + Args: + credential (Credential) : 凭证 + + receiver_id (int) : 接收者 UID + + msg_type (EventType) : 信息类型,参考 Event 类的事件类型。 + + content (str | Picture): 信息内容。支持文字和图片。 + + Returns: + dict: 调用 API 返回结果 + """ + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + + api = API["operate"]["send_msg"] + self_info = await get_self_info(credential) + sender_uid = self_info["mid"] + + if msg_type == EventType.TEXT: + real_content = json.dumps({"content": content}) + elif msg_type == EventType.WITHDRAW: + real_content = str(content) + elif msg_type == EventType.PICTURE or msg_type == EventType.GROUPS_PICTURE: + raise_for_statement(isinstance(content, Picture), "TypeError") + await content.upload_file(credential=credential, data={"biz": "im"}) + real_content = json.dumps( + { + "url": content.url, + "height": content.height, + "width": content.width, + "imageType": content.imageType, + "original": 1, + "size": content.size, + } + ) + else: + raise ApiException("信息类型不支持。") + + data = { + "msg[sender_uid]": sender_uid, + "msg[receiver_id]": receiver_id, + "msg[receiver_type]": 1, + "msg[msg_type]": msg_type.value, + "msg[msg_status]": 0, + "msg[content]": real_content, + "msg[dev_id]": "A6716E9A-7CE3-47AF-994B-F0B34178D28D", + "msg[new_face_version]": 0, + "msg[timestamp]": int(time.time()), + "from_filework": 0, + "build": 0, + "mobi_app": "web", + } + return await Api(**api, credential=credential).update_data(**data).result + + +class Session(AsyncEvent): + """ + 会话类,用来开启消息监听。 + """ + def __init__(self, credential: Credential, debug=False): + super().__init__() + # 会话状态 + self.__status = 0 + + # 已获取会话中最大的时间戳 默认当前时间 + self.maxTs = int(time.time() * 1000000) + + # 会话UID为键 会话中最大Seqno为值 + self.maxSeqno = dict() + + # 凭证 + self.credential = credential + + # 异步定时任务框架 + self.sched = AsyncIOScheduler(timezone="Asia/Shanghai") + + # 已接收的所有事件 用于撤回时找回 + self.events = dict() + + # logging + self.logger = logging.getLogger("Session") + self.logger.setLevel(logging.DEBUG if debug else logging.INFO) + if not self.logger.handlers: + handler = logging.StreamHandler() + handler.setFormatter( + logging.Formatter( + "[%(asctime)s][%(levelname)s]: %(message)s", "%Y-%m-%d %H:%M:%S" + ) + ) + self.logger.addHandler(handler) + + def on(self, event_type: EventType): + """ + 重载装饰器注册事件监听器 + + Args: + event_type (EventType): 事件类型 + """ + return super().on(event_name=str(event_type.value)) + + def get_status(self) -> int: + """ + 获取连接状态 + + Returns: + int: 0 初始化,1 已连接,2 断开连接中,3 已断开,4 错误 + """ + return self.__status + + async def run(self, exclude_self: bool = True) -> None: + """ + 非阻塞异步爬虫 定时发送请求获取消息 + + Args: + exclude_self: bool 是否排除自己发出的消息,默认排除 + """ + + # 获取自身UID 用于后续判断消息是发送还是接收 + self_info = await get_self_info(self.credential) + self.uid = self_info["mid"] + + # 初始化 只接收开始运行后的新消息 + js = await get_sessions(self.credential) + self.maxSeqno = { + _session["talker_id"]: _session["max_seqno"] + for _session in js.get("session_list", []) + } + + # 间隔 6 秒轮询消息列表 之前设置 3 秒询一次 跑了一小时给我账号冻结了 + @self.sched.scheduled_job( + "interval", + id="query", + seconds=6, + max_instances=3, + next_run_time=datetime.datetime.now(), + ) + async def qurey(): + js: dict = await new_sessions(self.credential, self.maxTs) + if js.get("session_list") is None: + return + + pending = set() + for session in js["session_list"]: + self.maxTs = max(self.maxTs, session["session_ts"]) + pending.add( + asyncio.create_task( + fetch_session_msgs( + session["talker_id"], + self.credential, + session["session_type"], + self.maxSeqno.get(session["talker_id"]), # type: ignore + ) + ) + ) + self.maxSeqno[session["talker_id"]] = session["max_seqno"] + + while pending: + done, pending = await asyncio.wait(pending) + for done_task in done: + result: dict = await done_task + if result is None or result.get("messages") is None: + continue + for message in result.get("messages", [])[::-1]: + event = Event(message, self.uid) + if event.msg_type == EventType.WITHDRAW.value: + self.logger.info( + str( + self.events.get( + event.content, f"key={event.content}" + ) + ) + + f" 被撤回({event.timestamp})" + ) + else: + self.logger.info(event) + + # 自己发出的消息不发布任务 + if event.sender_uid != self.uid or not exclude_self: + self.dispatch(str(event.msg_type), event) + + self.events[str(event.msg_key)] = event + + self.logger.debug(f"maxTs = {self.maxTs}") + + self.__status = 1 + self.sched.start() + self.logger.info("开始轮询") + + async def start(self, exclude_self: bool = True) -> None: + """ + 阻塞异步启动 通过调用 self.close() 后可断开连接 + + Args: + exclude_self: bool 是否排除自己发出的消息,默认排除 + """ + + await self.run(exclude_self) + while self.get_status() < 2: + await asyncio.sleep(1) + + if self.get_status() == 2: + self.__status = 3 + + async def reply(self, event: Event, content: Union[str, Picture]) -> dict: # type: ignore + """ + 快速回复消息 + + Args: + event : Event 要回复的消息 + + content: str | Picture 要回复的文字内容 + + Returns: + dict: 调用接口返回的内容。 + """ + + if self.uid == event.sender_uid: + self.logger.error("不能给自己发送消息哦~") + else: + msg_type = ( + EventType.PICTURE if isinstance(content, Picture) else EventType.TEXT + ) + return await send_msg(self.credential, event.sender_uid, msg_type, content) + + def close(self) -> None: + """结束轮询""" + + self.sched.remove_job("query") + self.__status = 2 + self.logger.info("结束轮询") diff --git a/bilibili_api/settings.py b/bilibili_api/settings.py new file mode 100644 index 0000000000000000000000000000000000000000..aed75d88bdff70f8879ce331bbf075562b4d6e21 --- /dev/null +++ b/bilibili_api/settings.py @@ -0,0 +1,68 @@ +""" +bilibili_api.settings + +这里是配置模块的地方 +""" + +import logging +from enum import Enum + +class HTTPClient(Enum): + """ + - AioHttp: aiohttp + - Httpx: httpx + """ + + AIOHTTP = "aiohttp" + HTTPX = "httpx" + +http_client: HTTPClient = HTTPClient.AIOHTTP +""" +用于设置使用的 HTTP 客户端,默认为 Httpx + +e.x.: +``` python +from bilibili_api import settings +settings.http_client = settings.HTTPClient.AIOHTTP +``` + +**Note: 当前模块所有 `Web Socket` 操作强制使用 `aiohttp`** +""" + +proxy: str = "" +""" +代理设置 + +e.x.: +``` python +from bilibili_api import settings +settings.proxy = "https://www.example.com" +``` +""" + +timeout: float = 5.0 +""" +web 请求超时时间设置 +""" + +geetest_auto_open: bool = True +""" +是否自动打开 geetest 验证窗口 +""" + +request_log: bool = False +""" +请求 Api 时是否打印 Api 信息 +""" + +wbi_retry_times: int = 3 +""" +WBI请求重试次数上限设置, 默认为3次 +""" + +logger = logging.getLogger("request") +if not logger.handlers: + logger.setLevel(logging.INFO) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("[Request][%(asctime)s] %(message)s")) + logger.addHandler(handler) diff --git a/bilibili_api/show.py b/bilibili_api/show.py new file mode 100644 index 0000000000000000000000000000000000000000..943191af8d55c714072013eecb22e8152b107f3c --- /dev/null +++ b/bilibili_api/show.py @@ -0,0 +1,294 @@ +""" +bilibili_api.show + +展出相关 +""" +import json +import random +import time +from dataclasses import dataclass, field +from typing import List + +from .utils.credential import Credential +from .utils.network import Api +from .utils.utils import get_api, get_deviceid + +API = get_api("show") + + +@dataclass +class Ticket: + """ + 票对象 + + id (int): 场次id + + price (float): 价格(RMB) + + desc (str): 描述 + + sale_start (str): 开售开始时间 + + sale_end (str): 开售结束时间 + """ + id: int + price: int + desc: str + sale_start: str + sale_end: str + + +@dataclass +class Session: + """ + 场次对象 + + id (int): 场次id + + start_time (int): 场次开始时间戳 + + formatted_time (str): 格式化start_time后的时间格式: YYYY-MM-DD dddd + + ticket_list (list[Ticket]): 存放Ticket对象的list + """ + id: int + start_time: int + formatted_time: str + ticket_list: List[Ticket] = field(default_factory=list) + + +@dataclass +class BuyerInfo: + """ + 购买人信息 + + id (int): 信息序号 + + uid (int): 用户 ID + + account_channel (str): 默认为空 + + personal_id (str): 身份证号 + + name (str): 姓名 + + id_card_front (str): 未知 + + id_card_back (str): 未知 + + is_default (bool): 是否为默认信息 + + tel (str): 电话号码 + + error_code (str): 错误代码 + + id_type (int): 默认 0 + + verify_status (int): 认证状态 + + accountId (int): 用户 ID + + isBuyerInfoVerified (bool): 默认为 True + + isBuyerValid (bool): 默认为 True + """ + id: int + uid: int + account_channel: str + personal_id: str + name: str + id_card_front: str + id_card_back: str + is_default: int + tel: str + error_code: str + id_type: int + verify_status: int + accountId: int + isBuyerInfoVerified: bool = True + isBuyerValid: bool = True + + +async def get_project_info(project_id: int) -> dict: + """ + 返回项目全部信息 + + Args: + project_id (int): 项目id + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["get"] + return await Api(**api).update_params(id=project_id).result + + +async def get_available_sessions(project_id: int) -> List[Session]: + """ + 返回该项目的所有可用场次 + + Args: + project_id (int): 项目id + + Returns: + list[Session]: 存放场次对象的list + """ + rtn_list = [] + project_info = await get_project_info(project_id) + for v in project_info["screen_list"]: + sess_obj = Session(id=v["id"], start_time=v["start_time"], formatted_time=v["name"]) + for t in v["ticket_list"]: + sess_obj.ticket_list.append( + Ticket( + id=t["id"], + price=t["price"], + desc=t["desc"], + sale_start=t["sale_start"], + sale_end=t["sale_end"], + ) + ) + rtn_list.append(sess_obj) + return rtn_list + + +async def get_all_buyer_info(credential: Credential) -> dict: + """ + 返回账号的全部身份信息 + + Args: + credential (Credential): 登录凭证 + + Returns: + dict: 调用 API 返回的结果 + """ + credential.raise_for_no_sessdata() + api = API["info"]["buyer_info"] + return await Api(**api, credential=credential).result + + +async def get_all_buyer_info_obj(credential: Credential) -> List[BuyerInfo]: + """ + 以BuyerInfo对象返回账号的全部身份信息 + + Args: + credential (Credential): 登录凭证 + + Returns: + list[BuyerInfo]: BuyerInfo对象列表 + """ + res = await get_all_buyer_info(credential) + return [BuyerInfo(**v) for v in res["list"]] + + +def generate_clickPosition() -> dict: + """ + 生成虚假的点击事件 + + Returns: + dict: 点击坐标和时间 + """ + # 生成随机的 x 和 y 坐标,以下范围大概是1920x1080屏幕下可能的坐标 + x = random.randint(1320, 1330) + y = random.randint(880, 890) + # 生成随机的起始时间和结束时间(或当前时间) + origin_timestamp = int(time.time() * 1000) + # 添加一些随机时间差 (5s ~ 10s) + now_timestamp = origin_timestamp + random.randint(5000, 10000) + return { + "x": x, + "y": y, + "origin": origin_timestamp, + "now": now_timestamp + } + + +@dataclass +class OrderTicket: + """ + 购票类 + + Args: + credential (Credential): Credential 对象 + + target_buyer (BuyerInfo): 购票人 + + project_id (int): 展出id + + session (Session): Session 对象 + + ticket (Ticket): Ticket 对象 + """ + credential: Credential + target_buyer: BuyerInfo + project_id: int + session: Session + ticket: Ticket + + async def _get_create_order_payload(self) -> dict: + """ + 获取 create order API 所需的载荷 + + Returns: + dict: payload + """ + res = await self.get_token() + header = { + "count": 1, + "order_type": 1, + "pay_money": self.ticket.price, + "project_id": self.project_id, + "screen_id": self.session.id, + "sku_id": self.ticket.id, + "timestamp": int(time.time() * 1000), + "token": res["token"], + "deviceId": get_deviceid('', True), + "clickPosition": json.dumps(generate_clickPosition()), + } + info = await get_project_info(self.project_id) + info = info["performance_desc"]["list"] + for element in info: + if element["module"] == "base_info": + info = element + break + for detail in info["details"]: + content = detail["content"] + if "一人一证" in content or "一单一证" in content: + header.update({ + "buyer_info": json.dumps([self.target_buyer.__dict__]) + }) + return header + header.update({ + "buyer": self.target_buyer.name, + "tel": self.target_buyer.tel, + }) + return header + + async def get_token(self): + """ + 获取购票Token + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + api = API["info"]["token"] + payload = { + "count": "1", + "order_type": 1, + "project_id": self.project_id, + "screen_id": self.session.id, + "sku_id": self.ticket.id + } + return await Api(**api, credential=self.credential).update_data(**payload).result + + async def create_order(self): + """ + 创建购买订单 + + Returns: + dict: 调用 API 返回的结果 + """ + payload = await self._get_create_order_payload() + api = API["operate"]["order"] + return await Api(**api, credential=self.credential).update_params(project_id=self.project_id).update_data( + **payload).result diff --git a/bilibili_api/tools/ivitools/__init__.py b/bilibili_api/tools/ivitools/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a945f6c362919cae3268a5bd1666f29fc984d6d9 --- /dev/null +++ b/bilibili_api/tools/ivitools/__init__.py @@ -0,0 +1,9 @@ +""" +IVITools + +A Simple IVI file manager & toolbox. + +BY Nemo2011 + +Licensed under the GNU General Public License v3+. +""" diff --git a/bilibili_api/tools/ivitools/__main__.py b/bilibili_api/tools/ivitools/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..d76f134fcd4aa04664ba333e3a8461babdb8c3d3 --- /dev/null +++ b/bilibili_api/tools/ivitools/__main__.py @@ -0,0 +1,80 @@ +""" +IVITools + +A Simple IVI file manager & toolbox. + +BY Nemo2011 + +Licensed under the GNU General Public License v3+. +""" +__author__ = "Nemo2011 " +__license__ = "GPLv3+" + +import sys +import platform +import warnings +from typing import List + +from colorama import Fore + +from .touch import touch_ivi +from .scan import scan_ivi_file +from .extract import extract_ivi +from .download import download_interactive_video + + +def run_args(command: str, args: List[str]): + if command == "help": + print( + "IVITools - A Simple IVI file manager & toolbox. \n\ +BY Nemo2011\n\ +\n\ +Commands: download, extract, help, play, scan, touch\n\ +\n\ +ivitools download [BVID] [OUT]\n\ +ivitools extract [IVI]\n\ +ivitools help\n\ +ivitools play [IVI] (PyQT6 require)\n\ +ivitools scan [IVI]\n\ +ivitools touch [IVI]" + ) + if command == "scan": + scan_ivi_file(args[0]) + elif command == "extract": + extract_ivi(args[0], args[1]) + elif command == "touch": + touch_ivi(args[0]) + elif command == "download": + download_interactive_video(args[0], args[1]) + elif command == "play": + try: + import PyQt6 + except ImportError: + warnings.warn( + "IVITools Built-in Player require PyQt6 but IVITools can't find it. \nYou can install it by `pip3 install PyQt6`. " + ) + return + from .player import main, prepopen + + if len(args) == 0: + main() + else: + prepopen(args[0]) + else: + raise ValueError("Command not found. Use `ivitools help` for helps. ") + + +def main(): + if len(sys.argv) == 1: + print(Fore.YELLOW + "[WRN]: No arguments. " + Fore.RESET) + print(Fore.YELLOW + "[WRN]: Use `ivitools help` for helps. " + Fore.RESET) + return + try: + args = sys.argv + run_args(args[1], args[2:]) + except Exception as e: + print(Fore.RED + "[ERR]: " + str(e) + Fore.RESET) + + +if __name__ == "__main__": + main() diff --git a/bilibili_api/tools/ivitools/__pycache__/__init__.cpython-38.pyc b/bilibili_api/tools/ivitools/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d810befeba0c7d95a6a9e0641819072295539deb Binary files /dev/null and b/bilibili_api/tools/ivitools/__pycache__/__init__.cpython-38.pyc differ diff --git a/bilibili_api/tools/ivitools/__pycache__/__main__.cpython-38.pyc b/bilibili_api/tools/ivitools/__pycache__/__main__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae0256e0a7eee6474ebeea0b81240649be815c14 Binary files /dev/null and b/bilibili_api/tools/ivitools/__pycache__/__main__.cpython-38.pyc differ diff --git a/bilibili_api/tools/ivitools/__pycache__/download.cpython-38.pyc b/bilibili_api/tools/ivitools/__pycache__/download.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1dc2e45353f5374546920d1ffcc6f4e5a66ff75c Binary files /dev/null and b/bilibili_api/tools/ivitools/__pycache__/download.cpython-38.pyc differ diff --git a/bilibili_api/tools/ivitools/__pycache__/extract.cpython-38.pyc b/bilibili_api/tools/ivitools/__pycache__/extract.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd0ea149c38d7a26509b4aa92a0c9ae2778b1c82 Binary files /dev/null and b/bilibili_api/tools/ivitools/__pycache__/extract.cpython-38.pyc differ diff --git a/bilibili_api/tools/ivitools/__pycache__/player.cpython-38.pyc b/bilibili_api/tools/ivitools/__pycache__/player.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..77d9462724c5a9a3e73247f90cbdadc6ec8e77bb Binary files /dev/null and b/bilibili_api/tools/ivitools/__pycache__/player.cpython-38.pyc differ diff --git a/bilibili_api/tools/ivitools/__pycache__/scan.cpython-38.pyc b/bilibili_api/tools/ivitools/__pycache__/scan.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d08e1745d47a89e8161a39c4d272fe6ea3794893 Binary files /dev/null and b/bilibili_api/tools/ivitools/__pycache__/scan.cpython-38.pyc differ diff --git a/bilibili_api/tools/ivitools/__pycache__/touch.cpython-38.pyc b/bilibili_api/tools/ivitools/__pycache__/touch.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..60732f1b21fbfc3c5c78f78cb615fb56f55d87b2 Binary files /dev/null and b/bilibili_api/tools/ivitools/__pycache__/touch.cpython-38.pyc differ diff --git a/bilibili_api/tools/ivitools/download.py b/bilibili_api/tools/ivitools/download.py new file mode 100644 index 0000000000000000000000000000000000000000..f9f0d7f24b59eb72e24545fac3413a2788a6264f --- /dev/null +++ b/bilibili_api/tools/ivitools/download.py @@ -0,0 +1,75 @@ +""" +ivitools.download + +下载互动视频 +""" +import os + +import tqdm +import requests +from colorama import Fore + +from bilibili_api import sync, interactive_video + + +async def download_file(url: str, out: str): + resp = requests.get( + url, + headers={"User-Agent": "Mozilla/5.0", "Referer": "https://www.bilibili.com"}, + stream=True, + ) + headers = resp.headers + CHUNK_SIZE = 1024 + parts = int(headers["Content-Length"]) // CHUNK_SIZE + if int(headers["Content-Length"]) % CHUNK_SIZE != 0: + parts += 1 + fp = open(out, "wb") + bar = tqdm.tqdm(range(parts)) + bar.set_description("DOWNLOADING...") + bar.display() + for chunk in resp.iter_content(CHUNK_SIZE): + fp.write(chunk) + bar.update(1) + + +def download_interactive_video(bvid: str, out: str): + ivideo = interactive_video.InteractiveVideo(bvid) + downloader = interactive_video.InteractiveVideoDownloader( + ivideo, out, download_file + ) + + @downloader.on("START") + async def on_start(data): + print("Start downloading " + bvid + "...") + + @downloader.on("GET") + async def on_get(data): + print(f'Get node {data["title"]} (node_id: {data["node_id"]}). ') + + @downloader.on("PREPARE_DOWNLOAD") + async def on_prepare_download(data): + print( + f'Start download the video for cid {data["cid"]}' + ) + + @downloader.on("PACKAGING") + async def on_packaing(data): + print("Almost!!! It's packaging your interactive video now! ") + + @downloader.on("SUCCESS") + async def on_success(data): + print( + Fore.GREEN + + "Congratulations! Your IVI file is ready. Check it at " + + os.path.abspath(out) + + ". " + + Fore.RESET + ) + + try: + sync(downloader.start()) + except KeyboardInterrupt: + sync(downloader.abort()) + print(Fore.YELLOW + "[WRN]: Aborted by user. " + Fore.RESET) + except Exception as e: + raise e diff --git a/bilibili_api/tools/ivitools/extract.py b/bilibili_api/tools/ivitools/extract.py new file mode 100644 index 0000000000000000000000000000000000000000..8bbfc7dd55554de2cf8ca638794011004de1a077 --- /dev/null +++ b/bilibili_api/tools/ivitools/extract.py @@ -0,0 +1,14 @@ +""" +ivitools.extract + +拆解 ivi 文件相关 +""" +import os +import zipfile + + +def extract_ivi(path: str, dest: str): + print("Extracting...") + if not os.path.exists(dest): + os.makedirs(dest) + zipfile.ZipFile(path).extractall(dest) diff --git a/bilibili_api/tools/ivitools/player.py b/bilibili_api/tools/ivitools/player.py new file mode 100644 index 0000000000000000000000000000000000000000..46e2216702552f2c39fc51a934e0bb1526306adc --- /dev/null +++ b/bilibili_api/tools/ivitools/player.py @@ -0,0 +1,899 @@ +import os +import sys +import copy +import enum +import json +import time +import random +import shutil +import zipfile +from random import random as rand +from typing import List, Tuple, Union + +from PyQt6 import QtGui, QtCore, QtWidgets, QtMultimedia, QtMultimediaWidgets + + +class InteractiveVariable: + """ + 互动节点的变量 + """ + + def __init__( + self, + name: str, + var_id: str, + var_value: int, + show: bool = False, + random: bool = False, + ): + """ + Args: + name (str) : 变量名 + var_id (str) : 变量 id + var_value (int) : 变量的值 + show (bool) : 是否显示 + random (bool) : 是否为随机值(1-100) + """ + self.__var_id = var_id + self.__var_value = var_value + self.__name = name + self.__is_show = show + self.__random = random + + def get_id(self) -> str: + return self.__var_id + + def refresh_value(self) -> None: + """ + 刷新变量数值 + """ + if self.is_random(): + self.__var_value = int(rand(0, 100)) + + def get_value(self) -> int: + return self.__var_value + + def is_show(self) -> bool: + return self.__is_show + + def is_random(self) -> bool: + return self.__random + + def get_name(self) -> str: + return self.__name + + def __str__(self): + return f"{self.__name} {self.__var_value}" + + +class InteractiveButtonAlign(enum.Enum): + """ + 按钮的文字在按钮中的位置 + + + ``` text + ----- + |xxx|----o (TEXT_LEFT) + ----- + + ----- + o----|xxx| (TEXT_RIGHT) + ----- + + ---------- + |XXXXXXXX| (DEFAULT) + ---------- + ``` + + - DEFAULT + - TEXT_UP + - TEXT_RIGHT + - TEXT_DOWN + - TEXT_LEFT + """ + + DEFAULT = 0 + TEXT_UP = 1 + TEXT_RIGHT = 2 + TEXT_DOWN = 3 + TEXT_LEFT = 4 + + +class InteractiveButton: + """ + 互动视频节点按钮类 + """ + + def __init__( + self, + text: str, + x: int, + y: int, + align: Union[InteractiveButtonAlign, int] = InteractiveButtonAlign.DEFAULT, + ): + """ + Args: + text (str) : 文字 + x (int) : x 轴 + y (int) : y 轴 + align (InteractiveButtonAlign | int): 按钮的文字在按钮中的位置 + """ + self.__text = text + self.__pos = (x, y) + if isinstance(align, InteractiveButtonAlign): + align = align.value + self.__align = align + + def get_text(self) -> str: + return self.__text + + def get_align(self) -> int: + return self.__align # type: ignore + + def get_pos(self) -> Tuple[int, int]: + return self.__pos + + def __str__(self): + return f"{self.__text} {self.__pos}" + + +class InteractiveJumpingCondition: + """ + 节点跳转的公式,只有公式成立才会跳转 + """ + + def __init__(self, var: List[InteractiveVariable] = [], condition: str = "True"): + """ + Args: + var (List[InteractiveVariable]): 所有变量 + condition (str) : 公式 + """ + self.__vars = var + self.__command = condition + + def get_result(self) -> bool: + """ + 计算公式获得结果 + + Returns: + bool: 是否成立 + """ + if self.__command == "": + return True + command = copy.copy(self.__command) + for var in self.__vars: + var_name = var.get_id() + var_value = var.get_value() + command = command.replace(var_name, str(var_value)) + command = command.replace("&&", " and ") + command = command.replace("||", " or ") + command = command.replace("!", " not ") + command = command.replace("===", "==") + command = command.replace("!==", "!=") + command = command.replace("true", "True") + command = command.replace("false", "False") + return eval(command) + + def __str__(self): + return f"{self.__command}" + + +class InteractiveJumpingCommand: + """ + 节点跳转对变量的操作 + """ + + def __init__(self, var: List[InteractiveVariable] = [], command: str = ""): + """ + Args: + var (List[InteractiveVariable]): 所有变量 + condition (str) : 公式 + """ + self.__vars = var + self.__command = command + + def run_command(self) -> List["InteractiveVariable"]: + """ + 执行操作 + + Returns: + List[InteractiveVariable] + """ + if self.__command == "": + return self.__vars + for code in self.__command.split(";"): + var_name_ = code.split("=")[0] + var_new_value = code.split("=")[1] + for var in self.__vars: + var_name = var.get_id() + var_value = var.get_value() + var_new_value = var_new_value.replace(var_name, str(var_value)) + var_new_value_calc = eval(var_new_value) + for var in self.__vars: + if var.get_id() == var_name_: + var._InteractiveVariable__var_value = var_new_value_calc # type: ignore + return self.__vars + + +class InteractiveNodeJumpingType(enum.Enum): + """ + 对下一节点的跳转的方式 + + - ASK : 选择 + - DEFAULT: 跳转到默认节点 + - READY : 选择(只有一个选择) + """ + + READY = 1 + DEFAULT = 0 + ASK = 2 + + +class Button: + def __init__(self, id_, pos, text, condition, command): + # A class that provides the button model. + self.node_id = id_ + self.pos = pos + self.text = text + self.condition = condition + self.command = command + # 什么?别问我为什么不用 TypedDict / dataclass + + def __str__(self) -> str: + return f"{self.pos} {self.text} {self.condition} {self.command}" + + +class ButtonLabel(QtWidgets.QLabel): + def __init__(self, parent: QtWidgets.QWidget = None): + super().__init__(parent=parent) + self.setObjectName(str(time.time())) + + def prep_text(self, text: str, x: int, y: int): + self.setText(text) + self.setWordWrap(True) + self.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + font = QtGui.QFont() + font.setPointSize(12) + font.setBold(True) + self.setFont(font) + rect = QtCore.QRect(x, y, 200, 50) + self.setGeometry(rect) + self.setStyleSheet( + "border-width: 5px;\ + border-style: solid;\ + border-color: rgb(100, 100, 100);\ + background-color: rgb(50, 50, 50);\ + color: rgb(255, 255, 255);" + ) + self.raise_() + return self + + +class MPlayer(object): + def setup(self, Form): + # UI + Form.setObjectName("Form") + Form.resize(800, 600) + sizePolicy = QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed + ) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) + Form.setSizePolicy(sizePolicy) + Form.setMinimumSize(QtCore.QSize(800, 650)) + Form.setMaximumSize(QtCore.QSize(800, 650)) + Form.setBaseSize(QtCore.QSize(800, 650)) + Form.setWindowTitle("MPlayer") + self.win: QtWidgets.QWidget = Form + self.player = QtMultimediaWidgets.QVideoWidget(Form) + self.player.setGeometry(QtCore.QRect(0, 0, 800, 450)) + self.player.setObjectName("player") + self.mediaplayer = QtMultimedia.QMediaPlayer() + self.mediaplayer.setVideoOutput(self.player) + self.audio_output = QtMultimedia.QAudioOutput() + self.audio_output.setVolume(0.0) + self.mediaplayer.setAudioOutput(self.audio_output) + self.slider = QtWidgets.QSlider(Form) + self.slider.setGeometry(QtCore.QRect(120, 455, 571, 22)) + self.slider.setOrientation(QtCore.Qt.Orientation.Horizontal) + self.slider.setObjectName("slider") + self.slider.setValue(100) + self.pp = QtWidgets.QPushButton(Form) + self.pp.setGeometry(QtCore.QRect(0, 450, 113, 32)) + self.pp.setObjectName("pp") + self.pushButton = QtWidgets.QPushButton(Form) + self.pushButton.setGeometry(QtCore.QRect(0, 525, 113, 32)) + self.pushButton.setObjectName("pushButton") + self.node = QtWidgets.QLabel(Form) + self.node.setGeometry(QtCore.QRect(120, 530, 191, 16)) + self.node.setObjectName("node") + self.info = QtWidgets.QLabel(Form) + self.info.setGeometry(QtCore.QRect(320, 520, 471, 36)) + self.info.setWordWrap(True) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.info.setFont(font) + self.info.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + self.info.setObjectName("info") + self.pushButton_2 = QtWidgets.QPushButton(Form) + self.pushButton_2.setGeometry(QtCore.QRect(0, 560, 113, 32)) + self.pushButton_2.setObjectName("pushButton_2") + self.lineEdit = QtWidgets.QLineEdit(Form) + self.lineEdit.setEnabled(True) + self.lineEdit.setGeometry(QtCore.QRect(120, 565, 561, 21)) + self.lineEdit.setText("") + self.lineEdit.setObjectName("lineEdit") + self.lineEdit.setPlaceholderText("Type in the path to your ivi file. ") + self.pushButton_3 = QtWidgets.QPushButton(Form) + self.pushButton_3.setGeometry(QtCore.QRect(690, 560, 108, 32)) + self.pushButton_3.setObjectName("pushButton_3") + self.pushButton_4 = QtWidgets.QPushButton(Form) + self.pushButton_4.setGeometry(QtCore.QRect(0, 485, 113, 32)) + self.pushButton_4.setObjectName("pushButton_4") + self.horizontalSlider = QtWidgets.QSlider(Form) + self.horizontalSlider.setGeometry(QtCore.QRect(120, 490, 571, 22)) + self.horizontalSlider.setOrientation(QtCore.Qt.Orientation.Horizontal) + self.horizontalSlider.setObjectName("horizontalSlider") + self.label = QtWidgets.QLabel(Form) + self.label.setGeometry(QtCore.QRect(699, 449, 81, 31)) + self.label.setObjectName("label") + self.label_2 = QtWidgets.QLabel(Form) + self.label_2.setGeometry(QtCore.QRect(700, 490, 60, 16)) + self.label_2.setObjectName("label_2") + + # Slot & String + self.pp.setEnabled(False) + self.pushButton.setEnabled(False) + self.retranslateUi(Form) + self.win.closeEvent = self.on_close_check + self.pushButton_2.clicked.connect(self.open_ivi) + self.pushButton_3.clicked.connect(self.close_ivi) + self.pushButton_4.clicked.connect(self.sound_on_off_event) + self.pp.clicked.connect(self.pp_button) + self.horizontalSlider.valueChanged.connect(self.volume_change_event) + self.slider.sliderReleased.connect(self.position_change_event) + self.slider.sliderPressed.connect(self.position_start_change_event) + self.pushButton.clicked.connect(self.back_to_previous) + + # InteractiveVariables + self.current_node = 0 + self.variables: List[InteractiveVariable] = [] + self.state_log = [] + self.graph = None + self.choice_buttons: List[Button] = [] + self.choice_labels: List[ButtonLabel] = [] + + # Video Play Variables & Functions + self.temp_dir = "" + self.is_draging_slider = False + self.is_stoping = False + self.win.startTimer(100) + self.has_end = False + self.final_position = -1 + + # Timer & Refresh + def timerEvent(*args, **kwargs): + # 创建要跳转的节点 + if self.has_end: + if len(self.choice_labels) != 0: + for lbl in self.choice_labels: + lbl.raise_() + self.player.lower() + else: + children = self.graph[str(self.current_node)]["sub"] + if len(children) == 0: + # 已结束 + pass + else: + # 跳转类型 + if ( + self.graph[str(children[0])]["jump_type"] + == InteractiveNodeJumpingType.DEFAULT.value + ): + # 直接跳转 + for node_id in children: + btn = Button( + node_id, + [0, 0], + "", + self.graph[str(node_id)]["condition"], + self.graph[str(node_id)]["command"], + ) + condition = InteractiveJumpingCondition( + self.variables, btn.condition + ) + if condition.get_result(): + # 可以跳转 + native_command = InteractiveJumpingCommand( + self.variables, btn.command + ) + self.variables = native_command.run_command() + btn_id = btn.node_id + self.set_source(self.graph[str(btn_id)]["cid"]) + self.current_node = btn.node_id + self.volume_change_event() + title = self.graph[str(node_id)]["title"] + self.node.setText(f"(当前节点: {title})") + break + else: + # 进行选择 + def get_info(node_id: int): + return self.graph[str(node_id)] + + cnt = 0 + for idx, child in enumerate(children): + pos_x = cnt * 200 + pos_y = 600 + cur_info = get_info(child) + # 生成 Button 对象 + self.choice_buttons.append( + Button( + child, + [pos_x, pos_y], + cur_info["button"]["text"], + cur_info["condition"], + cur_info["command"], + ) + ) + # 生成 ButtonLabel 对象 + if cur_info["button"]["pos"][0] == None: + if idx != 0: + previous_info = get_info(children[idx - 1]) + curtext, previoustext = ( + cur_info["button"]["text"], + previous_info["button"]["text"], + ) + if curtext[2:] == previoustext[2:]: + # 可确定与上一个按钮同一个位置(即概率按钮) + # 不再生成单独的 label + self.choice_buttons[-1].pos[0] -= 200 + continue + cnt += 1 + lbl = ButtonLabel(self.win) + lbl.prep_text( + cur_info["button"]["text"], pos_x, pos_y + ) + lbl.show() + self.choice_labels.append(lbl) + continue + if idx != 0: + previous_info = get_info(children[idx - 1]) + curpos, previouspos = ( + cur_info["button"]["pos"], + previous_info["button"]["pos"], + ) + if (abs(curpos[0] - previouspos[0]) <= 5) and ( + abs(curpos[1] - previouspos[1]) <= 5 + ): + # 可确定与上一个按钮同一个位置(即概率按钮) + # 不再生成单独的 label + self.choice_buttons[-1].pos[0] -= 200 + else: + # 生成 label + cnt += 1 + lbl = ButtonLabel(self.win) + lbl.prep_text( + cur_info["button"]["text"], pos_x, pos_y + ) + lbl.show() + self.choice_labels.append(lbl) + else: + # 生成 label + cnt += 1 + lbl = ButtonLabel(self.win) + lbl.prep_text( + cur_info["button"]["text"], pos_x, pos_y + ) + lbl.show() + self.choice_labels.append(lbl) + pass + add_space = int((800 - cnt * 200) / 2) + for idx, lbl in enumerate(self.choice_labels): + lbl.setGeometry( + QtCore.QRect( + lbl.geometry().left() + add_space, + lbl.geometry().top(), + lbl.geometry().width(), + lbl.geometry().height(), + ) + ) + for btn in self.choice_buttons: + btn.pos[0] += add_space + # 处理进度条 + if self.is_draging_slider: + return + if self.mediaplayer.duration() == 0: + self.slider.setValue(100) + self.label.setText("--:--/--:--") + return + if ( + (self.mediaplayer.duration() // 1000) + == ((self.mediaplayer.position() // 1000)) + ) and (not self.has_end): + self.has_end = True + self.mediaplayer.pause() + self.final_position = self.mediaplayer.position() + self.mediaplayer.setAudioOutput( + QtMultimedia.QAudioOutput().setVolume(0) + ) + self.slider.setValue(100) + duration = self.mediaplayer.duration() // 1000 + duration_sec = duration % 60 + duration_min = duration // 60 + if duration_sec < 10: + duration_sec = "0" + str(duration_sec) + if duration_min < 10: + duration_min = "0" + str(duration_min) + self.label.setText( + f"{duration_min}:{duration_sec}/{duration_min}:{duration_sec}" + ) + self.player.lower() + for lbl in self.choice_labels: + lbl.raise_() + return + elif self.has_end: + self.has_end = True + self.slider.setValue(100) + self.mediaplayer.setPosition(self.final_position) + self.mediaplayer.setAudioOutput( + QtMultimedia.QAudioOutput().setVolume(0) + ) + duration = self.mediaplayer.duration() // 1000 + duration_sec = duration % 60 + duration_min = duration // 60 + if duration_sec < 10: + duration_sec = "0" + str(duration_sec) + if duration_min < 10: + duration_min = "0" + str(duration_min) + self.label.setText( + f"{duration_min}:{duration_sec}/{duration_min}:{duration_sec}" + ) + self.player.lower() + for lbl in self.choice_labels: + lbl.raise_() + return + else: + self.has_end = False + self.choice_buttons = [] + for lbl in self.choice_labels: + lbl.hide() + self.choice_labels = [] + self.last_position = self.mediaplayer.position() + self.slider.setValue( + int(self.mediaplayer.position() / self.mediaplayer.duration() * 100) + ) + duration = self.mediaplayer.duration() // 1000 + position = self.mediaplayer.position() // 1000 + duration_sec = duration % 60 + duration_min = duration // 60 + position_sec = position % 60 + position_min = position // 60 + if duration_sec < 10: + duration_sec = "0" + str(duration_sec) + if duration_min < 10: + duration_min = "0" + str(duration_min) + if position_sec < 10: + position_sec = "0" + str(position_sec) + if position_min < 10: + position_min = "0" + str(position_min) + self.label.setText( + f"{position_min}:{position_sec}/{duration_min}:{duration_sec}" + ) + # 将选择的按钮置于最上层 + for lbl in self.choice_labels: + lbl.raise_() + + self.win.timerEvent = timerEvent + + # Click & Jump + def mouseReleaseEvent(event: QtGui.QMouseEvent): + pos = event.position() + pos = [pos.x(), pos.y()] + for var in self.variables: + if var.is_random(): + var._InteractiveVariable__var_value = random.random() * 100 + for btn in self.choice_buttons: + if ( + (pos[0] - btn.pos[0] <= 200) + and (pos[0] - btn.pos[0] >= 0) + and (pos[1] - btn.pos[1] <= 50) + and (pos[1] - btn.pos[1] >= 0) + ): + condition = InteractiveJumpingCondition( + self.variables, btn.condition + ) + if condition.get_result(): + # 可以跳转 + native_command = InteractiveJumpingCommand( + self.variables, btn.command + ) + self.variables = native_command.run_command() + btn_id = btn.node_id + self.set_source(self.graph[str(btn_id)]["cid"]) + self.current_node = btn.node_id + self.volume_change_event() + title = self.graph[str(btn.node_id)]["title"] + self.node.setText(f"(当前节点: {title})") + break + + self.win.mouseReleaseEvent = mouseReleaseEvent + + def start_playing(self): + self.mediaplayer.play() + self.is_stoping = False + + def stop_playing(self): + self.mediaplayer.stop() + self.is_stoping = True + + def pause_playing(self): + self.mediaplayer.pause() + self.is_stoping = True + + def retranslateUi(self, Form): + _translate = QtCore.QCoreApplication.translate + self.pp.setText(_translate("Form", "Pause")) + self.pushButton.setText(_translate("Form", "<- Previous")) + self.node.setText(_translate("Form", "(当前节点: 无)")) + self.info.setText(_translate("Form", "视频标题(BVID)")) + self.pushButton_2.setText(_translate("Form", "Open")) + self.pushButton_3.setText(_translate("Form", "Close")) + self.pushButton_4.setText(_translate("Form", "Sound: Off")) + self.label.setText(_translate("Form", "--:--/--:--")) + self.label_2.setText(_translate("Form", "0")) + + def set_source(self, cid: int): + wintitle = "MPlayer" + for var in self.variables: + if var.is_show(): + wintitle += f" - {var.get_name()}: {int(var.get_value())}" + for lbl in self.choice_labels: + lbl.hide() + self.choice_labels = [] + self.choice_buttons = [] + self.win.setWindowTitle(wintitle) + self.state_log.append({"cid": cid, "vars": copy.deepcopy(self.variables)}) + self.has_end = False + self.mediaplayer.setAudioOutput( + QtMultimedia.QAudioOutput().setVolume(self.horizontalSlider.value() / 100) + ) + self.stop_playing() + self.pp.setText("Pause") + dest = self.temp_dir + str(cid) + ".mp4" + self.mediaplayer.setSource(QtCore.QUrl(dest)) + self.mediaplayer.setPosition(0) + if self.mediaplayer.duration() <= 7: + self.mediaplayer.setPosition(self.mediaplayer.duration()) + self.start_playing() + + def extract_ivi(self, path: str): + curtime = str(time.time()) + try: + os.mkdir(".mplayer") + except: + pass + os.mkdir(".mplayer/" + curtime) + self.temp_dir = ".mplayer/" + curtime + "/" + ivi = zipfile.ZipFile(path) + ivi.extractall(self.temp_dir) + bilivideo_parser = json.JSONDecoder() + self.node.setText("(当前节点: 视频主节点)") + self.info.setText( + bilivideo_parser.decode( + open(self.temp_dir + "bilivideo.json", "r", encoding="utf-8").read() + )["title"] + + "(" + + bilivideo_parser.decode( + open(self.temp_dir + "bilivideo.json", "r", encoding="utf-8").read() + )["bvid"] + + ")" + ) + self.graph = json.load( + open(self.temp_dir + "ivideo.json", "r", encoding="utf-8") + ) + self.current_node = 1 + variables = self.graph["1"]["vars"] + for var in variables: + self.variables.append( + InteractiveVariable( + var["name"], var["id"], var["value"], var["show"], var["random"] + ) + ) + self.set_source(self.graph["1"]["cid"]) + self.volume_change_event() + + def close_ivi(self): + self.player.hide() + self.player = QtMultimediaWidgets.QVideoWidget(self.win) + self.player.setGeometry(QtCore.QRect(0, 0, 800, 450)) + self.player.setObjectName("player") + self.player.show() + self.current_node = 0 + self.variables = [] + self.state_log = [] + self.choice_buttons = [] + for lbl in self.choice_labels: + lbl.hide() + self.choice_labels = [] + self.graph = None + self.stop_playing() + self.pp.setText("Pause") + self.has_end = False + self.mediaplayer = QtMultimedia.QMediaPlayer() # Clear the multimedia source + self.mediaplayer.setVideoOutput(self.player) + self.mediaplayer.setAudioOutput(QtMultimedia.QAudioOutput()) + self.volume_change_event() + shutil.rmtree(self.temp_dir) + while True: + if not os.path.exists(self.temp_dir): + break + try: + os.rmdir(".mplayer") + except: + # Not empty + pass + self.temp_dir = "" + self.node.setText("(当前节点: 无)") + self.info.setText("视频标题(BVID)") + self.win.setWindowTitle("MPlayer") + self.lineEdit.setText("") + self.pp.setEnabled(False) + self.pushButton.setEnabled(False) + + def open_ivi(self): + if self.current_node != 0: + return + try: + if self.lineEdit.text() != "": + self.extract_ivi(self.lineEdit.text()) + else: + dialog = QtWidgets.QFileDialog() + filename, _ = dialog.getOpenFileName( + self.win, + "Choose an 'ivi' file to open. ", + filter="Bilibili Interactive Video (*.ivi)", + ) + self.extract_ivi(filename) + self.lineEdit.setText(filename) + self.pp.setEnabled(True) + self.pushButton.setEnabled(True) + except Exception as e: + warning = QtWidgets.QMessageBox() + warning.warning(self.win, "Oops...", str(e)) + + def volume_change_event(self): + if self.horizontalSlider.value() == 0: + self.pushButton_4.setText("Sound: Off") + else: + self.pushButton_4.setText("Sound: On") + position = self.mediaplayer.position() + if (not self.has_end) or (not self.is_stoping): + pass + else: + self.pause_playing() + volume = self.horizontalSlider.value() + self.label_2.setText(str(volume)) + self.audio_output.setVolume(float(volume / 100)) + self.mediaplayer.setAudioOutput(self.audio_output) + if (not self.has_end) or (not self.is_stoping): + pass + else: + self.mediaplayer.setPosition(position) + self.start_playing() + + def position_start_change_event(self): + self.mediaplayer.pause() + self.is_draging_slider = True + + def position_change_event(self): + volume = self.slider.value() + if volume != 100 and self.has_end: + self.slider.setValue(100) + return + self.mediaplayer.setPosition(int(self.mediaplayer.duration() * volume / 100)) + if not self.is_stoping: + self.start_playing() + self.is_draging_slider = False + + def sound_on_off_event(self): + if "on" in self.pushButton_4.text().lower(): + self.pushButton_4.setText("Sound: Off") + curpos = self.mediaplayer.position() + self.mediaplayer.pause() + volume = self.horizontalSlider.value() + self.label_2.setText(str(volume)) + self.audio_output = QtMultimedia.QAudioOutput() + self.audio_output.setVolume(0.0) + self.mediaplayer.setAudioOutput(self.audio_output) + self.mediaplayer.setPosition(curpos) + if not self.is_stoping: + self.start_playing() + self.horizontalSlider.setSliderPosition(0) + else: + self.pushButton_4.setText("Sound: On") + curpos = self.mediaplayer.position() + self.mediaplayer.pause() + volume = self.horizontalSlider.value() + self.label_2.setText(str(volume)) + self.audio_output = QtMultimedia.QAudioOutput() + self.audio_output.setVolume(1.0) + self.mediaplayer.setAudioOutput(self.audio_output) + self.mediaplayer.setPosition(curpos) + if not self.is_stoping: + self.start_playing() + self.horizontalSlider.setSliderPosition(100) + + def pp_button(self): + if self.is_stoping: + self.start_playing() + self.pp.setText("Pause") + else: + self.pause_playing() + self.pp.setText("Play") + + def on_close_check(self, event): + if self.current_node != 0: + reply = QtWidgets.QMessageBox.question( + self.win, + "WARNING", + "IVI file is playing. Are you sure want to exit? ", + QtWidgets.QMessageBox.StandardButton.Yes + | QtWidgets.QMessageBox.StandardButton.No, + QtWidgets.QMessageBox.StandardButton.No, + ) + if reply == QtWidgets.QMessageBox.StandardButton.Yes: + self.close_ivi() + event.accept() + else: + event.ignore() + else: + event.accept() + + def back_to_previous(self): + if len(self.state_log) < 2: + QtWidgets.QMessageBox.warning( + self.win, + "WTF???", + "MPlayer can't find the previous node. \nMaybe there's not any node or only one node?", + ) + return + new_cid = copy.deepcopy(self.state_log[-2]["cid"]) + new_vars = copy.deepcopy(self.state_log[-2]["vars"]) + self.state_log.pop() + for key in self.graph.keys(): + if self.graph[key]["cid"] == new_cid: + new_node_id = int(key) + self.current_node = new_node_id + self.variables = new_vars + self.set_source(new_cid) + self.state_log.pop() + title = self.graph[str(new_node_id)]["title"] + self.node.setText(f"(当前节点: {title})") + self.volume_change_event() + return + + +def main(): + app = QtWidgets.QApplication(sys.argv) + win = QtWidgets.QMainWindow() + ui = MPlayer() + ui.setup(win) + win.show() + sys.exit(app.exec()) + + +def prepopen(path: str): + app = QtWidgets.QApplication(sys.argv) + win = QtWidgets.QMainWindow() + ui = MPlayer() + ui.setup(win) + ui.lineEdit.setText(path) + ui.open_ivi() + win.show() + sys.exit(app.exec()) + + +if __name__ == "__main__": + main() diff --git a/bilibili_api/tools/ivitools/scan.py b/bilibili_api/tools/ivitools/scan.py new file mode 100644 index 0000000000000000000000000000000000000000..dd994c5a6ec7f09e2e90cc6ee371c3f5e02fef25 --- /dev/null +++ b/bilibili_api/tools/ivitools/scan.py @@ -0,0 +1,53 @@ +""" +ivitools.scan + +扫描 ivi 文件相关 +""" +import os +import json +import time +import zipfile +import tempfile + +from tqdm import tqdm +from colorama import Fore, Cursor +from colorama.ansi import clear_line + +from .touch import touch_ivi + + +def scan_ivi_file(path: str): + print(Fore.RESET + f"Scanning {path} ...") + # First, make a temp folder. + tmp_dir = tempfile.gettempdir() + if not os.path.exists(os.path.join(tmp_dir, "ivitools")): + os.mkdir(os.path.join(tmp_dir, "ivitools")) + for root, dirs, files in os.walk(os.path.join(tmp_dir, "ivitools"), topdown=False): + for name in files: + os.remove(os.path.join(root, name)) + for name in dirs: + os.rmdir(os.path.join(root, name)) + # Then, extract the ivi file. + extract_dir = os.path.join(tmp_dir, "ivitools", str(time.time())) + # Use the time to make folders different + zipfile.ZipFile(open(path, "rb")).extractall(extract_dir) + # Finally, display the result. + Cursor.UP() + clear_line() + print(path) + meta = touch_ivi(path) + print(f'{meta["title"]}({meta["bvid"]})') + graph = json.load(open(os.path.join(extract_dir, "ivideo.json"), encoding="utf-8")) + print(f"There're {len(graph.keys())} nodes in the file! ") + bar = tqdm(graph.keys()) + for item in bar: + bar.set_description("Scanning node ") + if not ("cid" in graph[str(item)].keys()): + raise Exception(f"Missing CID in the node {item}") + return + else: + cid = graph[str(item)]["cid"] + if not (os.path.exists(os.path.join(extract_dir, str(cid) + ".mp4"))): + raise Exception(f"Missing video source of the node {item}") + time.sleep(0.01) + print(Fore.GREEN + "Congratulation! Your file is OK. ", Fore.RESET) diff --git a/bilibili_api/tools/ivitools/touch.py b/bilibili_api/tools/ivitools/touch.py new file mode 100644 index 0000000000000000000000000000000000000000..7901a7913fd3ac781478155bd799669696fed6a5 --- /dev/null +++ b/bilibili_api/tools/ivitools/touch.py @@ -0,0 +1,14 @@ +""" +ivitools.touch + +获取 ivi 文件信息 +""" +import json +import zipfile + + +def touch_ivi(path: str): + ivi = zipfile.ZipFile(open(path, "rb")) + info = ivi.open("bilivideo.json").read() + print(json.loads(info)) + return json.loads(info) diff --git a/bilibili_api/tools/opendocs/__init__.py b/bilibili_api/tools/opendocs/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bilibili_api/tools/opendocs/__main__.py b/bilibili_api/tools/opendocs/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..90a1db0e7fcc39c325682e95992dc3db8b15ae5e --- /dev/null +++ b/bilibili_api/tools/opendocs/__main__.py @@ -0,0 +1,38 @@ +# Bibibili API Documentions + +import sys +import webbrowser + +try: + from PyQt5 import QtGui, QtCore, QtWidgets, QtWebEngineWidgets +except: + PYQT5 = False +else: + PYQT5 = True + + +def main(): + if not PYQT5: + webbrowser.open("https://nemo2011.github.io/bilibili-api") + else: + QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling) + app = QtWidgets.QApplication(sys.argv) + mainwindow = QtWidgets.QMainWindow() + mainwindow.resize(800, 600) + webengine = QtWebEngineWidgets.QWebEngineView(mainwindow) + webengine.setGeometry(QtCore.QRect(0, 0, 800, 600)) + webengine.setUrl(QtCore.QUrl("https://nemo2011.github.io/bilibili-api")) + + def timerEvent(*args, **kwargs): + webengine.setGeometry( + QtCore.QRect(0, 0, mainwindow.width(), mainwindow.height()) + ) + + mainwindow.timerEvent = timerEvent + mainwindow.startTimer(1) + mainwindow.show() + sys.exit(app.exec_()) + + +if __name__ == "__main__": + main() diff --git a/bilibili_api/tools/opendocs/__pycache__/__init__.cpython-38.pyc b/bilibili_api/tools/opendocs/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e233494e7a62d3ea5b0ecd3c096aead2e18bf6f0 Binary files /dev/null and b/bilibili_api/tools/opendocs/__pycache__/__init__.cpython-38.pyc differ diff --git a/bilibili_api/tools/opendocs/__pycache__/__main__.cpython-38.pyc b/bilibili_api/tools/opendocs/__pycache__/__main__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15dd14572e422186af9e8d40997b08925ad84814 Binary files /dev/null and b/bilibili_api/tools/opendocs/__pycache__/__main__.cpython-38.pyc differ diff --git a/bilibili_api/tools/parser/Card.vue b/bilibili_api/tools/parser/Card.vue new file mode 100644 index 0000000000000000000000000000000000000000..d2480fcb35e8709c772cc810efce7f36b2f74fa2 --- /dev/null +++ b/bilibili_api/tools/parser/Card.vue @@ -0,0 +1,120 @@ + + + + + \ No newline at end of file diff --git a/bilibili_api/tools/parser/README.md b/bilibili_api/tools/parser/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8f63c734fba62c930063977dbda77439c976cf78 --- /dev/null +++ b/bilibili_api/tools/parser/README.md @@ -0,0 +1,98 @@ +### 这是一个解析器 + +```python +# 需要单独安装 fastapi 和 uvicorn + +from bilibili_api.tools.parser import get_fastapi + +import uvicorn + +if __name__ == "__main__": + uvicorn.run(get_fastapi(), host="0.0.0.0", port=9000) +``` + +以上代码可以用来开启一个 `uvicorn` 后端,配合同目录下 `Card.vue` 可实现一个简单的 bilibili 主播卡片。 + +
+ +![card](https://user-images.githubusercontent.com/41439182/216977177-5575ebcf-2596-4053-84e9-19b1d44c3f33.png) + +
+ +--- + +### 起因 + +很久很久之前,我刚来到这个仓库,当时有个 Issue #31 他说: + +> 这个包可以在脚手架里用吗,我在vue-cli开发模式下启动直接提示跨域 + +解决办法是在后端写请求,例如使用 fastapi + uvicorn 开一个后端,自己写接口。 + +开始我不懂啥意思,直到后来我也写了点 vue ,用到了 bilibili 的接口发现跨域,我就打算按照那个方法写后端。 + +但是一个个重新写接口名再找对应函数确实很累,所以我写了这个解析器 + +--- + +### 用法 + +这段代码我已经部署在阿里云的函数计算里了,域名:[https://aliyun.nana7mi.link](https://aliyun.nana7mi.link) + +```python +from bilibili_api import user, sync + +async def main(): + return await user.User(uid=2).get_user_info() + +print(sync(main())) +``` + +上述代码现在只需要一个链接:[https://aliyun.nana7mi.link/user.User(2).get_user_info()](https://aliyun.nana7mi.link/user.User(2).get_user_info()) 就能实现。 + +属于是从接口来回接口去了。 + +类似的还有 [https://aliyun.nana7mi.link/live.LiveRoom(21452505).get_room_info()](https://aliyun.nana7mi.link/live.LiveRoom(21452505).get_room_info()) + +--- + +### FAQ + +> Q1. 这个有什么用呢? + +前端访问不跨域了。 + +> Q2. 为什么要解析器,直接用 eval() 不好吗? + +有安全隐患,用解析器这样一步一步调用比较安全。 + +--- + +### 进阶用法 + +使用网址 params 请求参数储存值。 + +[https://aliyun.nana7mi.link/comment.get_comments(708326075350908930,type,1:int)?type=comment.CommentResourceType.DYNAMIC](https://aliyun.nana7mi.link/comment.get_comments(708326075350908930,type,1:int)?type=comment.CommentResourceType.DYNAMIC) + +这个变量是另一个需要被解析的文本,为什么不直接放在网址里呢?因为放前面会被当做字符串传进去。 + +同时为了不让所有参数都以字符串传入,还加了类型标注,在变量后使用类似 `:int` 的方式来强制转换,目前支持 `:int` `:float` `:bool` `:parse`。 + +其中 `:parse` 较为特殊,它的作用是解析前面这个字符串,用前面这个网址举例 + +[https://aliyun.nana7mi.link/comment.get_comments(708326075350908930,comment.CommentResourceType.DYNAMIC:parse,1:int)](https://aliyun.nana7mi.link/comment.get_comments(708326075350908930,comment.CommentResourceType.DYNAMIC:parse,1:int)) + +--- + +### 再高级一点呢 + +使用 `?max_age=86400` 参数设置为期 86400 秒的缓存。 + +在获取的字典结果后再使用 `.key` 的方式获得更精细数据,节省带宽,例如: + +[https://aliyun.nana7mi.link/user.User(2).get_user_info().face?max_age=86400](https://aliyun.nana7mi.link/user.User(2).get_user_info().face?max_age=86400) + +对于列表结果可以使用 `.index` 的方式获取列表中对应元素,例如: + +[https://aliyun.nana7mi.link/user.User(660303135).get_dynamics(0:int).cards.3.card](https://aliyun.nana7mi.link/user.User(660303135).get_dynamics(0:int).cards.3.card) + diff --git a/bilibili_api/tools/parser/__init__.py b/bilibili_api/tools/parser/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..019a2f64508185900948f8642ba344f2ede59ce4 --- /dev/null +++ b/bilibili_api/tools/parser/__init__.py @@ -0,0 +1,8 @@ +from .parser import Parser +from .app import get_fastapi, bilibili_api_web + +__all__ = [ + "bilibili_api_web", + "get_fastapi", + "Parser", +] \ No newline at end of file diff --git a/bilibili_api/tools/parser/__pycache__/__init__.cpython-38.pyc b/bilibili_api/tools/parser/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9bc8abea7e9287ce599e736654e9ee5eba6f5583 Binary files /dev/null and b/bilibili_api/tools/parser/__pycache__/__init__.cpython-38.pyc differ diff --git a/bilibili_api/tools/parser/__pycache__/app.cpython-38.pyc b/bilibili_api/tools/parser/__pycache__/app.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..999213aeb33bb4b6b171dbd518be7ac0086babd9 Binary files /dev/null and b/bilibili_api/tools/parser/__pycache__/app.cpython-38.pyc differ diff --git a/bilibili_api/tools/parser/__pycache__/parser.cpython-38.pyc b/bilibili_api/tools/parser/__pycache__/parser.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b3e640a71cddb0f0f2ad27ef4dbf8020715b764c Binary files /dev/null and b/bilibili_api/tools/parser/__pycache__/parser.cpython-38.pyc differ diff --git a/bilibili_api/tools/parser/app.py b/bilibili_api/tools/parser/app.py new file mode 100644 index 0000000000000000000000000000000000000000..a0cbf2c52fc2e74ce3b786f66ad0259ed9afc4fd --- /dev/null +++ b/bilibili_api/tools/parser/app.py @@ -0,0 +1,74 @@ +from typing import Any, Dict + +from fastapi import FastAPI, Request, Response + +from .parser import Parser + + +def Result(code: int, data: Any) -> dict: + """ + 请求结果 + + Args: + code (int): 状态码 + + data (Any): 返回数据 + + Result: + dict: 返回体 + """ + return { + "code": code, + "data" if code == 0 else "error": data + } + + +async def bilibili_api_web( + request: Request, + response: Response, + path: str, +) -> dict: + """ + 网络接口 + + Args: + request (request): 请求 + + response (Response): 响应 + + path (str): 请求路径 例如 `user.User(434334701).get_user_info()` + + max_age (int, Optional): 请求头 `Cache-Control` 中的 `max-age` + + params (str, Optional): 可选变量 如果 path 中多次用到某个值可保存 + + 例如 `?uid=434334701&roomid=21452505` + + Returns: + dict: 解析结果 + """ + # 返回头设置 + vars: Dict[str, str] = dict(request.query_params) + max_age = vars.pop("max_age", None) + if max_age is not None: + response.headers["Cache-Control"] = f"max-age={max_age}" + response.headers["Access-Control-Allow-Origin"] = "*" + + # 先判断是否有效 再分析 + try: + async with Parser(vars) as parser: + if not parser.valid: + return Result(1, "Credential 验证失败") + obj, err = await parser.parse(path) # 什么 golang 写法 + if err is None: + return Result(0, obj) + return Result(2, f"解析语句 {err} 错误") + except Exception as e: + return Result(3, f"未知错误 {e}") + + +def get_fastapi(app_run_path: str = "/{path}") -> FastAPI: + app = FastAPI() + app_run_path = app_run_path if "{path}" in app_run_path else "/{path}" + app.add_api_route(app_run_path, bilibili_api_web, methods=["GET"]) + return app \ No newline at end of file diff --git a/bilibili_api/tools/parser/parser.py b/bilibili_api/tools/parser/parser.py new file mode 100644 index 0000000000000000000000000000000000000000..477d89b4763f8303253e086271ac3c308608c2ef --- /dev/null +++ b/bilibili_api/tools/parser/parser.py @@ -0,0 +1,145 @@ +import re +from enum import Enum +from inspect import isclass +from inspect import isfunction as isFn +from inspect import iscoroutinefunction as isAsync +from typing import Any, Dict, List, Tuple, Optional + +import bilibili_api + +FUNC = re.compile(r"[^\.\(]+") +ARGS = re.compile(r"[^,\(\)]*[,\)]") +SENTENCES = re.compile(r"\w+(?:\([^\)]*\))?\.?") +OPS = { + ":int": int, + ":float": float, + ":bool": lambda s: s == "True", +} + + +class Parser: + """ + 解析器 + """ + + def __init__(self, params: Dict[str, str]): + self.valid = True + self.params = params + + async def __aenter__(self): + """ + 解析前准备 + + 把 params 中的参数先解析了 + """ + for key, val in self.params.items(): + obj, err = await self.parse(val) + if err is None: + if isinstance(obj, bilibili_api.Credential): + self.valid = await bilibili_api.check_valid(obj) + self.params[key] = obj + return self + + async def __aexit__(self, type, value, trace): + ... + + async def transform(self, var: str) -> Any: + """ + 类型装换函数 + + 通过在字符串后加上 `:int` `:float` `:bool` `:parse` 等操作符来实现 + + Args: + var (str): 需要转换的字符串 + + Returns: + Any: 装换结果 + """ + for key, fn in OPS.items(): + if var.endswith(key): + return fn(var.replace(key, "")) + if var.endswith(":parse"): + obj, err = await self.parse(var.replace(":parse", "")) + if err is None: + return obj + + # 是请求时 params 中定义的变量 获取出来 不是的话返回原字符 + return self.params.get(var, var) + + async def parse(self, path: str) -> Tuple[Any, Optional[str]]: + """ + 分析指令 + + Args: + path (str): 需要解析的 token 对应库中的路径 + + Returns: + Any: 最终数据 若解析失败为 None + + str: 错误信息 若解析成功为 None + """ + # 纯数字 + if path.replace(":int", "").replace(":float", "").replace(".", "").replace("-", "").isdigit(): + return await self.transform(path), None + + # 指令列表 + sentences = SENTENCES.findall(path) + # 起始点 + position: Any = bilibili_api + + async def inner() -> Optional[str]: + """ + 递归取值 + + Returns: + str: 错误信息 若解析成功为 None + """ + nonlocal position + # 分解执行的函数名、参数、指名参数 + sentence = sentences.pop(0) + func: str = FUNC.findall(sentence)[0] + flags: List[str] = ARGS.findall(sentence) + args, kwargs = [], {} + + for flag in flags: + # 去除句尾的逗号或小括号 + flag = flag[:-1] + if flag == "": + continue + + # 通过判断是否有等号存入参数列表或指名参数字典 + arg = flag.split("=") + if len(arg) == 1: + args.append(await self.transform(arg[0])) + else: + kwargs[arg[0]] = await self.transform(arg[1]) + + # print(position, func, args, kwargs) + + # 开始转移 + if isinstance(position, dict): + position = position.get(func, None) + elif isinstance(position, list): + position = position[int(func)] + else: + position = getattr(position, func, None) + + # 赋值参数 + if isAsync(position): + position = await position(*args, **kwargs) + elif isFn(position): + position = position(*args, **kwargs) + elif isclass(position) and not issubclass(position, Enum): + position = position(*args, **kwargs) + + # 为空返回出错语句 + # 否则检查是否分析完全部语句 + # 是则返回 None 否继续递归 + if position is None: + return sentence + if len(sentences) == 0: + return None + return await inner() + + msg = await inner() + return position, msg diff --git a/bilibili_api/topic.py b/bilibili_api/topic.py new file mode 100644 index 0000000000000000000000000000000000000000..557ec4b31103fe2e1291d50c42462047ee23ed3c --- /dev/null +++ b/bilibili_api/topic.py @@ -0,0 +1,174 @@ +""" +bilibili_api.topic + +话题相关 +""" + +from enum import Enum +from typing import Union, Optional + +from . import dynamic +from .user import get_self_info +from .utils.utils import get_api +from .utils.credential import Credential +from .utils.network import Api + +API = get_api("topic") + + +class TopicCardsSortBy(Enum): + """ + 话题下内容排序方式 + + + NEW: 最新 + + HOT: 最热 + + RECOMMEND: 推荐 + """ + + NEW = 3 + HOT = 2 + RECOMMEND = 1 + + +async def get_hot_topics(numbers: int = 33) -> dict: + """ + 获取动态页的火热话题 + + Args: + numbers (int): 话题数量. Defaults to 33. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["dynamic_page_topics"] + params = {"page_size": numbers} + return await Api(**api).update_params(**params).result + + +async def search_topic(keyword: str, ps: int = 20, pn: int = 1) -> dict: + """ + 搜索话题 + + 从动态页发布动态处的话题搜索框搜索话题 + + Args: + keyword (str): 搜索关键词 + + ps (int): 每页数量. Defaults to 20. + + pn (int): 页数. Defaults to 1. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["search"] + params = {"keywords": keyword, "page_size": ps, "page_num": pn} + return await Api(**api).update_params(**params).result + + +class Topic: + """ + 话题类 + + Attributes: + credential (Credential): 凭据类 + """ + + def __init__(self, topic_id: int, credential: Union[Credential, None] = None): + """ + Args: + topic_id (int) : 话题 id + + credential (Credential): 凭据类 + """ + self.__topic_id = topic_id + self.credential = credential if credential else Credential() + + def get_topic_id(self) -> int: + """ + 获取话题 id + + Returns: + int: 话题 id + """ + return self.__topic_id + + async def get_info(self) -> dict: + """ + 获取话题简介 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["info"] + params = {"topic_id": self.get_topic_id()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_cards( + self, + ps: int = 100, + offset: Optional[str] = None, + sort_by: TopicCardsSortBy = TopicCardsSortBy.HOT, + ) -> dict: + """ + 获取话题下的内容 + + 未登录无法使用热门排序字段即 TopicCardsSortBy.RECOMMEND + + Args: + ps (int): 数据数量. Defaults to 100. + + offset (Optional[str]): 偏移量. 生成格式为 f'{页码}_{页码*数据量]}' 如'2_40' Defaults to None. + + sort_by (TopicCardsSortBy): 排序方式. Defaults to TopicCardsSortBy.HOT. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["cards"] + params = { + "topic_id": self.get_topic_id(), + "page_size": ps, + "sort_by": sort_by.value + } + if offset: + params.update({"offset": offset}) + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + + async def like(self, status: bool = True) -> dict: + """ + 设置点赞话题 + + Args: + status (bool): 是否设置点赞. Defaults to True. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["operate"]["like"] + data = { + "topic_id": self.get_topic_id(), + "action": "like" if status else "cancel_like", + "business": "topic", + "up_mid": (await get_self_info(self.credential))["mid"], + } + return await Api(**api, credential=self.credential).update_data(**data).result + + async def set_favorite(self, status: bool = True) -> dict: + """ + 设置收藏话题 + + Args: + status (bool): 是否设置收藏. Defaults to True. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["operate"]["add_favorite" if status else "cancel_favorite"] + data = {"topic_id": self.get_topic_id()} + return await Api(**api, credential=self.credential).update_data(**data).result diff --git a/bilibili_api/user.py b/bilibili_api/user.py new file mode 100644 index 0000000000000000000000000000000000000000..515152392036b6d7f137c0b5b3f9115c3ae013c3 --- /dev/null +++ b/bilibili_api/user.py @@ -0,0 +1,1548 @@ +""" +bilibili_api.user + +用户相关 +""" + +import json +import time +from enum import Enum +from typing import List, Union, Tuple +from json.decoder import JSONDecodeError + +from .utils.utils import get_api, join, raise_for_statement +from .utils.credential import Credential +from .exceptions import ResponseCodeException +from .utils.network import get_session, Api +from .channel_series import ChannelOrder, ChannelSeries, ChannelSeriesType + +API = get_api("user") + + +class VideoOrder(Enum): + """ + 视频排序顺序。 + + + PUBDATE : 上传日期倒序。 + + FAVORITE: 收藏量倒序。 + + VIEW : 播放量倒序。 + """ + + PUBDATE = "pubdate" + FAVORITE = "stow" + VIEW = "click" + + +class MedialistOrder(Enum): + """ + medialist排序顺序。 + + + PUBDATE : 上传日期。 + + PLAY : 播放量。 + + COLLECT : 收藏量。 + """ + + PUBDATE = 1 + PLAY = 2 + COLLECT = 3 + + +class AudioOrder(Enum): + """ + 音频排序顺序。 + + + PUBDATE : 上传日期倒序。 + + FAVORITE: 收藏量倒序。 + + VIEW : 播放量倒序。 + """ + + PUBDATE = 1 + VIEW = 2 + FAVORITE = 3 + + +class AlbumType(Enum): + """ + 相册类型 + + + ALL : 全部。 + + DRAW: 绘画。 + + PHOTO : 摄影。 + + DAILY : 日常。 + """ + + ALL = "all" + DRAW = "draw" + PHOTO = "photo" + DAILY = "daily" + + +class ArticleOrder(Enum): + """ + 专栏排序顺序。 + + + PUBDATE : 发布日期倒序。 + + FAVORITE: 收藏量倒序。 + + VIEW : 阅读量倒序。 + """ + + PUBDATE = "publish_time" + FAVORITE = "fav" + VIEW = "view" + + +class ArticleListOrder(Enum): + """ + 文集排序顺序。 + + + LATEST: 最近更新倒序。 + + VIEW : 总阅读量倒序。 + """ + + LATEST = 0 + VIEW = 1 + + +class BangumiType(Enum): + """ + 番剧类型。 + + + BANGUMI: 番剧。 + + DRAMA : 电视剧/纪录片等。 + """ + + BANGUMI = 1 + DRAMA = 2 + + +class RelationType(Enum): + """ + 用户关系操作类型。 + + + SUBSCRIBE : 关注。 + + UNSUBSCRIBE : 取关。 + + SUBSCRIBE_SECRETLY: 悄悄关注。已失效 + + BLOCK : 拉黑。 + + UNBLOCK : 取消拉黑。 + + REMOVE_FANS : 移除粉丝。 + """ + + SUBSCRIBE = 1 + UNSUBSCRIBE = 2 + # SUBSCRIBE_SECRETLY = 3 + BLOCK = 5 + UNBLOCK = 6 + REMOVE_FANS = 7 + + +class BangumiFollowStatus(Enum): + """ + 番剧追番状态类型。 + + + ALL : 全部 + + WANT : 想看 + + WATCHING : 在看 + + WATCHED : 已看 + """ + + ALL = 0 + WANT = 1 + WATCHING = 2 + WATCHED = 3 + + +class HistoryType(Enum): + """ + 历史记录分类 + + + ALL : 全部 + + archive : 稿件 + + live : 直播 + + article : 专栏 + """ + + ALL = "all" + archive = "archive" + live = "live" + article = "article" + + +class HistoryBusinessType(Enum): + """ + 历史记录 Business 分类 + + + archive:稿件 + + pgc:剧集(番剧 / 影视) + + live:直播 + + article-list:文集 + + article:文章 + """ + + archive = "archive" + pgc = "pgc" + live = "live" + article_list = "article-list" + article = "article" + + +class OrderType(Enum): + """ + 排序字段 + + + desc:倒序 + + asc:正序 + """ + + desc = "desc" + asc = "asc" + + +async def name2uid_sync(names: Union[str, List[str]]): + """ + 将用户名转为 uid + + Args: + names (str/List[str]): 用户名 + + Returns: + dict: 调用 API 返回的结果 + """ + if isinstance(names, str): + n = names + else: + n = ",".join(names) + params = {"names": n} + return Api(**API["info"]["name_to_uid"]).update_params(**params).result_sync + + +async def name2uid(names: Union[str, List[str]]): + """ + 将用户名转为 uid + + Args: + names (str/List[str]): 用户名 + + Returns: + dict: 调用 API 返回的结果 + """ + if isinstance(names, str): + n = names + else: + n = ",".join(names) + params = {"names": n} + return await Api(**API["info"]["name_to_uid"]).update_params(**params).result + + +class User: + """ + 用户相关 + """ + + def __init__(self, uid: int, credential: Union[Credential, None] = None): + """ + Args: + uid (int) : 用户 UID + + credential (Credential | None, optional): 凭据. Defaults to None. + """ + self.__uid = uid + + if credential is None: + credential = Credential() + self.credential = credential + self.__self_info = None + + def get_user_info_sync(self) -> dict: + """ + 获取用户信息(昵称,性别,生日,签名,头像 URL,空间横幅 URL 等) + + Returns: + dict: 调用接口返回的内容。 + + [用户空间详细信息](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/user/info.md#%E7%94%A8%E6%88%B7%E7%A9%BA%E9%97%B4%E8%AF%A6%E7%BB%86%E4%BF%A1%E6%81%AF) + """ + params = { + "mid": self.__uid, + } + result = Api( + **API["info"]["info"], credential=self.credential, params=params + ).result_sync + return result + + async def get_user_info(self) -> dict: + """ + 获取用户信息(昵称,性别,生日,签名,头像 URL,空间横幅 URL 等) + + Returns: + dict: 调用接口返回的内容。 + + [用户空间详细信息](https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/user/info.md#%E7%94%A8%E6%88%B7%E7%A9%BA%E9%97%B4%E8%AF%A6%E7%BB%86%E4%BF%A1%E6%81%AF) + """ + params = { + "mid": self.__uid, + } + return ( + await Api(**API["info"]["info"], credential=self.credential) + .update_params(**params) + .result + ) + + async def __get_self_info(self) -> dict: + """ + 获取自己的信息。如果存在缓存则使用缓存。 + + Returns: + dict: 调用接口返回的内容。 + """ + if self.__self_info is not None: + return self.__self_info + + self.__self_info = await self.get_user_info() + return self.__self_info + + def get_uid(self) -> int: + """ + 获取用户 UID + + Returns: + int: 用户 UID + """ + return self.__uid + + async def get_user_fav_tag(self, pn: int = 1, ps: int = 20) -> dict: + """ + 获取用户关注的 Tag 信息,如果用户设为隐私,则返回 获取登录数据失败 + + Args: + pn (int, optional): 页码,从 1 开始. Defaults to 1. + ps (int, optional): 每页的数据量. Defaults to 20. + + Returns: + dict: 调用接口返回的内容。 + """ + api = API["info"]["user_tag"] + params = {"vmid": self.__uid} # , "pn": pn, "ps": ps} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_space_notice(self) -> dict: + """ + 获取用户空间公告 + + Returns: + dict: 调用接口返回的内容。 + """ + api = API["info"]["space_notice"] + params = {"mid": self.__uid} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def set_space_notice(self, content: str = "") -> dict: + """ + 修改用户空间公告 + + Args: + content(str): 需要修改的内容 + + Returns: + dict: 调用接口返回的内容。 + """ + + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["operate"]["set_space_notice"] + data = {"notice": content} + return await Api(**api, credential=self.credential).update_data(**data).result + + async def get_relation_info(self) -> dict: + """ + 获取用户关系信息(关注数,粉丝数,悄悄关注,黑名单数) + + Returns: + dict: 调用接口返回的内容。 + """ + api = API["info"]["relation_stat"] + params = {"vmid": self.__uid} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_up_stat(self) -> dict: + """ + 获取 UP 主数据信息(视频总播放量,文章总阅读量,总点赞数) + + Returns: + dict: 调用接口返回的内容。 + """ + self.credential.raise_for_no_bili_jct() + + api = API["info"]["upstat"] + params = {"mid": self.__uid} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_top_videos(self) -> dict: + """ + 获取用户的指定视频(代表作) + + Returns: + dict: 调用接口返回的内容。 + """ + api = API["info"]["user_top_videos"] + params = {"vmid": self.get_uid()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_masterpiece(self) -> list: + """ + 获取用户代表作 + + Returns: + list: 调用接口返回的内容。 + """ + api = API["info"]["masterpiece"] + params = {"vmid": self.get_uid()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_user_medal(self) -> dict: + """ + 读取用户粉丝牌详细列表,如果隐私则不可以 + + Returns: + dict: 调用接口返回的内容。 + """ + self.credential.raise_for_no_sessdata() + # self.credential.raise_for_no_bili_jct() + api = API["info"]["user_medal"] + params = {"target_id": self.__uid} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_live_info(self) -> dict: + """ + 获取用户直播间信息。 + + Returns: + dict: 调用接口返回的内容。 + """ + api = API["info"]["live"] + params = {"mid": self.__uid} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_videos( + self, + tid: int = 0, + pn: int = 1, + ps: int = 30, + keyword: str = "", + order: VideoOrder = VideoOrder.PUBDATE, + ) -> dict: + """ + 获取用户投稿视频信息。 + + Args: + tid (int, optional) : 分区 ID. Defaults to 0(全部). + + pn (int, optional) : 页码,从 1 开始. Defaults to 1. + + ps (int, optional) : 每一页的视频数. Defaults to 30. + + keyword (str, optional) : 搜索关键词. Defaults to "". + + order (VideoOrder, optional): 排序方式. Defaults to VideoOrder.PUBDATE + + Returns: + dict: 调用接口返回的内容。 + """ + api = API["info"]["video"] + params = { + "mid": self.__uid, + "ps": ps, + "tid": tid, + "pn": pn, + "keyword": keyword, + "order": order.value, + # -352 https://github.com/Nemo2011/bilibili-api/issues/595 + "dm_img_list": "[]", # 鼠标/键盘操作记录 + # WebGL 1.0 (OpenGL ES 2.0 Chromium) + "dm_img_str": "V2ViR0wgMS4wIChPcGVuR0wgRVMgMi4wIENocm9taXVtKQ", + # ANGLE (Intel, Intel(R) UHD Graphics 630 (0x00003E9B) Direct3D11 vs_5_0 ps_5_0, D3D11)Google Inc. (Intel + "dm_cover_img_str": "QU5HTEUgKEludGVsLCBJbnRlbChSKSBVSEQgR3JhcGhpY3MgNjMwICgweDAwMDAzRTlCKSBEaXJlY3QzRDExIHZzXzVfMCBwc181XzAsIEQzRDExKUdvb2dsZSBJbmMuIChJbnRlbC", + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_media_list( + self, + oid: Union[int, None] = None, + ps: int = 20, + direction: bool = False, + desc: bool = True, + sort_field: MedialistOrder = MedialistOrder.PUBDATE, + tid: int = 0, + with_current: bool = False, + ) -> dict: + """ + 以 medialist 形式获取用户投稿信息。 + + Args: + oid (int, optional) : 起始视频 aid, 默认为列表开头 + ps (int, optional) : 每一页的视频数. Defaults to 20. Max 100 + direction (bool, optional) : 相对于给定oid的查询方向 True 向列表末尾方向 False 向列表开头方向 Defaults to False. + desc (bool, optional) : 倒序排序. Defaults to True. + sort_field (int, optional) : 用于排序的栏 1 发布时间,2 播放量,3 收藏量 + tid (int, optional) : 分区 ID. Defaults to 0(全部). 1 部分(未知) + with_current (bool, optional) : 返回的列表中是否包含给定oid自身 Defaults to False. + + Returns: + dict: 调用接口返回的内容。 + """ + api = API["info"]["media_list"] + params = { + "mobi_app": "web", + "type": 1, + "biz_id": self.__uid, + "oid": oid, + "otype": 2, + "ps": ps, + "direction": direction, + "desc": desc, + "sort_field": sort_field.value, + "tid": tid, + "with_current": with_current, + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_audios( + self, order: AudioOrder = AudioOrder.PUBDATE, pn: int = 1, ps: int = 30 + ) -> dict: + """ + 获取用户投稿音频。 + + Args: + order (AudioOrder, optional): 排序方式. Defaults to AudioOrder.PUBDATE. + pn (int, optional) : 页码数,从 1 开始。 Defaults to 1. + ps (int, optional) : 每一页的视频数. Defaults to 30. + + Returns: + dict: 调用接口返回的内容。 + """ + api = API["info"]["audio"] + params = {"uid": self.__uid, "ps": ps, "pn": pn, "order": order.value} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_album( + self, biz: AlbumType = AlbumType.ALL, page_num: int = 1, page_size: int = 30 + ) -> dict: + """ + 获取用户投稿相簿。 + + Args: + biz (AlbumType, optional): 排序方式. Defaults to AlbumType.ALL. + + page_num (int, optional) : 页码数,从 1 开始。 Defaults to 1. + + page_size (int) : 每一页的相簿条目. Defaults to 30. + + Returns: + dict: 调用接口返回的内容。 + """ + api = API["info"]["album"] + params = { + "uid": self.__uid, + "page_num": page_num, + "page_size": page_size, + "biz": biz.value, + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_articles( + self, pn: int = 1, order: ArticleOrder = ArticleOrder.PUBDATE, ps: int = 30 + ) -> dict: + """ + 获取用户投稿专栏。 + + Args: + order (ArticleOrder, optional): 排序方式. Defaults to ArticleOrder.PUBDATE. + + pn (int, optional) : 页码数,从 1 开始。 Defaults to 1. + + ps (int, optional) : 每一页的视频数. Defaults to 30. + + Returns: + dict: 调用接口返回的内容。 + """ + api = API["info"]["article"] + params = {"mid": self.__uid, "ps": ps, "pn": pn, "sort": order.value} + return ( + await Api(**api, credential=self.credential, wbi=True) + .update_params(**params) + .result + ) + + async def get_article_list( + self, order: ArticleListOrder = ArticleListOrder.LATEST + ) -> dict: + """ + 获取用户专栏文集。 + + Args: + order (ArticleListOrder, optional): 排序方式. Defaults to ArticleListOrder.LATEST + + Returns: + dict: 调用接口返回的内容。 + """ + api = API["info"]["article_lists"] + params = {"mid": self.__uid, "sort": order.value} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_dynamics(self, offset: int = 0, need_top: bool = False) -> dict: + """ + 获取用户动态。 + + 建议使用 user.get_dynamics_new() 新接口。 + Args: + offset (str, optional): 该值为第一次调用本方法时,数据中会有个 next_offset 字段, + 指向下一动态列表第一条动态(类似单向链表)。 + 根据上一次获取结果中的 next_offset 字段值, + 循环填充该值即可获取到全部动态。 + 0 为从头开始。 + Defaults to 0. + need_top (bool, optional): 显示置顶动态. Defaults to False. + Returns: + dict: 调用接口返回的内容。 + """ + api = API["info"]["dynamic"] + params = { + "host_uid": self.__uid, + "offset_dynamic_id": offset, + "need_top": 1 if need_top else 0, + } + data = ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + # card 字段自动转换成 JSON。 + if "cards" in data: + for card in data["cards"]: + card["card"] = json.loads(card["card"]) + card["extend_json"] = json.loads(card["extend_json"]) + return data + + async def get_dynamics_new(self, offset: int = "") -> dict: + """ + 获取用户动态。 + + Args: + offset (str, optional): 该值为第一次调用本方法时,数据中会有个 offset 字段, + + 指向下一动态列表第一条动态(类似单向链表)。 + + 根据上一次获取结果中的 next_offset 字段值, + + 循环填充该值即可获取到全部动态。 + + 空字符串为从头开始。 + Defaults to "". + + Returns: + dict: 调用接口返回的内容。 + """ + self.credential.raise_for_no_sessdata() + api = API["info"]["dynamic_new"] + params = { + "host_mid": self.__uid, + "offset": offset, + "features": "itemOpusStyle", + "timezone_offset": -480, + } + data = ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + return data + + async def get_subscribed_bangumi( + self, + type_: BangumiType = BangumiType.BANGUMI, + follow_status: BangumiFollowStatus = BangumiFollowStatus.ALL, + pn: int = 1, + ps: int = 15, + ) -> dict: + """ + 获取用户追番/追剧列表。 + + Args: + pn (int, optional) : 页码数,从 1 开始。 Defaults to 1. + + ps (int, optional) : 每一页的番剧数. Defaults to 15. + + type_ (BangumiType, optional): 资源类型. Defaults to BangumiType.BANGUMI + + follow_status (BangumiFollowStatus, optional): 追番状态. Defaults to BangumiFollowStatus.ALL + + Returns: + dict: 调用接口返回的内容。 + """ + api = API["info"]["bangumi"] + params = { + "vmid": self.__uid, + "pn": pn, + "ps": ps, + "type": type_.value, + "follow_status": follow_status.value, + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_followings( + self, + pn: int = 1, + ps: int = 100, + attention: bool = False, + order: OrderType = OrderType.desc, + ) -> dict: + """ + 获取用户关注列表(不是自己只能访问前 5 页) + + Args: + pn (int, optional) : 页码,从 1 开始. Defaults to 1. + + ps (int, optional) : 每页的数据量. Defaults to 100. + + attention (bool, optional) : 是否采用“最常访问”排序,否则为“关注顺序”排序. Defaults to False. + + order (OrderType, optional) : 排序方式. Defaults to OrderType.desc. + + Returns: + dict: 调用接口返回的内容。 + """ + api = API["info"]["all_followings2"] + params = { + "vmid": self.__uid, + "ps": ps, + "pn": pn, + "order_type": "attention" if attention else "", + "order": order.value, + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_all_followings(self) -> dict: + """ + 获取所有的关注列表。(如果用户设置保密会没有任何数据) + + Returns: + list: 关注列表 + """ + api = API["info"]["all_followings"] + params = {"mid": self.__uid} + sess = get_session() + data = json.loads( + ( + await sess.get( + url=api["url"], params=params, cookies=self.credential.get_cookies() + ) + ).text + ) + return data["card"]["attentions"] + + async def get_followers( + self, pn: int = 1, ps: int = 100, desc: bool = True + ) -> dict: + """ + 获取用户粉丝列表(不是自己只能访问前 5 页,是自己也不能获取全部的样子) + + Args: + pn (int, optional) : 页码,从 1 开始. Defaults to 1. + + ps (int, optional) : 每页的数据量. Defaults to 100. + + desc (bool, optional): 倒序排序. Defaults to True. + + Returns: + dict: 调用接口返回的内容。 + """ + api = API["info"]["followers"] + params = { + "vmid": self.__uid, + "ps": ps, + "pn": pn, + "order": "desc" if desc else "asc", + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_self_same_followers(self, pn: int = 1, ps: int = 50) -> dict: + """ + 获取用户与自己共同关注的 up 主 + + Args: + pn (int): 页码. Defaults to 1. + + ps (int): 单页数据量. Defaults to 50. + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + api = API["info"]["get_same_followings"] + params = {"vmid": self.get_uid(), "pn": pn, "ps": ps} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def top_followers(self, since=None) -> dict: + """ + 获取用户粉丝排行 + Args: + since (int, optional) : 开始时间(msec) + + Returns: + dict: 调用接口返回的内容。 + """ + api = API["info"]["top_followers"] + params = {} + if since: + params["t"] = int(since) + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_overview_stat(self) -> dict: + """ + 获取用户的简易订阅和投稿信息。 + + Returns: + dict: 调用接口返回的内容。 + """ + api = API["info"]["overview"] + params = {"mid": self.__uid, "jsonp": "jsonp"} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_relation(self, uid: int) -> dict: + """ + 获取与某用户的关系 + + Args: + uid (int): 用户 UID + + Returns: + dict: 调用接口返回的内容。 + """ + + api = API["info"]["relation"] + params = {"mid": uid} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + # 操作用户关系 + + async def modify_relation(self, relation: RelationType) -> dict: + """ + 修改和用户的关系,比如拉黑、关注、取关等。 + + Args: + relation (RelationType): 用户关系。 + + Returns: + dict: 调用接口返回的内容。 + """ + + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["operate"]["modify"] + data = {"fid": self.__uid, "act": relation.value, "re_src": 11} + return await Api(**api, credential=self.credential).update_data(**data).result + + # 有关合集与列表 + + async def get_channel_videos_series( + self, + sid: int, + sort: ChannelOrder = ChannelOrder.DEFAULT, + pn: int = 1, + ps: int = 100, + ) -> dict: + """ + 查看频道内所有视频。仅供 series_list。 + + Args: + sid(int): 频道的 series_id + + pn(int) : 页数,默认为 1 + + ps(int) : 每一页显示的视频数量 + + Returns: + dict: 调用接口返回的内容 + """ + api = API["info"]["channel_video_series"] + params = { + "mid": self.__uid, + "series_id": sid, + "pn": pn, + "ps": ps, + "sort": "asc" if sort == ChannelOrder.CHANGE else "desc", + } + return ( + await Api(**api, wbi=True, credential=self.credential) + .update_params(**params) + .result + ) + + async def get_channel_videos_season( + self, + sid: int, + sort: ChannelOrder = ChannelOrder.DEFAULT, + pn: int = 1, + ps: int = 100, + ) -> dict: + """ + 查看频道内所有视频。仅供 season_list。 + + Args: + sid(int) : 频道的 season_id + + sort(ChannelOrder): 排序方式 + + pn(int) : 页数,默认为 1 + + ps(int) : 每一页显示的视频数量 + + Returns: + dict: 调用接口返回的内容 + """ + api = API["info"]["channel_video_season"] + params = { + "mid": self.__uid, + "season_id": sid, + "sort_reverse": sort.value, + "page_num": pn, + "page_size": ps, + } + return ( + await Api(**api, wbi=True, credential=self.credential) + .update_params(**params) + .result + ) + + async def get_channel_list(self) -> dict: + """ + 查看用户所有的频道(包括新版)和部分视频。 + + 适用于获取列表。 + + 未处理数据。不推荐。 + + Returns: + dict: 调用接口返回的结果 + """ + api = API["info"]["channel_list"] + params = {"mid": self.__uid, "page_num": 1, "page_size": 1} + res = ( + await Api(**api, wbi=True, credential=self.credential) + .update_params(**params) + .result + ) + items = res["items_lists"]["page"]["total"] + time.sleep(0.5) + if items == 0: + items = 1 + params["page_size"] = items + return ( + await Api(**api, wbi=True, credential=self.credential) + .update_params(**params) + .result + ) + + async def get_channels(self) -> List["ChannelSeries"]: + """ + 获取用户所有合集 + + Returns: + List[ChannelSeries]: 合集与列表类的列表 + """ + from . import channel_series + + channel_data = await self.get_channel_list() + channels = [] + for item in channel_data["items_lists"]["seasons_list"]: + id_ = item["meta"]["season_id"] + meta = item["meta"] + channel_series.channel_meta_cache[ + str(ChannelSeriesType.SEASON.value) + "-" + str(id_) + ] = meta + channels.append( + ChannelSeries( + self.__uid, ChannelSeriesType.SEASON, id_, self.credential + ) + ) + for item in channel_data["items_lists"]["series_list"]: + id_ = item["meta"]["series_id"] + meta = item["meta"] + channel_series.channel_meta_cache[ + str(ChannelSeriesType.SERIES.value) + "-" + str(id_) + ] = meta + channels.append( + ChannelSeries( + self.__uid, ChannelSeriesType.SERIES, id_, self.credential + ) + ) + return channels + + async def get_cheese(self) -> dict: + """ + 查看用户的所有课程 + + Returns: + dict: 调用接口返回的结果 + """ + api = API["info"]["pugv"] + params = {"mid": self.__uid} + return ( + await Api(**api, wbi=True, credential=self.credential) + .update_params(**params) + .result + ) + + async def get_reservation(self) -> dict: + """ + 获取用户空间预约 + + Returns: + dict: 调用接口返回的结果 + """ + api = API["info"]["reservation"] + params = {"vmid": self.get_uid()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_elec_user_monthly(self) -> dict: + """ + 获取空间充电公示信息 + + Returns: + dict: 调用接口返回的结果 + """ + api = API["info"]["elec_user_monthly"] + params = {"up_mid": self.get_uid()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_uplikeimg(self) -> dict: + """ + 视频三联特效 + + Returns: + dict: 调用 API 返回的结果。 + """ + api = API["info"]["uplikeimg"] + params = {"vmid": self.get_uid()} + return await Api(**api).update_params(**params).result + + +async def get_self_info(credential: Credential) -> dict: + """ + 获取自己的信息 + + Args: + credential (Credential): Credential + """ + api = API["info"]["my_info"] + credential.raise_for_no_sessdata() + + return await Api(**api, credential=credential).result + + +async def edit_self_info( + birthday: str, sex: str, uname: str, usersign: str, credential: Credential +) -> dict: + """ + 修改自己的信息 (Web) + + Args: + birthday (str) : 生日 YYYY-MM-DD + + sex (str) : 性别 男|女|保密 + + uname (str) : 用户名 + + usersign (str) : 个性签名 + + credential (Credential): Credential + """ + + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + + api = API["info"]["edit_my_info"] + data = {"birthday": birthday, "sex": sex, "uname": uname, "usersign": usersign} + + return await Api(**api, credential=credential).update_data(**data).result + + +async def create_subscribe_group(name: str, credential: Credential) -> dict: + """ + 创建用户关注分组 + + Args: + name (str) : 分组名 + + credential (Credential): Credential + + Returns: + API 调用返回结果。 + """ + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + + api = API["operate"]["create_subscribe_group"] + data = {"tag": name} + + return await Api(**api, credential=credential).update_data(**data).result + + +async def delete_subscribe_group(group_id: int, credential: Credential) -> dict: + """ + 删除用户关注分组 + + Args: + group_id (int) : 分组 ID + + credential (Credential): Credential + + Returns: + 调用 API 返回结果 + """ + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + + api = API["operate"]["del_subscribe_group"] + data = {"tagid": group_id} + + return await Api(**api, credential=credential).update_data(**data).result + + +async def rename_subscribe_group( + group_id: int, new_name: str, credential: Credential +) -> dict: + """ + 重命名关注分组 + + Args: + group_id (int) : 分组 ID + + new_name (str) : 新的分组名 + + credential (Credential): Credential + + Returns: + 调用 API 返回结果 + """ + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + + api = API["operate"]["rename_subscribe_group"] + data = {"tagid": group_id, "name": new_name} + + return await Api(**api, credential=credential).update_data(**data).result + + +async def set_subscribe_group( + uids: List[int], group_ids: List[int], credential: Credential +) -> dict: + """ + 设置用户关注分组 + + Args: + uids (List[int]) : 要设置的用户 UID 列表,必须已关注。 + + group_ids (List[int]) : 要复制到的分组列表 + + credential (Credential): Credential + + Returns: + API 调用结果 + """ + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + + api = API["operate"]["set_user_subscribe_group"] + data = {"fids": join(",", uids), "tagids": join(",", group_ids)} + + return await Api(**api, credential=credential).update_data(**data).result + + +async def get_self_history( + page_num: int = 1, + per_page_item: int = 100, + credential: Union[Credential, None] = None, +) -> dict: + """ + 获取用户浏览历史记录(旧版) + + Args: + page_num (int): 页码数 + + per_page_item (int): 每页多少条历史记录 + + credential (Credential): Credential + + Returns: + list(dict): 返回当前页的指定历史记录列表 + """ + if not credential: + credential = Credential() + + credential.raise_for_no_sessdata() + + api = API["info"]["history"] + params = {"pn": page_num, "ps": per_page_item} + + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_self_history_new( + credential: Credential, + _type: HistoryType = HistoryType.ALL, + ps: int = 20, + view_at: int = None, + max: int = None, + business: HistoryBusinessType = None, +) -> dict: + """ + 获取用户浏览历史记录(新版),与旧版不同有分类参数,但相对缺少视频信息 + + max、business、view_at 参数用于历史记录列表的 IFS (无限滚动),其用法类似链表的 next 指针 + + 将返回值某历史记录的 oid、business、view_at 作为上述参数传入,即可获取此 oid 之前的历史记录 + + Args: + credential (Credential) : Credential + + _type (HistroyType): 历史记录分类, 默认为 HistroyType.ALL + + ps (int) : 每页多少条历史记录, 默认为 20 + + view_at (int) : 时间戳,获取此时间戳之前的历史记录 + + max (int) : 历史记录截止目标 oid + + Returns: + dict: 调用 API 返回的结果 + """ + credential.raise_for_no_sessdata() + api = API["info"]["history_new"] + params = { + "type": _type.value, + "ps": ps, + "view_at": view_at, + "max": max, + "business": business if business is None else business.value, + } + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_self_coins(credential: Credential) -> int: + """ + 获取自己的硬币数量。 + + Returns: + int: 硬币数量 + """ + if credential is None: + credential = Credential() + credential.raise_for_no_sessdata() + credential.raise_for_no_dedeuserid() + api = API["info"]["get_coins"] + return (await Api(**api, credential=credential).result)["money"] + + +async def get_self_special_followings( + credential: Credential, pn: int = 1, ps: int = 50 +) -> dict: + """ + 获取自己特殊关注的列表 + + Args: + credential (Credential) : 凭据类 + + pn (int, optional): 页码. Defaults to 1. + + ps (int, optional): 每页数据大小. Defaults to 50. + """ + credential.raise_for_no_sessdata() + api = API["info"]["get_special_followings"] + params = {"pn": pn, "ps": ps} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_self_whisper_followings( + credential: Credential, pn: int = 1, ps: int = 50 +) -> dict: + """ + 获取自己悄悄关注的列表。 + + Args: + credential (Credential) : 凭据类 + + pn (int, optional): 页码. Defaults to 1. + + ps (int, optional): 每页数据大小. Defaults to 50. + """ + credential.raise_for_no_sessdata() + api = API["info"]["get_whisper_followings"] + params = {"pn": pn, "ps": ps} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_self_friends(credential: Credential) -> dict: + """ + 获取与自己互粉的人 + + Args: + credential (Credential) : 凭据类 + """ + credential.raise_for_no_sessdata() + api = API["info"]["get_friends"] + return await Api(**api, credential=credential).result + + +async def get_self_black_list( + credential: Credential, pn: int = 1, ps: int = 50 +) -> dict: + """ + 获取自己的黑名单信息 + + Args: + credential (Credential) : 凭据类 + + pn (int, optional): 页码. Defaults to 1. + + ps (int, optional): 每页数据大小. Defaults to 50. + """ + credential.raise_for_no_sessdata() + api = API["info"]["get_black_list"] + params = {"pn": pn, "ps": ps} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_toview_list(credential: Credential): + """ + 获取稍后再看列表 + + Args: + credential (Credential): 凭据类 + + Returns: + dict: 调用 API 返回的结果 + """ + api = get_api("toview")["info"]["list"] + credential.raise_for_no_sessdata() + return await Api(**api, credential=credential).result + + +async def clear_toview_list(credential: Credential): + """ + 清空稍后再看列表 + + Args: + credential(Credential): 凭据类 + + Returns: + dict: 调用 API 返回的结果 + """ + api = get_api("toview")["operate"]["clear"] + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + return await Api(**api, credential=credential).result + + +async def delete_viewed_videos_from_toview(credential: Credential): + """ + 删除稍后再看列表已经看过的视频 + + Args: + credential(Credential): 凭据类 + + Returns: + dict: 调用 API 返回的结果 + """ + api = get_api("toview")["operate"]["del"] + credential.raise_for_no_sessdata() + credential.raise_for_no_bili_jct() + datas = {"viewed": "true"} + return await Api(**api, credential=credential).update_data(**datas).result + + +async def check_nickname(nick_name: str) -> Tuple[bool, str]: + """ + 检验昵称是否可用 + + Args: + nick_name(str): 昵称 + + Returns: + List[bool, str]: 昵称是否可用 + 不可用原因 + """ + api = get_api("common")["nickname"]["check_nickname"] + params = {"nickName": nick_name} + try: + resp = await Api(**api).update_params(**params).result + except ResponseCodeException as e: + return False, str(e) + else: + return True, "" + + +async def get_self_events(ts: int = 0, credential: Union[Credential, None] = None): + """ + 获取自己入站后每一刻的事件 + + Args: + ts(int, optional) : 时间戳. Defaults to 0. + + credential(Credential | None, optional): 凭据. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + credential = credential if credential else Credential() + api = API["info"]["events"] + params = {"ts": ts} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_self_notes_info( + page_num: int, page_size: int, credential: Credential +) -> dict: + """ + 获取自己的笔记列表 + + Args: + page_num: 页码 + + page_size: 每页项数 + + credential(Credential): 凭据类 + + Returns: + dict: 调用 API 返回的结果 + """ + + raise_for_statement(page_num > 0) + raise_for_statement(page_size > 0) + + credential.raise_for_no_sessdata() + + api = API["info"]["all_notes"] + params = {"pn": page_num, "ps": page_size} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_self_public_notes_info( + page_num: int, page_size: int, credential: Credential +) -> dict: + """ + 获取自己的公开笔记列表 + + Args: + page_num: 页码 + + page_size: 每页项数 + + credential(Credential): 凭据类 + + Returns: + dict: 调用 API 返回的结果 + """ + + raise_for_statement(page_num > 0) + raise_for_statement(page_size > 0) + + credential.raise_for_no_sessdata() + + api = API["info"]["public_notes"] + params = {"pn": page_num, "ps": page_size} + return await Api(**api, credential=credential).update_params(**params).result + + +async def get_self_jury_info(credential: Credential) -> dict: + """ + 获取自己风纪委员信息 + """ + credential.raise_for_no_sessdata() + api = API["info"]["jury"] + return await Api(**api, credential=credential).result + + +async def get_self_login_log(credential: Credential) -> dict: + """ + 获取自己的登录记录 + + Args: + credential (Credential): 凭证。 + + Returns: + dict: 调用 API 返回的结果 + """ + credential.raise_for_no_sessdata() + api = API["info"]["login_log"] + return await Api(**api, credential=credential).result + + +async def get_self_moral_log(credential: Credential) -> dict: + """ + 获取自己的节操记录 + + Args: + credential (Credential): 凭证。 + + Returns: + dict: 调用 API 返回的结果 + """ + credential.raise_for_no_sessdata() + api = API["info"]["moral_log"] + return await Api(**api, credential=credential).result + + +async def get_self_experience_log(credential: Credential) -> dict: + """ + 获取自己的经验记录 + + Args: + credential (Credential): 凭证。 + + Returns: + dict: 调用 API 返回的结果 + """ + credential.raise_for_no_sessdata() + api = API["info"]["exp_log"] + return await Api(**api, credential=credential).result diff --git a/bilibili_api/utils/AsyncEvent.py b/bilibili_api/utils/AsyncEvent.py new file mode 100644 index 0000000000000000000000000000000000000000..3c6e6c2436547972f84d50a72ed2569335ff1a60 --- /dev/null +++ b/bilibili_api/utils/AsyncEvent.py @@ -0,0 +1,109 @@ +""" +bilibili_api.utils.AsyncEvent + +发布-订阅模式异步事件类支持。 +""" + +import asyncio +from typing import Callable, Coroutine + + +class AsyncEvent: + """ + 发布-订阅模式异步事件类支持。 + + 特殊事件:__ALL__ 所有事件均触发 + """ + + def __init__(self): + self.__handlers = {} + self.__ignore_events = [] + + def add_event_listener(self, name: str, handler: Coroutine) -> None: + """ + 注册事件监听器。 + + Args: + name (str): 事件名。 + handler (Coroutine): 回调异步函数。 + """ + name = name.upper() + if name not in self.__handlers: + self.__handlers[name] = [] + self.__handlers[name].append(handler) + + def on(self, event_name: str) -> Callable: + """ + 装饰器注册事件监听器。 + + Args: + event_name (str): 事件名。 + """ + + def decorator(func: Coroutine): + self.add_event_listener(event_name, func) + return func + + return decorator + + def remove_all_event_listener(self) -> None: + """ + 移除所有事件监听函数 + """ + self.__handlers = {} + + def remove_event_listener(self, name: str, handler: Coroutine) -> bool: + """ + 移除事件监听函数。 + + Args: + name (str): 事件名。 + handler (Coroutine): 要移除的函数。 + + Returns: + bool, 是否移除成功。 + """ + name = name.upper() + if name in self.__handlers: + if handler in self.__handlers[name]: + self.__handlers[name].remove(handler) + return True + return False + + def ignore_event(self, name: str) -> None: + """ + 忽略指定事件 + + Args: + name (str): 事件名。 + """ + name = name.upper() + self.__ignore_events.append(name) + + def remove_ignore_events(self) -> None: + """ + 移除所有忽略事件 + """ + self.__ignore_events = [] + + def dispatch(self, name: str, *args, **kwargs) -> None: + """ + 异步发布事件。 + + Args: + name (str): 事件名。 + *args, **kwargs: 要传递给函数的参数。 + """ + if len(args) == 0 and len(kwargs.keys()) == 0: + args = [{}] + if name.upper() in self.__ignore_events: + return + + name = name.upper() + if name in self.__handlers: + for coroutine in self.__handlers[name]: + asyncio.create_task(coroutine(*args, **kwargs)) + + if name != "__ALL__": + kwargs.update({"name": name, "data": args}) + self.dispatch("__ALL__", kwargs) diff --git a/bilibili_api/utils/BytesReader.py b/bilibili_api/utils/BytesReader.py new file mode 100644 index 0000000000000000000000000000000000000000..c7e8fb7f37153427833ef51d5d1d981d95ff1121 --- /dev/null +++ b/bilibili_api/utils/BytesReader.py @@ -0,0 +1,243 @@ +""" +bilibili_api.BytesReader + +读字节流助手。 +""" +import struct + +from .varint import read_varint + + +class BytesReader: + """ + 读字节流助手类。 + """ + + def __init__(self, stream: bytes): + """ + + Args: + stream (bytes): 字节流 + """ + self.__stream = stream + self.__offset: int = 0 + + def has_end(self) -> bool: # pylint: disable=used-before-assignment + """ + 是否已读到末尾 + + Returns: + bool。 + """ + return self.__offset >= len(self.__stream) + + def double(self, LE=False) -> float: # pylint: disable=used-before-assignment + """ + 读 double。 + + Args: + LE (bool): 为小端。 + + Returns: + float。 + """ + data = struct.unpack( + "d", self.__stream[self.__offset : self.__offset + 8] + ) + self.__offset += 8 + return data[0] + + def float(self, LE=False) -> float: + """ + 读 float。 + + Args: + LE (bool): 为小端。 + + Returns: + float。 + """ + stream = self.__stream[self.__offset : self.__offset + 4] + data = struct.unpack("f", stream) + self.__offset += 4 + return data[0] + + def varint(self) -> int: + """ + 读 varint。 + + Returns: + int。 + """ + d, l = read_varint(self.__stream[self.__offset :]) + self.__offset += l + return d + + def byte(self) -> int: + """ + 读 byte。 + + Returns: + int。 + """ + data = self.__stream[self.__offset] + self.__offset += 1 + return data + + def string(self, encoding="utf8") -> str: + """ + 读 string。 + + Args: + encoding (str): 编码方式。 + + Returns: + str。 + """ + str_len = self.varint() + data = self.__stream[self.__offset : self.__offset + str_len] + self.__offset += str_len + return data.decode(encoding=encoding, errors="ignore") + + def bool(self) -> bool: + """ + 读 bool。 + + Returns: + bool。 + """ + data = self.__stream[self.__offset] + self.__offset += 1 + return data == 1 + + def bytes_string(self) -> bytes: + """ + 读原始字节流。 + + Returns: + bytes。 + """ + str_len = self.varint() + data = self.__stream[self.__offset : self.__offset + str_len] + self.__offset += str_len + return data + + def fixed16(self, LE=False) -> int: + """ + 读 Fixed int16。 + + Args: + LE (bool): 为小端。 + + Returns: + int。 + """ + data = struct.unpack( + "h", self.__stream[self.__offset : self.__offset + 2] + ) + self.__offset += 2 + return data[0] + + def fixed32(self, LE=False) -> int: + """ + 读 Fixed int32. + + Args: + LE (bool): 为小端。 + + Returns: + int。 + """ + data = struct.unpack( + "i", self.__stream[self.__offset : self.__offset + 4] + ) + self.__offset += 4 + return data[0] + + def fixed64(self, LE=False) -> int: + """ + 读 Fixed int64。 + + Args: + LE (bool): 为小端。 + + Returns: + int。 + """ + data = struct.unpack( + "q", self.__stream[self.__offset : self.__offset + 8] + ) + self.__offset += 8 + return data[0] + + def ufixed16(self, LE=False) -> int: + """ + 读 Unsigned fixed Int16。 + + Args: + LE (bool): 为小端。 + + Returns: + int。 + """ + data = struct.unpack( + "H", self.__stream[self.__offset : self.__offset + 2] + ) + self.__offset += 2 + return data[0] + + def ufixed32(self, LE=False) -> int: + """ + 读 Unsigned fixed Int32。 + + Args: + LE (bool): 为小端。 + + Returns: + int。 + """ + data = struct.unpack( + "I", self.__stream[self.__offset : self.__offset + 4] + ) + self.__offset += 4 + return data[0] + + def ufixed64(self, LE=False) -> int: + """ + 读 Unsigned fixed Int64。 + + Args: + LE (bool): 为小端。 + + Returns: + int。 + """ + data = struct.unpack( + "Q", self.__stream[self.__offset : self.__offset + 8] + ) + self.__offset += 8 + return data[0] + + def set_pos(self, pos: int) -> None: + """ + 设置读取起始位置。 + + Args: + pos (int): 读取起始位置。 + """ + if pos < 0: + raise Exception("读取位置不能小于 0") + + if pos >= len(self.__stream): + raise Exception("读取位置超过字节流长度") + + self.__offset = pos + + def get_pos(self) -> int: + """ + 获取当前位置。 + + Returns: + int, 当前位置。 + """ + return self.__offset diff --git a/bilibili_api/utils/__init__.py b/bilibili_api/utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bilibili_api/utils/__pycache__/AsyncEvent.cpython-38.pyc b/bilibili_api/utils/__pycache__/AsyncEvent.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa9eddefa89d1bcc2ffc2bf537f7bc587735e3f8 Binary files /dev/null and b/bilibili_api/utils/__pycache__/AsyncEvent.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/BytesReader.cpython-38.pyc b/bilibili_api/utils/__pycache__/BytesReader.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..372404408a7c4c89a7eaa137151a566acf639ffc Binary files /dev/null and b/bilibili_api/utils/__pycache__/BytesReader.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/__init__.cpython-38.pyc b/bilibili_api/utils/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6d861481a085f6ebdfcb69b3c86e2aedcece6953 Binary files /dev/null and b/bilibili_api/utils/__pycache__/__init__.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/aid_bvid_transformer.cpython-38.pyc b/bilibili_api/utils/__pycache__/aid_bvid_transformer.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..09ae56e66bba82fa47e1c5f732472d61ee1a1d06 Binary files /dev/null and b/bilibili_api/utils/__pycache__/aid_bvid_transformer.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/cache_pool.cpython-38.pyc b/bilibili_api/utils/__pycache__/cache_pool.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..45b838c44fb527db4b1a99346532bd52fe8ff6fc Binary files /dev/null and b/bilibili_api/utils/__pycache__/cache_pool.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/captcha.cpython-38.pyc b/bilibili_api/utils/__pycache__/captcha.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..26e6d3bbe749f46fe3ac91acfbf12b8bd9642d9f Binary files /dev/null and b/bilibili_api/utils/__pycache__/captcha.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/credential.cpython-38.pyc b/bilibili_api/utils/__pycache__/credential.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8d7664bbe08a9a5cff3b40897c8b7de72ebd3596 Binary files /dev/null and b/bilibili_api/utils/__pycache__/credential.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/credential_refresh.cpython-38.pyc b/bilibili_api/utils/__pycache__/credential_refresh.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fef038a8d9595519d467472de973d09963b69a20 Binary files /dev/null and b/bilibili_api/utils/__pycache__/credential_refresh.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/danmaku.cpython-38.pyc b/bilibili_api/utils/__pycache__/danmaku.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..49ec911b9d9fc26248234c271d6e0632f36bcc6d Binary files /dev/null and b/bilibili_api/utils/__pycache__/danmaku.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/danmaku2ass.cpython-38.pyc b/bilibili_api/utils/__pycache__/danmaku2ass.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0dca29c5e28b2acd4cba70de116f6d87c38e5b88 Binary files /dev/null and b/bilibili_api/utils/__pycache__/danmaku2ass.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/initial_state.cpython-38.pyc b/bilibili_api/utils/__pycache__/initial_state.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f43a9de6bdea1b34ae793b22708647ad4f30d902 Binary files /dev/null and b/bilibili_api/utils/__pycache__/initial_state.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/json2srt.cpython-38.pyc b/bilibili_api/utils/__pycache__/json2srt.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e5aefe722e006a5f19862d728d5ace377efdfc0 Binary files /dev/null and b/bilibili_api/utils/__pycache__/json2srt.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/network.cpython-38.pyc b/bilibili_api/utils/__pycache__/network.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e830deb4eda2ad181ca478030de74f89ec547e19 Binary files /dev/null and b/bilibili_api/utils/__pycache__/network.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/parse_link.cpython-38.pyc b/bilibili_api/utils/__pycache__/parse_link.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7e6ef2fc6f53f55f7a5c465987b458747b4bcbfd Binary files /dev/null and b/bilibili_api/utils/__pycache__/parse_link.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/picture.cpython-38.pyc b/bilibili_api/utils/__pycache__/picture.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..803362a2c0a0c1989d2aeb7854a238614e8dd3b5 Binary files /dev/null and b/bilibili_api/utils/__pycache__/picture.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/safecenter_captcha.cpython-38.pyc b/bilibili_api/utils/__pycache__/safecenter_captcha.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b4634519af32ca48fe0ea1ea76e8d99aac9381a1 Binary files /dev/null and b/bilibili_api/utils/__pycache__/safecenter_captcha.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/short.cpython-38.pyc b/bilibili_api/utils/__pycache__/short.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8518e4370f7c718bc337c4d898c6e0b2bd68d825 Binary files /dev/null and b/bilibili_api/utils/__pycache__/short.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/srt2ass.cpython-38.pyc b/bilibili_api/utils/__pycache__/srt2ass.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af2c3b293be147aa726df4ed109c5b9956416a09 Binary files /dev/null and b/bilibili_api/utils/__pycache__/srt2ass.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/sync.cpython-38.pyc b/bilibili_api/utils/__pycache__/sync.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b971c3ca6fa3d7d81cd10b753b093d3617784913 Binary files /dev/null and b/bilibili_api/utils/__pycache__/sync.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/upos.cpython-38.pyc b/bilibili_api/utils/__pycache__/upos.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..681a46841fe6385cbdf12e0d1932efa10ee041ec Binary files /dev/null and b/bilibili_api/utils/__pycache__/upos.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/utils.cpython-38.pyc b/bilibili_api/utils/__pycache__/utils.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..022635240aeaa3c3b595ae8cca2dcba2e09e1eae Binary files /dev/null and b/bilibili_api/utils/__pycache__/utils.cpython-38.pyc differ diff --git a/bilibili_api/utils/__pycache__/varint.cpython-38.pyc b/bilibili_api/utils/__pycache__/varint.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..27cfd8b8056ca2a7b0540a19c624bf90b87b5a43 Binary files /dev/null and b/bilibili_api/utils/__pycache__/varint.cpython-38.pyc differ diff --git a/bilibili_api/utils/aid_bvid_transformer.py b/bilibili_api/utils/aid_bvid_transformer.py new file mode 100644 index 0000000000000000000000000000000000000000..d88f316b2fff756302298cad2b1bc8f3615b6701 --- /dev/null +++ b/bilibili_api/utils/aid_bvid_transformer.py @@ -0,0 +1,54 @@ +""" +bilibili_api.utils.aid_bvid_transformer + +av 号和 bv 号互转,代码来源:https://www.zhihu.com/question/381784377/answer/1099438784。 + +此部分代码以 WTFPL 开源。 +""" + +XOR_CODE = 23442827791579 +MASK_CODE = 2251799813685247 +MAX_AID = 1 << 51 + +data = [b'F', b'c', b'w', b'A', b'P', b'N', b'K', b'T', b'M', b'u', b'g', b'3', b'G', b'V', b'5', b'L', b'j', b'7', b'E', b'J', b'n', b'H', b'p', b'W', b's', b'x', b'4', b't', b'b', b'8', b'h', b'a', b'Y', b'e', b'v', b'i', b'q', b'B', b'z', b'6', b'r', b'k', b'C', b'y', b'1', b'2', b'm', b'U', b'S', b'D', b'Q', b'X', b'9', b'R', b'd', b'o', b'Z', b'f'] + +BASE = 58 +BV_LEN = 12 +PREFIX = "BV1" + +def bvid2aid(bvid: str) -> int: + """ + BV 号转 AV 号。 + Args: + bvid (str): BV 号。 + Returns: + int: AV 号。 + """ + bvid = list(bvid) + bvid[3], bvid[9] = bvid[9], bvid[3] + bvid[4], bvid[7] = bvid[7], bvid[4] + bvid = bvid[3:] + tmp = 0 + for i in bvid: + idx = data.index(i.encode()) + tmp = tmp * BASE + idx + return (tmp & MASK_CODE) ^ XOR_CODE + +def aid2bvid(aid: int) -> str: + """ + AV 号转 BV 号。 + Args: + aid (int): AV 号。 + Returns: + str: BV 号。 + """ + bytes = [b'B', b'V', b'1', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0', b'0'] + bv_idx = BV_LEN - 1 + tmp = (MAX_AID | aid) ^ XOR_CODE + while int(tmp) != 0: + bytes[bv_idx] = data[int(tmp % BASE)] + tmp /= BASE + bv_idx -= 1 + bytes[3], bytes[9] = bytes[9], bytes[3] + bytes[4], bytes[7] = bytes[7], bytes[4] + return "".join([i.decode() for i in bytes]) diff --git a/bilibili_api/utils/cache_pool.py b/bilibili_api/utils/cache_pool.py new file mode 100644 index 0000000000000000000000000000000000000000..7da6c349730086753427925e9f7878eaef3f716d --- /dev/null +++ b/bilibili_api/utils/cache_pool.py @@ -0,0 +1,5 @@ +article_is_opus = {} +article_dyn_id = {} +dynamic_is_opus = {} +opus_type = {} +opus_info = {} diff --git a/bilibili_api/utils/captcha.py b/bilibili_api/utils/captcha.py new file mode 100644 index 0000000000000000000000000000000000000000..0f590db053ca3b8af3e0672c687c95a784874c22 --- /dev/null +++ b/bilibili_api/utils/captcha.py @@ -0,0 +1,273 @@ +""" +bilibili_api.utils.captcha + +人机测试 +""" +import os +import copy +import json +import time + +from .utils import get_api +from .network import Api + +validate = None +seccode = None +gt = None +challenge = None +key = None +server = None +thread = None + +API = get_api("login") + + +def _geetest_urlhandler(url: str, content_type: str): + """ + 极验验证服务器 html 源获取函数 + """ + global gt, challenge, key + url = url[1:] + if url[:7] == "result/": + global validate, seccode + datas = url[7:] + datas = datas.split("&") + for data in datas: + if data[:8] == "validate": + validate = data[9:] + elif data[:7] == "seccode": + seccode = data[8:].replace("%7C", "|") + with open( + os.path.abspath( + os.path.join( + os.path.dirname(__file__), "..", "data", "geetest", "done.html" + ) + ), + encoding="utf8", + ) as f: + html_source_bytes = f.read() + return html_source_bytes + elif url[:7] == "": + api = API["password"]["captcha"] + json_data = Api(**api).result_sync + gt = json_data["geetest"]["gt"] + challenge = json_data["geetest"]["challenge"] + key = json_data["token"] + with open( + os.path.abspath( + os.path.join( + os.path.dirname(__file__), "..", "data", "geetest", "captcha.html" + ) + ), + encoding="utf8", + ) as f: + html_source_bytes = ( + f.read() + .replace("{ Python_Interface: GT }", f'"{gt}"') + .replace("{ Python_Interface: CHALLENGE }", f'"{challenge}"') + ) + return html_source_bytes + else: + return "" + + +def _start_server(urlhandler, hostname, port): + """Start an HTTP server thread on a specific port. + + Start an HTML/text server thread, so HTML or text documents can be + browsed dynamically and interactively with a web browser. Example use: + + >>> import time + >>> import pydoc + + Define a URL handler. To determine what the client is asking + for, check the URL and content_type. + + Then get or generate some text or HTML code and return it. + + >>> def my_url_handler(url, content_type): + ... text = 'the URL sent was: (%s, %s)' % (url, content_type) + ... return text + + Start server thread on port 0. + If you use port 0, the server will pick a random port number. + You can then use serverthread.port to get the port number. + + >>> port = 0 + >>> serverthread = pydoc._start_server(my_url_handler, port) + + Check that the server is really started. If it is, open browser + and get first page. Use serverthread.url as the starting page. + + >>> if serverthread.serving: + ... import webbrowser + + The next two lines are commented out so a browser doesn't open if + doctest is run on this module. + + #... webbrowser.open(serverthread.url) + #True + + Let the server do its thing. We just need to monitor its status. + Use time.sleep so the loop doesn't hog the CPU. + + >>> starttime = time.monotonic() + >>> timeout = 1 #seconds + + This is a short timeout for testing purposes. + + >>> while serverthread.serving: + ... time.sleep(.01) + ... if serverthread.serving and time.monotonic() - starttime > timeout: + ... serverthread.stop() + ... break + + Print any errors that may have occurred. + + >>> print(serverthread.error) + None + """ + import select + import threading + import http.server + import email.message + + class DocHandler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + """Process a request from an HTML browser. + + The URL received is in self.path. + Get an HTML page from self.urlhandler and send it. + """ + if self.path.endswith(".css"): + content_type = "text/css" + else: + content_type = "text/html" + self.send_response(200) + self.send_header("Content-Type", "%s; charset=UTF-8" % content_type) + self.end_headers() + self.wfile.write(self.urlhandler(self.path, content_type).encode("utf-8")) # type: ignore + + def log_message(self, *args): + # Don't log messages. + pass + + class DocServer(http.server.HTTPServer): + def __init__(self, host, port, callback): + self.host = host + self.address = (self.host, port) + self.callback = callback + self.base.__init__(self, self.address, self.handler) # type: ignore + self.quit = False + + def serve_until_quit(self): + while not self.quit: + rd, wr, ex = select.select([self.socket.fileno()], [], [], 1) + if rd: + self.handle_request() + self.server_close() + + def server_activate(self): + self.base.server_activate(self) # type: ignore + if self.callback: + self.callback(self) + + class ServerThread(threading.Thread): + def __init__(self, urlhandler, host, port): + self.urlhandler = urlhandler + self.host = host + self.port = int(port) + threading.Thread.__init__(self) + self.serving = False + self.error = None + + def run(self): + """Start the server.""" + try: + DocServer.base = http.server.HTTPServer # type: ignore + DocServer.handler = DocHandler # type: ignore + DocHandler.MessageClass = email.message.Message # type: ignore + DocHandler.urlhandler = staticmethod(self.urlhandler) # type: ignore + docsvr = DocServer(self.host, self.port, self.ready) + self.docserver = docsvr + docsvr.serve_until_quit() + except Exception as e: + self.error = e + + def ready(self, server): + self.serving = True + self.host = server.host + self.port = server.server_port + self.url = "http://%s:%d/" % (self.host, self.port) + + def stop(self): + """Stop the server and this thread nicely""" + if self.docserver != None: + self.docserver.quit = True + self.join() + # explicitly break a reference cycle: DocServer.callback + # has indirectly a reference to ServerThread. + self.docserver = None + self.serving = False + self.url = None + + thread = ServerThread(urlhandler, hostname, port) + thread.start() + # Wait until thread.serving is True to make sure we are + # really up before returning. + while not thread.error and not thread.serving: + time.sleep(0.01) + return thread + + +def start_server(): + """ + 验证码服务打开服务器 + + Returns: + ServerThread: 服务进程 + + 返回值内函数及属性: + - url (str) : 验证码服务地址 + - start (Callable): 开启进程 + - stop (Callable): 结束进程 + """ + global thread + thread = _start_server(_geetest_urlhandler, "127.0.0.1", 0) + print("请打开 " + thread.url + " 进行验证。") # type: ignore + return thread + + +def close_server(): + """ + 关闭服务器 + """ + global thread + thread.stop() # type: ignore + + +def get_result(): + """ + 获取结果 + + Returns: + dict: 验证结果 + """ + global validate, seccode, challenge, gt, key + if ( + validate is None + or seccode is None + or gt is None + or challenge is None + or key is None + ): + return -1 + else: + dct = { + "gt": copy.copy(gt), + "challenge": copy.copy(challenge), + "validate": copy.copy(validate), + "seccode": copy.copy(seccode), + "token": copy.copy(key), + } + return dct diff --git a/bilibili_api/utils/credential.py b/bilibili_api/utils/credential.py new file mode 100644 index 0000000000000000000000000000000000000000..8b96cf1de61c4c53c34ffe4a6956bfd722987a9f --- /dev/null +++ b/bilibili_api/utils/credential.py @@ -0,0 +1,169 @@ +""" +bilibili_api.utils.Credential + +凭据类,用于各种请求操作的验证。 +""" + +import uuid +from typing import Union +import urllib.parse + +from ..exceptions import ( + CredentialNoBuvid3Exception, + CredentialNoBiliJctException, + CredentialNoSessdataException, + CredentialNoDedeUserIDException, + CredentialNoAcTimeValueException, +) + + +class Credential: + """ + 凭据类,用于各种请求操作的验证。 + """ + + def __init__( + self, + sessdata: Union[str, None] = None, + bili_jct: Union[str, None] = None, + buvid3: Union[str, None] = None, + dedeuserid: Union[str, None] = None, + ac_time_value: Union[str, None] = None, + ) -> None: + """ + 各字段获取方式查看:https://nemo2011.github.io/bilibili-api/#/get-credential.md + + Args: + sessdata (str | None, optional): 浏览器 Cookies 中的 SESSDATA 字段值. Defaults to None. + + bili_jct (str | None, optional): 浏览器 Cookies 中的 bili_jct 字段值. Defaults to None. + + buvid3 (str | None, optional): 浏览器 Cookies 中的 BUVID3 字段值. Defaults to None. + + dedeuserid (str | None, optional): 浏览器 Cookies 中的 DedeUserID 字段值. Defaults to None. + + ac_time_value (str | None, optional): 浏览器 Cookies 中的 ac_time_value 字段值. Defaults to None. + """ + self.sessdata = ( + None + if sessdata is None + else ( + sessdata if sessdata.find("%") != -1 else urllib.parse.quote(sessdata) + ) + ) + self.bili_jct = bili_jct + self.buvid3 = buvid3 + self.dedeuserid = dedeuserid + self.ac_time_value = ac_time_value + + def get_cookies(self) -> dict: + """ + 获取请求 Cookies 字典 + + Returns: + dict: 请求 Cookies 字典 + """ + cookies = { + "SESSDATA": self.sessdata, + "buvid3": self.buvid3, + "bili_jct": self.bili_jct, + "ac_time_value": self.ac_time_value, + } + if self.dedeuserid: + cookies.update({"DedeUserID": self.dedeuserid}) + return cookies + + def has_dedeuserid(self) -> bool: + """ + 是否提供 dedeuserid。 + + Returns: + bool。 + """ + return self.dedeuserid is not None and self.sessdata != "" + + def has_sessdata(self) -> bool: + """ + 是否提供 sessdata。 + + Returns: + bool。 + """ + return self.sessdata is not None and self.sessdata != "" + + def has_bili_jct(self) -> bool: + """ + 是否提供 bili_jct。 + + Returns: + bool。 + """ + return self.bili_jct is not None and self.sessdata != "" + + def has_buvid3(self) -> bool: + """ + 是否提供 buvid3 + + Returns: + bool. + """ + return self.buvid3 is not None and self.sessdata != "" + + def has_ac_time_value(self) -> bool: + """ + 是否提供 ac_time_value + + Returns: + bool. + """ + return self.ac_time_value is not None and self.sessdata != "" + + def raise_for_no_sessdata(self): + """ + 没有提供 sessdata 则抛出异常。 + """ + if not self.has_sessdata(): + raise CredentialNoSessdataException() + + def raise_for_no_bili_jct(self): + """ + 没有提供 bili_jct 则抛出异常。 + """ + if not self.has_bili_jct(): + raise CredentialNoBiliJctException() + + def raise_for_no_buvid3(self): + """ + 没有提供 buvid3 时抛出异常。 + """ + if not self.has_buvid3(): + raise CredentialNoBuvid3Exception() + + def raise_for_no_dedeuserid(self): + """ + 没有提供 DedeUserID 时抛出异常。 + """ + if not self.has_dedeuserid(): + raise CredentialNoDedeUserIDException() + + def raise_for_no_ac_time_value(self): + """ + 没有提供 ac_time_value 时抛出异常。 + """ + if not self.has_ac_time_value(): + raise CredentialNoAcTimeValueException() + + async def check_valid(self): + """ + 检查 cookies 是否有效 + + Returns: + bool: cookies 是否有效 + """ + + # def generate_buvid3(self): + # """ + # 生成 buvid3 + # """ + # self.buvid3 = str(uuid.uuid1()) + "infoc" + # 长度都不同了...用 credential.get_spi_buvid diff --git a/bilibili_api/utils/credential_refresh.py b/bilibili_api/utils/credential_refresh.py new file mode 100644 index 0000000000000000000000000000000000000000..1a016f43c5ebe7a19fdcd9dc7f0188805d124f0d --- /dev/null +++ b/bilibili_api/utils/credential_refresh.py @@ -0,0 +1,207 @@ +""" +from bilibili_api import Credential + +凭据操作类 +""" + +import re +import time +import uuid +import binascii +from typing import Union + +from Cryptodome.Hash import SHA256 +from Cryptodome.PublicKey import RSA +from Cryptodome.Cipher import PKCS1_OAEP + +from .credential import Credential as _Credential +from .network import Api, get_api, get_session, HEADERS + +key = RSA.importKey( + """\ +-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLgd2OAkcGVtoE3ThUREbio0Eg +Uc/prcajMKXvkCKFCWhJYJcLkcM2DKKcSeFpD/j6Boy538YXnR6VhcuUJOhH2x71 +nzPjfdTcqMz7djHum0qSZA0AyCBDABUqCrfNgCiJ00Ra7GmRj+YCK1NJEuewlb40 +JNrRuoEUXpabUzGB8QIDAQAB +-----END PUBLIC KEY-----""" +) + +API = get_api("credential") + + +class Credential(_Credential): + """ + 凭据操作类,用于各种请求操作。 + """ + + async def check_refresh(self) -> bool: + """ + 检查是否需要刷新 cookies + + Returns: + bool: cookies 是否需要刷新 + """ + return await check_cookies(self) + + async def refresh(self) -> None: + """ + 刷新 cookies + """ + new_cred: Credential = await refresh_cookies(self) + self.sessdata = new_cred.sessdata + self.bili_jct = new_cred.bili_jct + self.dedeuserid = new_cred.dedeuserid + self.ac_time_value = new_cred.ac_time_value + + async def check_valid(self) -> bool: + """ + 检查 cookies 是否有效 + + Returns: + bool: cookies 是否有效 + """ + data = await Api( + credential=self, **get_api("credential")["info"]["valid"] + ).result + return data["isLogin"] + + @staticmethod + def from_cookies(cookies: dict={}) -> "Credential": + """ + 从 cookies 新建 Credential + + Args: + cookies (dict, optional): Cookies. Defaults to {}. + + Returns: + Credential: 凭据类 + """ + c = Credential() + c.sessdata = cookies.get("SESSDATA") + c.bili_jct = cookies.get("bili_jct") + c.buvid3 = cookies.get("buvid3") + c.dedeuserid = cookies.get("DedeUserID") + c.ac_time_value = cookies.get("ac_time_value") + return c + + +""" +Cookies 刷新相关 + +感谢 bilibili-API-collect 提供的刷新 Cookies 的思路 + +https://socialsisteryi.github.io/bilibili-API-collect/docs/login/cookie_refresh.html +""" + + +async def check_cookies(credential: Credential) -> bool: + """ + 检查是否需要刷新 Cookies + + Args: + credential (Credential): 用户凭证 + + Return: + bool: 是否需要刷新 Cookies + """ + api = API["info"]["check_cookies"] + return (await Api(**api, credential=credential).result)["refresh"] + + +def getCorrespondPath() -> str: + """ + 根据时间生成 CorrespondPath + + Return: + str: CorrespondPath + """ + ts = round(time.time() * 1000) + cipher = PKCS1_OAEP.new(key, SHA256) + encrypted = cipher.encrypt(f"refresh_{ts}".encode()) + return binascii.b2a_hex(encrypted).decode() + + +async def get_refresh_csrf(credential: Credential) -> str: + """ + 获取刷新 Cookies 的 csrf + + Return: + str: csrf + """ + correspond_path = getCorrespondPath() + api = API["operate"]["get_refresh_csrf"] + cookies = credential.get_cookies() + cookies["buvid3"] = str(uuid.uuid1()) + cookies["Domain"] = ".bilibili.com" + resp = await get_session().request( + "GET", + api["url"].replace("{correspondPath}", correspond_path), + cookies=cookies, + headers=HEADERS.copy(), + ) + if resp.status_code == 404: + raise Exception("correspondPath 过期或错误。") + elif resp.status_code == 200: + text = resp.text + refresh_csrf = re.findall('
(.+?)
', text)[0] + return refresh_csrf + elif resp.status_code != 200: + raise Exception("获取刷新 Cookies 的 csrf 失败。") + + +async def refresh_cookies(credential: Credential) -> Credential: + """ + 刷新 Cookies + + Args: + credential (Credential): 用户凭证 + + Return: + Credential: 新的用户凭证 + """ + api = API["operate"]["refresh_cookies"] + credential.raise_for_no_bili_jct() + credential.raise_for_no_ac_time_value() + refresh_csrf = await get_refresh_csrf(credential) + data = { + "csrf": credential.bili_jct, + "refresh_csrf": refresh_csrf, + "refresh_token": credential.ac_time_value, + "source": "main_web", + } + cookies = credential.get_cookies() + cookies["buvid3"] = str(uuid.uuid1()) + cookies["Domain"] = ".bilibili.com" + resp = await get_session().request( + "POST", api["url"], cookies=cookies, data=data, headers=HEADERS.copy() + ) + if resp.status_code != 200 or resp.json()["code"] != 0: + raise Exception("刷新 Cookies 失败") + new_credential = Credential( + sessdata=resp.cookies["SESSDATA"], + bili_jct=resp.cookies["bili_jct"], + dedeuserid=resp.cookies["DedeUserID"], + ac_time_value=resp.json()["data"]["refresh_token"], + ) + await confirm_refresh(credential, new_credential) + return new_credential + + +async def confirm_refresh( + old_credential: Credential, new_credential: Credential +) -> None: + """ + 让旧的refresh_token对应的 Cookie 失效 + + Args: + old_credential (Credential): 旧的用户凭证 + + new_credential (Credential): 新的用户凭证 + """ + api = API["operate"]["confirm_refresh"] + data = { + "csrf": new_credential.bili_jct, + "refresh_token": old_credential.ac_time_value, + } + await Api(**api, credential=new_credential).update_data(**data).result diff --git a/bilibili_api/utils/danmaku.py b/bilibili_api/utils/danmaku.py new file mode 100644 index 0000000000000000000000000000000000000000..3dbecb9a18e698b35c7195c954c8e48dd123a599 --- /dev/null +++ b/bilibili_api/utils/danmaku.py @@ -0,0 +1,178 @@ +""" +bilibili_api.utils.Danmaku + +弹幕类。 +""" + +import time +from enum import Enum +from typing import Union + +from .utils import crack_uid as _crack_uid + + +class DmFontSize(Enum): + """ + 字体大小枚举。 + """ + + EXTREME_SMALL = 12 + SUPER_SMALL = 16 + SMALL = 18 + NORMAL = 25 + BIG = 36 + SUPER_BIG = 45 + EXTREME_BIG = 64 + + +class DmMode(Enum): + """ + 弹幕模式枚举。 + """ + + FLY = 1 + TOP = 5 + BOTTOM = 4 + REVERSE = 6 + SPECIAL = 9 + + +class Danmaku: + """ + 弹幕类。 + """ + + def __init__( + self, + text: str, + dm_time: float = 0.0, + send_time: float = time.time(), + crc32_id: str = "", + color: str = "ffffff", + weight: int = -1, + id_: int = -1, + id_str: str = "", + action: str = "", + mode: Union[DmMode, int] = DmMode.FLY, + font_size: Union[DmFontSize, int] = DmFontSize.NORMAL, + is_sub: bool = False, + pool: int = 0, + attr: int = -1, + uid: int = -1, + ): + """ + Args: + (self.)text (str) : 弹幕文本。 + + (self.)dm_time (float, optional) : 弹幕在视频中的位置,单位为秒。Defaults to 0.0. + + (self.)send_time (float, optional) : 弹幕发送的时间。Defaults to time.time(). + + (self.)crc32_id (str, optional) : 弹幕发送者 UID 经 CRC32 算法取摘要后的值。Defaults to "". + + (self.)color (str, optional) : 弹幕十六进制颜色。Defaults to "ffffff" (如果为大会员专属的颜色则为"special"). + + (self.)weight (int, optional) : 弹幕在弹幕列表显示的权重。Defaults to -1. + + (self.)id_ (int, optional) : 弹幕 ID。Defaults to -1. + + (self.)id_str (str, optional) : 弹幕字符串 ID。Defaults to "". + + (self.)action (str, optional) : 暂不清楚。Defaults to "". + + (self.)mode (Union[DmMode, int], optional) : 弹幕模式。Defaults to Mode.FLY. + + (self.)font_size (Union[DmFontSize, int], optional): 弹幕字体大小。Defaults to FontSize.NORMAL. + + (self.)is_sub (bool, optional) : 是否为字幕弹幕。Defaults to False. + + (self.)pool (int, optional) : 池。Defaults to 0. + + (self.)attr (int, optional) : 暂不清楚。 Defaults to -1. + + (self.)uid (int, optional) : 弹幕发送者 UID。Defaults to -1. + + 大会员专属颜色文字填充:http://i0.hdslb.com/bfs/dm/9dcd329e617035b45d2041ac889c49cb5edd3e44.png + + 大会员专属颜色背景填充:http://i0.hdslb.com/bfs/dm/ba8e32ae03a0a3f70f4e51975a965a9ddce39d50.png + """ + self.text = text + self.dm_time = dm_time + self.send_time = send_time + self.crc32_id = crc32_id + self.color = color + self.weight = weight + self.id_ = id_ + self.id_str = id_str + self.action = action + self.mode = mode.value if isinstance(mode, DmMode) else mode + self.font_size = ( + font_size.value if isinstance(font_size, DmFontSize) else font_size + ) + self.is_sub = is_sub + self.pool = pool + self.attr = attr + self.uid = uid + + def __str__(self): + ret = "%s, %s, %s" % (self.send_time, self.dm_time, self.text) + return ret + + def __len__(self): + return len(self.text) + + @staticmethod + def crack_uid(crc32_id: str): + """ + (@staticmethod) + + 暴力破解 UID,可能存在误差,请慎重使用。 + + 精确至 UID 小于 10000000 的破解。 + + Args: + crc32_id (str): crc32 id + + Returns: + int: 真实 UID。 + """ + return int(_crack_uid(crc32_id)) + + def to_xml(self): + """ + 将弹幕转换为 xml 格式弹幕 + """ + txt = self.text.replace("&", "&").replace("<", "<").replace(">", ">") + string = f'{txt}' + return string + + +class SpecialDanmaku: + def __init__( + self, + content: str, + id_: int = -1, + id_str: str = "", + mode: Union[DmMode, int] = DmMode.SPECIAL, + pool: int = 2, + ): + """ + Args: + (self.)content (str) : 弹幕内容 + + (self.)id_ (int) : 弹幕 id. Defaults to -1. + + (self.)id_str (str) : 弹幕 id (string 类型). Defaults to "". + + (self.)mode (Union[DmMode, int]): 弹幕类型. Defaults to DmMode.SPECIAL. + + (self.)pool (int) : 弹幕池. Defaults to 2. + """ + self.content = content + self.id_ = id_ + self.id_str = id_str + self.mode = mode.value if isinstance(mode, DmMode) else mode + self.pool = pool + + def __str__(self): + return f"{self.content}" diff --git a/bilibili_api/utils/danmaku2ass.py b/bilibili_api/utils/danmaku2ass.py new file mode 100644 index 0000000000000000000000000000000000000000..bbea273c2952bd4206766ba52d1ad6062d08ccec --- /dev/null +++ b/bilibili_api/utils/danmaku2ass.py @@ -0,0 +1,1343 @@ +#!/usr/bin/env python3 + +# The original author of this program, Danmaku2ASS, is StarBrilliant. +# This file is released under General Public License version 3. +# You should have received a copy of General Public License text alongside with +# this program. If not, you can obtain it at http://gnu.org/copyleft/gpl.html . +# This program comes with no warranty, the author will not be resopnsible for +# any damage or problems caused by this program. + +# You can obtain a latest copy of Danmaku2ASS at: +# https://github.com/m13253/danmaku2ass +# Please update to the latest version before complaining. + +# pylint: skip-file +# type: ignore + +import io +import os +import re +import sys +import json +import math +import time +import random +import gettext +import logging +import argparse +import calendar +import xml.dom.minidom + +if sys.version_info < (3,): + raise RuntimeError("at least Python 3.0 is required") + +gettext.install( + "danmaku2ass", + os.path.join( + os.path.dirname(os.path.abspath(os.path.realpath(sys.argv[0] or "locale"))), + "locale", + ), +) + + +def SeekZero(function): + def decorated_function(file_): + file_.seek(0) + try: + return function(file_) + finally: + file_.seek(0) + + return decorated_function + + +def EOFAsNone(function): + def decorated_function(*args, **kwargs): + try: + return function(*args, **kwargs) + except EOFError: + return None + + return decorated_function + + +@SeekZero +@EOFAsNone +def ProbeCommentFormat(f): + tmp = f.read(1) + if tmp == "[": + return "Acfun" + # It is unwise to wrap a JSON object in an array! + # See this: http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx/ + # Do never follow what Acfun developers did! + elif tmp == "{": + tmp = f.read(14) + if tmp == '"status_code":': + return "Tudou" + elif tmp.strip().startswith('"result'): + return "Tudou2" + elif tmp == "<": + tmp = f.read(1) + if tmp == "?": + tmp = f.read(38) + if tmp == 'xml version="1.0" encoding="UTF-8"?>\n<': + return "Bilibili" # Komica, with the same file format as Bilibili + elif tmp == 'xml version="1.0" encoding="UTF-8"?>\n<': + tmp = f.read(20) + if tmp == "!-- BoonSutazioData=": + return "Niconico" # Niconico videos downloaded with NicoFox + else: + return "MioMio" + elif tmp == "p": + return "Niconico" # Himawari Douga, with the same file format as Niconico Douga + + +# +# ReadComments**** protocol +# +# Input: +# f: Input file +# fontsize: Default font size +# +# Output: +# yield a tuple: +# (timeline, timestamp, no, comment, pos, color, size, height, width) +# timeline: The position when the comment is replayed +# timestamp: The UNIX timestamp when the comment is submitted +# no: A sequence of 1, 2, 3, ..., used for sorting +# comment: The content of the comment +# pos: 0 for regular moving comment, +# 1 for bottom centered comment, +# 2 for top centered comment, +# 3 for reversed moving comment +# color: Font color represented in 0xRRGGBB, +# e.g. 0xffffff for white +# size: Font size +# height: The estimated height in pixels +# i.e. (comment.count('\n')+1)*size +# width: The estimated width in pixels +# i.e. CalculateLength(comment)*size +# +# After implementing ReadComments****, make sure to update ProbeCommentFormat +# and CommentFormatMap. +# + + +def ReadCommentsNiconico(f, fontsize): + NiconicoColorMap = { + "red": 0xFF0000, + "pink": 0xFF8080, + "orange": 0xFFCC00, + "yellow": 0xFFFF00, + "green": 0x00FF00, + "cyan": 0x00FFFF, + "blue": 0x0000FF, + "purple": 0xC000FF, + "black": 0x000000, + "niconicowhite": 0xCCCC99, + "white2": 0xCCCC99, + "truered": 0xCC0033, + "red2": 0xCC0033, + "passionorange": 0xFF6600, + "orange2": 0xFF6600, + "madyellow": 0x999900, + "yellow2": 0x999900, + "elementalgreen": 0x00CC66, + "green2": 0x00CC66, + "marineblue": 0x33FFCC, + "blue2": 0x33FFCC, + "nobleviolet": 0x6633CC, + "purple2": 0x6633CC, + } + dom = xml.dom.minidom.parse(f) + comment_element = dom.getElementsByTagName("chat") + for comment in comment_element: + try: + c = str(comment.childNodes[0].wholeText) + if c.startswith("/"): + continue # ignore advanced comments + pos = 0 + color = 0xFFFFFF + size = fontsize + for mailstyle in str(comment.getAttribute("mail")).split(): + if mailstyle == "ue": + pos = 1 + elif mailstyle == "shita": + pos = 2 + elif mailstyle == "big": + size = fontsize * 1.44 + elif mailstyle == "small": + size = fontsize * 0.64 + elif mailstyle in NiconicoColorMap: + color = NiconicoColorMap[mailstyle] + yield ( + max(int(comment.getAttribute("vpos")), 0) * 0.01, + int(comment.getAttribute("date")), + int(comment.getAttribute("no")), + c, + pos, + color, + size, + (c.count("\n") + 1) * size, + CalculateLength(c) * size, + ) + except (AssertionError, AttributeError, IndexError, TypeError, ValueError): + logging.warning(_("Invalid comment: %s") % comment.toxml()) + continue + + +def ReadCommentsAcfun(f, fontsize): + # comment_element = json.load(f) + # after load acfun comment json file as python list, flatten the list + # comment_element = [c for sublist in comment_element for c in sublist] + comment_elements = json.load(f) + comment_element = comment_elements[2] + for i, comment in enumerate(comment_element): + try: + p = str(comment["c"]).split(",") + assert len(p) >= 6 + assert p[2] in ("1", "2", "4", "5", "7") + size = int(p[3]) * fontsize / 25.0 + if p[2] != "7": + c = str(comment["m"]).replace("\\r", "\n").replace("\r", "\n") + yield ( + float(p[0]), + int(p[5]), + i, + c, + {"1": 0, "2": 0, "4": 2, "5": 1}[p[2]], + int(p[1]), + size, + (c.count("\n") + 1) * size, + CalculateLength(c) * size, + ) + else: + c = dict(json.loads(comment["m"])) + yield (float(p[0]), int(p[5]), i, c, "acfunpos", int(p[1]), size, 0, 0) + except (AssertionError, AttributeError, IndexError, TypeError, ValueError): + logging.warning(_("Invalid comment: %r") % comment) + continue + + +def ReadCommentsBilibili(f, fontsize): + dom = xml.dom.minidom.parse(f) + comment_element = dom.getElementsByTagName("d") + for i, comment in enumerate(comment_element): + try: + p = str(comment.getAttribute("p")).split(",") + assert len(p) >= 5 + assert p[1] in ("1", "4", "5", "6", "7", "8") + if comment.childNodes.length > 0: + if p[1] in ("1", "4", "5", "6"): + c = str(comment.childNodes[0].wholeText).replace("/n", "\n") + size = int(p[2]) * fontsize / 25.0 + yield ( + float(p[0]), + int(p[4]), + i, + c, + {"1": 0, "4": 2, "5": 1, "6": 3}[p[1]], + int(p[3]), + size, + (c.count("\n") + 1) * size, + CalculateLength(c) * size, + ) + elif p[1] == "7": # positioned comment + c = str(comment.childNodes[0].wholeText) + yield ( + float(p[0]), + int(p[4]), + i, + c, + "bilipos", + int(p[3]), + int(p[2]), + 0, + 0, + ) + elif p[1] == "8": + pass # ignore scripted comment + except (AssertionError, AttributeError, IndexError, TypeError, ValueError): + logging.warning(_("Invalid comment: %s") % comment.toxml()) + continue + + +def ReadCommentsBilibili2(f, fontsize): + dom = xml.dom.minidom.parse(f) + comment_element = dom.getElementsByTagName("d") + for i, comment in enumerate(comment_element): + try: + p = str(comment.getAttribute("p")).split(",") + assert len(p) >= 7 + assert p[3] in ("1", "4", "5", "6", "7", "8") + if comment.childNodes.length > 0: + time = float(p[2]) / 1000.0 + if p[3] in ("1", "4", "5", "6"): + c = str(comment.childNodes[0].wholeText).replace("/n", "\n") + size = int(p[4]) * fontsize / 25.0 + yield ( + time, + int(p[6]), + i, + c, + {"1": 0, "4": 2, "5": 1, "6": 3}[p[3]], + int(p[5]), + size, + (c.count("\n") + 1) * size, + CalculateLength(c) * size, + ) + elif p[3] == "7": # positioned comment + c = str(comment.childNodes[0].wholeText) + yield (time, int(p[6]), i, c, "bilipos", int(p[5]), int(p[4]), 0, 0) + elif p[3] == "8": + pass # ignore scripted comment + except (AssertionError, AttributeError, IndexError, TypeError, ValueError): + logging.warning(_("Invalid comment: %s") % comment.toxml()) + continue + + +def ReadCommentsTudou(f, fontsize): + comment_element = json.load(f) + for i, comment in enumerate(comment_element["comment_list"]): + try: + assert comment["pos"] in (3, 4, 6) + c = str(comment["data"]) + assert comment["size"] in (0, 1, 2) + size = {0: 0.64, 1: 1, 2: 1.44}[comment["size"]] * fontsize + yield ( + int(comment["replay_time"] * 0.001), + int(comment["commit_time"]), + i, + c, + {3: 0, 4: 2, 6: 1}[comment["pos"]], + int(comment["color"]), + size, + (c.count("\n") + 1) * size, + CalculateLength(c) * size, + ) + except (AssertionError, AttributeError, IndexError, TypeError, ValueError): + logging.warning(_("Invalid comment: %r") % comment) + continue + + +def ReadCommentsTudou2(f, fontsize): + comment_element = json.load(f) + for i, comment in enumerate(comment_element["result"]): + try: + c = str(comment["content"]) + prop = json.loads(str(comment["propertis"]) or "{}") + size = int(prop.get("size", 1)) + assert size in (0, 1, 2) + size = {0: 0.64, 1: 1, 2: 1.44}[size] * fontsize + pos = int(prop.get("pos", 3)) + assert pos in (0, 3, 4, 6) + yield ( + int(comment["playat"] * 0.001), + int(comment["createtime"] * 0.001), + i, + c, + {0: 0, 3: 0, 4: 2, 6: 1}[pos], + int(prop.get("color", 0xFFFFFF)), + size, + (c.count("\n") + 1) * size, + CalculateLength(c) * size, + ) + except (AssertionError, AttributeError, IndexError, TypeError, ValueError): + logging.warning(_("Invalid comment: %r") % comment) + continue + + +def ReadCommentsMioMio(f, fontsize): + NiconicoColorMap = { + "red": 0xFF0000, + "pink": 0xFF8080, + "orange": 0xFFC000, + "yellow": 0xFFFF00, + "green": 0x00FF00, + "cyan": 0x00FFFF, + "blue": 0x0000FF, + "purple": 0xC000FF, + "black": 0x000000, + } + dom = xml.dom.minidom.parse(f) + comment_element = dom.getElementsByTagName("data") + for i, comment in enumerate(comment_element): + try: + message = comment.getElementsByTagName("message")[0] + c = str(message.childNodes[0].wholeText) + pos = 0 + size = int(message.getAttribute("fontsize")) * fontsize / 25.0 + yield ( + float( + comment.getElementsByTagName("playTime")[0].childNodes[0].wholeText + ), + int( + calendar.timegm( + time.strptime( + comment.getElementsByTagName("times")[0] + .childNodes[0] + .wholeText, + "%Y-%m-%d %H:%M:%S", + ) + ) + ) + - 28800, + i, + c, + {"1": 0, "4": 2, "5": 1}[message.getAttribute("mode")], + int(message.getAttribute("color")), + size, + (c.count("\n") + 1) * size, + CalculateLength(c) * size, + ) + except (AssertionError, AttributeError, IndexError, TypeError, ValueError): + logging.warning(_("Invalid comment: %s") % comment.toxml()) + continue + + +CommentFormatMap = { + "Niconico": ReadCommentsNiconico, + "Acfun": ReadCommentsAcfun, + "Bilibili": ReadCommentsBilibili, + "Bilibili2": ReadCommentsBilibili2, + "Tudou": ReadCommentsTudou, + "Tudou2": ReadCommentsTudou2, + "MioMio": ReadCommentsMioMio, +} + + +def WriteCommentBilibiliPositioned(f, c, width, height, styleid): + # BiliPlayerSize = (512, 384) # Bilibili player version 2010 + # BiliPlayerSize = (540, 384) # Bilibili player version 2012 + BiliPlayerSize = (672, 438) # Bilibili player version 2014 + ZoomFactor = GetZoomFactor(BiliPlayerSize, (width, height)) + + def GetPosition(InputPos, isHeight): + isHeight = int(isHeight) # True -> 1 + if isinstance(InputPos, int): + return ZoomFactor[0] * InputPos + ZoomFactor[isHeight + 1] + elif isinstance(InputPos, float): + if InputPos > 1: + return ZoomFactor[0] * InputPos + ZoomFactor[isHeight + 1] + else: + return ( + BiliPlayerSize[isHeight] * ZoomFactor[0] * InputPos + + ZoomFactor[isHeight + 1] + ) + else: + try: + InputPos = int(InputPos) + except ValueError: + InputPos = float(InputPos) + return GetPosition(InputPos, isHeight) + + try: + comment_args = safe_list(json.loads(c[3])) + text = ASSEscape(str(comment_args[4]).replace("/n", "\n")) + from_x = comment_args.get(0, 0) + from_y = comment_args.get(1, 0) + to_x = comment_args.get(7, from_x) + to_y = comment_args.get(8, from_y) + from_x = GetPosition(from_x, False) + from_y = GetPosition(from_y, True) + to_x = GetPosition(to_x, False) + to_y = GetPosition(to_y, True) + alpha = safe_list(str(comment_args.get(2, "1")).split("-")) + from_alpha = float(alpha.get(0, 1)) + to_alpha = float(alpha.get(1, from_alpha)) + from_alpha = 255 - round(from_alpha * 255) + to_alpha = 255 - round(to_alpha * 255) + rotate_z = int(comment_args.get(5, 0)) + rotate_y = int(comment_args.get(6, 0)) + lifetime = float(comment_args.get(3, 4500)) + duration = int(comment_args.get(9, lifetime * 1000)) + delay = int(comment_args.get(10, 0)) + fontface = comment_args.get(12) + isborder = comment_args.get(11, "true") + from_rotarg = ConvertFlashRotation( + rotate_y, rotate_z, from_x, from_y, width, height + ) + to_rotarg = ConvertFlashRotation(rotate_y, rotate_z, to_x, to_y, width, height) + styles = ["\\org(%d, %d)" % (width / 2, height / 2)] + if from_rotarg[0:2] == to_rotarg[0:2]: + styles.append("\\pos(%.0f, %.0f)" % (from_rotarg[0:2])) + else: + styles.append( + "\\move(%.0f, %.0f, %.0f, %.0f, %.0f, %.0f)" + % (from_rotarg[0:2] + to_rotarg[0:2] + (delay, delay + duration)) + ) + styles.append( + "\\frx%.0f\\fry%.0f\\frz%.0f\\fscx%.0f\\fscy%.0f" % (from_rotarg[2:7]) + ) + if (from_x, from_y) != (to_x, to_y): + styles.append("\\t(%d, %d, " % (delay, delay + duration)) + styles.append( + "\\frx%.0f\\fry%.0f\\frz%.0f\\fscx%.0f\\fscy%.0f" % (to_rotarg[2:7]) + ) + styles.append(")") + if fontface: + styles.append("\\fn%s" % ASSEscape(fontface)) + styles.append("\\fs%.0f" % (c[6] * ZoomFactor[0])) + if c[5] != 0xFFFFFF: + styles.append("\\c&H%s&" % ConvertColor(c[5])) + if c[5] == 0x000000: + styles.append("\\3c&HFFFFFF&") + if from_alpha == to_alpha: + styles.append("\\alpha&H%02X" % from_alpha) + elif (from_alpha, to_alpha) == (255, 0): + styles.append("\\fad(%.0f,0)" % (lifetime * 1000)) + elif (from_alpha, to_alpha) == (0, 255): + styles.append("\\fad(0, %.0f)" % (lifetime * 1000)) + else: + styles.append( + "\\fade(%(from_alpha)d, %(to_alpha)d, %(to_alpha)d, 0, %(end_time).0f, %(end_time).0f, %(end_time).0f)" + % { + "from_alpha": from_alpha, + "to_alpha": to_alpha, + "end_time": lifetime * 1000, + } + ) + if isborder == "false": + styles.append("\\bord0") + f.write( + "Dialogue: -1,%(start)s,%(end)s,%(styleid)s,,0,0,0,,{%(styles)s}%(text)s\n" + % { + "start": ConvertTimestamp(c[0]), + "end": ConvertTimestamp(c[0] + lifetime), + "styles": "".join(styles), + "text": text, + "styleid": styleid, + } + ) + except (IndexError, ValueError) as e: + try: + logging.warning(_("Invalid comment: %r") % c[3]) + except IndexError: + logging.warning(_("Invalid comment: %r") % c) + + +def WriteCommentAcfunPositioned(f, c, width, height, styleid): + AcfunPlayerSize = (560, 400) + ZoomFactor = GetZoomFactor(AcfunPlayerSize, (width, height)) + + def GetPosition(InputPos, isHeight): + isHeight = int(isHeight) # True -> 1 + return ( + AcfunPlayerSize[isHeight] * ZoomFactor[0] * InputPos * 0.001 + + ZoomFactor[isHeight + 1] + ) + + def GetTransformStyles( + x=None, + y=None, + scale_x=None, + scale_y=None, + rotate_z=None, + rotate_y=None, + color=None, + alpha=None, + ): + styles = [] + out_x, out_y = x, y + if rotate_z is not None and rotate_y is not None: + assert x is not None + assert y is not None + rotarg = ConvertFlashRotation(rotate_y, rotate_z, x, y, width, height) + out_x, out_y = rotarg[0:2] + if scale_x is None: + scale_x = 1 + if scale_y is None: + scale_y = 1 + styles.append( + "\\frx%.0f\\fry%.0f\\frz%.0f\\fscx%.0f\\fscy%.0f" + % (rotarg[2:5] + (rotarg[5] * scale_x, rotarg[6] * scale_y)) + ) + else: + if scale_x is not None: + styles.append("\\fscx%.0f" % (scale_x * 100)) + if scale_y is not None: + styles.append("\\fscy%.0f" % (scale_y * 100)) + if color is not None: + styles.append("\\c&H%s&" % ConvertColor(color)) + if color == 0x000000: + styles.append("\\3c&HFFFFFF&") + if alpha is not None: + alpha = 255 - round(alpha * 255) + styles.append("\\alpha&H%02X" % alpha) + return out_x, out_y, styles + + def FlushCommentLine(f, text, styles, start_time, end_time, styleid): + if end_time > start_time: + f.write( + "Dialogue: -1,%(start)s,%(end)s,%(styleid)s,,0,0,0,,{%(styles)s}%(text)s\n" + % { + "start": ConvertTimestamp(start_time), + "end": ConvertTimestamp(end_time), + "styles": "".join(styles), + "text": text, + "styleid": styleid, + } + ) + + try: + comment_args = c[3] + text = ASSEscape(str(comment_args["n"]).replace("\r", "\n")) + common_styles = ["\org(%d, %d)" % (width / 2, height / 2)] + anchor = {0: 7, 1: 8, 2: 9, 3: 4, 4: 5, 5: 6, 6: 1, 7: 2, 8: 3}.get( + comment_args.get("c", 0), 7 + ) + if anchor != 7: + common_styles.append("\\an%s" % anchor) + font = comment_args.get("w") + if font: + font = dict(font) + fontface = font.get("f") + if fontface: + common_styles.append("\\fn%s" % ASSEscape(str(fontface))) + fontbold = bool(font.get("b")) + if fontbold: + common_styles.append("\\b1") + common_styles.append("\\fs%.0f" % (c[6] * ZoomFactor[0])) + isborder = bool(comment_args.get("b", True)) + if not isborder: + common_styles.append("\\bord0") + to_pos = dict(comment_args.get("p", {"x": 0, "y": 0})) + to_x = round(GetPosition(int(to_pos.get("x", 0)), False)) + to_y = round(GetPosition(int(to_pos.get("y", 0)), True)) + to_scale_x = float(comment_args.get("e", 1.0)) + to_scale_y = float(comment_args.get("f", 1.0)) + to_rotate_z = float(comment_args.get("r", 0.0)) + to_rotate_y = float(comment_args.get("k", 0.0)) + to_color = c[5] + to_alpha = float(comment_args.get("a", 1.0)) + from_time = float(comment_args.get("t", 0.0)) + action_time = float(comment_args.get("l", 3.0)) + actions = list(comment_args.get("z", [])) + to_out_x, to_out_y, transform_styles = GetTransformStyles( + to_x, + to_y, + to_scale_x, + to_scale_y, + to_rotate_z, + to_rotate_y, + to_color, + to_alpha, + ) + FlushCommentLine( + f, + text, + common_styles + + ["\\pos(%.0f, %.0f)" % (to_out_x, to_out_y)] + + transform_styles, + c[0] + from_time, + c[0] + from_time + action_time, + styleid, + ) + action_styles = transform_styles + for action in actions: + action = dict(action) + from_x, from_y = to_x, to_y + from_out_x, from_out_y = to_out_x, to_out_y + from_scale_x, from_scale_y = to_scale_x, to_scale_y + from_rotate_z, from_rotate_y = to_rotate_z, to_rotate_y + from_color, from_alpha = to_color, to_alpha + transform_styles, action_styles = action_styles, [] + from_time += action_time + action_time = float(action.get("l", 0.0)) + if "x" in action: + to_x = round(GetPosition(int(action["x"]), False)) + if "y" in action: + to_y = round(GetPosition(int(action["y"]), True)) + if "f" in action: + to_scale_x = float(action["f"]) + if "g" in action: + to_scale_y = float(action["g"]) + if "c" in action: + to_color = int(action["c"]) + if "t" in action: + to_alpha = float(action["t"]) + if "d" in action: + to_rotate_z = float(action["d"]) + if "e" in action: + to_rotate_y = float(action["e"]) + to_out_x, to_out_y, action_styles = GetTransformStyles( + to_x, + to_y, + from_scale_x, + from_scale_y, + to_rotate_z, + to_rotate_y, + from_color, + from_alpha, + ) + if (from_out_x, from_out_y) == (to_out_x, to_out_y): + pos_style = "\\pos(%.0f, %.0f)" % (to_out_x, to_out_y) + else: + pos_style = "\\move(%.0f, %.0f, %.0f, %.0f)" % ( + from_out_x, + from_out_y, + to_out_x, + to_out_y, + ) + styles = common_styles + transform_styles + styles.append(pos_style) + if action_styles: + styles.append("\\t(%s)" % ("".join(action_styles))) + FlushCommentLine( + f, + text, + styles, + c[0] + from_time, + c[0] + from_time + action_time, + styleid, + ) + except (IndexError, ValueError) as e: + logging.warning(_("Invalid comment: %r") % c[3]) + + +# Result: (f, dx, dy) +# To convert: NewX = f*x+dx, NewY = f*y+dy +def GetZoomFactor(SourceSize, TargetSize): + try: + if (SourceSize, TargetSize) == GetZoomFactor.Cached_Size: + return GetZoomFactor.Cached_Result + except AttributeError: + pass + GetZoomFactor.Cached_Size = (SourceSize, TargetSize) + try: + SourceAspect = SourceSize[0] / SourceSize[1] + TargetAspect = TargetSize[0] / TargetSize[1] + if TargetAspect < SourceAspect: # narrower + ScaleFactor = TargetSize[0] / SourceSize[0] + GetZoomFactor.Cached_Result = ( + ScaleFactor, + 0, + (TargetSize[1] - TargetSize[0] / SourceAspect) / 2, + ) + elif TargetAspect > SourceAspect: # wider + ScaleFactor = TargetSize[1] / SourceSize[1] + GetZoomFactor.Cached_Result = ( + ScaleFactor, + (TargetSize[0] - TargetSize[1] * SourceAspect) / 2, + 0, + ) + else: + GetZoomFactor.Cached_Result = (TargetSize[0] / SourceSize[0], 0, 0) + return GetZoomFactor.Cached_Result + except ZeroDivisionError: + GetZoomFactor.Cached_Result = (1, 0, 0) + return GetZoomFactor.Cached_Result + + +# Calculation is based on https://github.com/jabbany/CommentCoreLibrary/issues/5#issuecomment-40087282 +# and https://github.com/m13253/danmaku2ass/issues/7#issuecomment-41489422 +# ASS FOV = width*4/3.0 +# But Flash FOV = width/math.tan(100*math.pi/360.0)/2 will be used instead +# Result: (transX, transY, rotX, rotY, rotZ, scaleX, scaleY) +def ConvertFlashRotation(rotY, rotZ, X, Y, width, height): + def WrapAngle(deg): + return 180 - ((180 - deg) % 360) + + rotY = WrapAngle(rotY) + rotZ = WrapAngle(rotZ) + if rotY in (90, -90): + rotY -= 1 + if rotY == 0 or rotZ == 0: + outX = 0 + outY = -rotY # Positive value means clockwise in Flash + outZ = -rotZ + rotY *= math.pi / 180.0 + rotZ *= math.pi / 180.0 + else: + rotY *= math.pi / 180.0 + rotZ *= math.pi / 180.0 + outY = ( + math.atan2(-math.sin(rotY) * math.cos(rotZ), math.cos(rotY)) * 180 / math.pi + ) + outZ = ( + math.atan2(-math.cos(rotY) * math.sin(rotZ), math.cos(rotZ)) * 180 / math.pi + ) + outX = math.asin(math.sin(rotY) * math.sin(rotZ)) * 180 / math.pi + trX = ( + (X * math.cos(rotZ) + Y * math.sin(rotZ)) / math.cos(rotY) + + (1 - math.cos(rotZ) / math.cos(rotY)) * width / 2 + - math.sin(rotZ) / math.cos(rotY) * height / 2 + ) + trY = ( + Y * math.cos(rotZ) + - X * math.sin(rotZ) + + math.sin(rotZ) * width / 2 + + (1 - math.cos(rotZ)) * height / 2 + ) + trZ = (trX - width / 2) * math.sin(rotY) + FOV = width * math.tan(2 * math.pi / 9.0) / 2 + try: + scaleXY = FOV / (FOV + trZ) + except ZeroDivisionError: + logging.error("Rotation makes object behind the camera: trZ == %.0f" % trZ) + scaleXY = 1 + trX = (trX - width / 2) * scaleXY + width / 2 + trY = (trY - height / 2) * scaleXY + height / 2 + if scaleXY < 0: + scaleXY = -scaleXY + outX += 180 + outY += 180 + logging.error( + "Rotation makes object behind the camera: trZ == %.0f < %.0f" % (trZ, FOV) + ) + return ( + trX, + trY, + WrapAngle(outX), + WrapAngle(outY), + WrapAngle(outZ), + scaleXY * 100, + scaleXY * 100, + ) + + +def ProcessComments( + comments, + f, + width, + height, + bottomReserved, + fontface, + fontsize, + alpha, + duration_marquee, + duration_still, + filters_regex, + reduced, + progress_callback, +): + styleid = "Danmaku2ASS_%04x" % random.randint(0, 0xFFFF) + WriteASSHead(f, width, height, fontface, fontsize, alpha, styleid) + rows = [[None] * (height - bottomReserved + 1) for i in range(4)] + for idx, i in enumerate(comments): + if progress_callback and idx % 1000 == 0: + progress_callback(idx, len(comments)) + if isinstance(i[4], int): + skip = False + for filter_regex in filters_regex: + if filter_regex and filter_regex.search(i[3]): + skip = True + break + if skip: + continue + row = 0 + rowmax = height - bottomReserved - i[7] + while row <= rowmax: + freerows = TestFreeRows( + rows, + i, + row, + width, + height, + bottomReserved, + duration_marquee, + duration_still, + ) + if freerows >= i[7]: + MarkCommentRow(rows, i, row) + WriteComment( + f, + i, + row, + width, + height, + bottomReserved, + fontsize, + duration_marquee, + duration_still, + styleid, + ) + break + else: + row += freerows or 1 + else: + if not reduced: + row = FindAlternativeRow(rows, i, height, bottomReserved) + MarkCommentRow(rows, i, row) + WriteComment( + f, + i, + row, + width, + height, + bottomReserved, + fontsize, + duration_marquee, + duration_still, + styleid, + ) + elif i[4] == "bilipos": + WriteCommentBilibiliPositioned(f, i, width, height, styleid) + elif i[4] == "acfunpos": + WriteCommentAcfunPositioned(f, i, width, height, styleid) + else: + logging.warning(_("Invalid comment: %r") % i[3]) + if progress_callback: + progress_callback(len(comments), len(comments)) + + +def TestFreeRows( + rows, c, row, width, height, bottomReserved, duration_marquee, duration_still +): + res = 0 + rowmax = height - bottomReserved + targetRow = None + if c[4] in (1, 2): + while row < rowmax and res < c[7]: + if targetRow != rows[c[4]][row]: + targetRow = rows[c[4]][row] + if targetRow and targetRow[0] + duration_still > c[0]: + break + row += 1 + res += 1 + else: + try: + thresholdTime = c[0] - duration_marquee * (1 - width / (c[8] + width)) + except ZeroDivisionError: + thresholdTime = c[0] - duration_marquee + while row < rowmax and res < c[7]: + if targetRow != rows[c[4]][row]: + targetRow = rows[c[4]][row] + try: + if targetRow and ( + targetRow[0] > thresholdTime + or targetRow[0] + + targetRow[8] * duration_marquee / (targetRow[8] + width) + > c[0] + ): + break + except ZeroDivisionError: + pass + row += 1 + res += 1 + return res + + +def FindAlternativeRow(rows, c, height, bottomReserved): + res = 0 + for row in range(height - bottomReserved - math.ceil(c[7])): + if not rows[c[4]][row]: + return row + elif rows[c[4]][row][0] < rows[c[4]][res][0]: + res = row + return res + + +def MarkCommentRow(rows, c, row): + try: + for i in range(row, row + math.ceil(c[7])): + rows[c[4]][i] = c + except IndexError: + pass + + +def WriteASSHead(f, width, height, fontface, fontsize, alpha, styleid): + f.write( + """[Script Info] +; Script generated by Danmaku2ASS +; https://github.com/m13253/danmaku2ass +Script Updated By: Danmaku2ASS (https://github.com/m13253/danmaku2ass) +ScriptType: v4.00+ +PlayResX: %(width)d +PlayResY: %(height)d +Aspect Ratio: %(width)d:%(height)d +Collisions: Normal +WrapStyle: 2 +ScaledBorderAndShadow: yes +YCbCr Matrix: TV.601 +[V4+ Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding +Style: %(styleid)s, %(fontface)s, %(fontsize).0f, &H%(alpha)02XFFFFFF, &H%(alpha)02XFFFFFF, &H%(alpha)02X000000, &H%(alpha)02X000000, 0, 0, 0, 0, 100, 100, 0.00, 0.00, 1, %(outline).0f, 0, 7, 0, 0, 0, 0 +[Events] +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text +""" + % { + "width": width, + "height": height, + "fontface": fontface, + "fontsize": fontsize, + "alpha": 255 - round(alpha * 255), + "outline": max(fontsize / 25.0, 1), + "styleid": styleid, + } + ) + + +def WriteComment( + f, + c, + row, + width, + height, + bottomReserved, + fontsize, + duration_marquee, + duration_still, + styleid, +): + text = ASSEscape(c[3]) + styles = [] + if c[4] == 1: + styles.append( + "\\an8\\pos(%(halfwidth)d, %(row)d)" % {"halfwidth": width / 2, "row": row} + ) + duration = duration_still + elif c[4] == 2: + styles.append( + "\\an2\\pos(%(halfwidth)d, %(row)d)" + % {"halfwidth": width / 2, "row": ConvertType2(row, height, bottomReserved)} + ) + duration = duration_still + elif c[4] == 3: + styles.append( + "\\move(%(neglen)d, %(row)d, %(width)d, %(row)d)" + % {"width": width, "row": row, "neglen": -math.ceil(c[8])} + ) + duration = duration_marquee + else: + styles.append( + "\\move(%(width)d, %(row)d, %(neglen)d, %(row)d)" + % {"width": width, "row": row, "neglen": -math.ceil(c[8])} + ) + duration = duration_marquee + if not (-1 < c[6] - fontsize < 1): + styles.append("\\fs%.0f" % c[6]) + if c[5] != 0xFFFFFF: + styles.append("\\c&H%s&" % ConvertColor(c[5])) + if c[5] == 0x000000: + styles.append("\\3c&HFFFFFF&") + f.write( + "Dialogue: 2,%(start)s,%(end)s,%(styleid)s,,0000,0000,0000,,{%(styles)s}%(text)s\n" + % { + "start": ConvertTimestamp(c[0]), + "end": ConvertTimestamp(c[0] + duration), + "styles": "".join(styles), + "text": text, + "styleid": styleid, + } + ) + + +def ASSEscape(s): + def ReplaceLeadingSpace(s): + sstrip = s.strip(" ") + slen = len(s) + if slen == len(sstrip): + return s + else: + llen = slen - len(s.lstrip(" ")) + rlen = slen - len(s.rstrip(" ")) + return "".join(("\u2007" * llen, sstrip, "\u2007" * rlen)) + + return "\\N".join( + ( + ReplaceLeadingSpace(i) or " " + for i in str(s) + .replace("\\", "\\\\") + .replace("{", "\\{") + .replace("}", "\\}") + .split("\n") + ) + ) + + +def CalculateLength(s): + return max(map(len, s.split("\n"))) # May not be accurate + + +def ConvertTimestamp(timestamp): + timestamp = round(timestamp * 100.0) + hour, minute = divmod(timestamp, 360000) + minute, second = divmod(minute, 6000) + second, centsecond = divmod(second, 100) + return "%d:%02d:%02d.%02d" % (int(hour), int(minute), int(second), int(centsecond)) + + +def ConvertColor(RGB, width=1280, height=576): + if RGB == 0x000000: + return "000000" + elif RGB == 0xFFFFFF: + return "FFFFFF" + R = (RGB >> 16) & 0xFF + G = (RGB >> 8) & 0xFF + B = RGB & 0xFF + if width < 1280 and height < 576: + return "%02X%02X%02X" % (B, G, R) + else: # VobSub always uses BT.601 colorspace, convert to BT.709 + ClipByte = lambda x: 255 if x > 255 else 0 if x < 0 else round(x) + return "%02X%02X%02X" % ( + ClipByte( + R * 0.00956384088080656 + + G * 0.03217254540203729 + + B * 0.95826361371715607 + ), + ClipByte( + R * -0.10493933142075390 + + G * 1.17231478191855154 + + B * -0.06737545049779757 + ), + ClipByte( + R * 0.91348912373987645 + + G * 0.07858536372532510 + + B * 0.00792551253479842 + ), + ) + + +def ConvertType2(row, height, bottomReserved): + return height - bottomReserved - row + + +def ConvertToFile(filename_or_file, *args, **kwargs): + if isinstance(filename_or_file, bytes): + filename_or_file = str(bytes(filename_or_file).decode("utf-8", "replace")) + if isinstance(filename_or_file, str): + return open(filename_or_file, *args, **kwargs) + else: + return filename_or_file + + +def FilterBadChars(f): + s = f.read() + s = re.sub("[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f]", "\ufffd", s) + return io.StringIO(s) + + +class safe_list(list): + def get(self, index, default=None): + try: + return self[index] + except IndexError: + return default + + +def export(func): + global __all__ + try: + __all__.append(func.__name__) + except NameError: + __all__ = [func.__name__] + return func + + +@export +def Danmaku2ASS( + input_files, + input_format, + output_file, + stage_width, + stage_height, + reserve_blank=0, + font_face=_("(FONT) sans-serif")[7:], + font_size=25.0, + text_opacity=1.0, + duration_marquee=5.0, + duration_still=5.0, + comment_filter=None, + comment_filters_file=None, + is_reduce_comments=False, + progress_callback=None, +): + comment_filters = [comment_filter] + if comment_filters_file: + with open(comment_filters_file, "r") as f: + d = f.readlines() + comment_filters.extend([i.strip() for i in d]) + filters_regex = [] + for comment_filter in comment_filters: + try: + if comment_filter: + filters_regex.append(re.compile(comment_filter)) + except: + raise ValueError(_("Invalid regular expression: %s") % comment_filter) + fo = None + comments = ReadComments(input_files, input_format, font_size) + try: + if output_file: + fo = ConvertToFile( + output_file, "w", encoding="utf-8-sig", errors="replace", newline="\r\n" + ) + else: + fo = sys.stdout + ProcessComments( + comments, + fo, + stage_width, + stage_height, + reserve_blank, + font_face, + font_size, + text_opacity, + duration_marquee, + duration_still, + filters_regex, + is_reduce_comments, + progress_callback, + ) + finally: + if output_file and fo != output_file: + fo.close() + + +@export +def ReadComments(input_files, input_format, font_size=25.0, progress_callback=None): + if isinstance(input_files, bytes): + input_files = str(bytes(input_files).decode("utf-8", "replace")) + if isinstance(input_files, str): + input_files = [input_files] + else: + input_files = list(input_files) + comments = [] + for idx, i in enumerate(input_files): + if progress_callback: + progress_callback(idx, len(input_files)) + with ConvertToFile(i, "r", encoding="utf-8", errors="replace") as f: + s = f.read() + str_io = io.StringIO(s) + if input_format == "autodetect": + CommentProcessor = GetCommentProcessor(str_io) + if not CommentProcessor: + raise ValueError(_("Failed to detect comment file format: %s") % i) + else: + CommentProcessor = CommentFormatMap.get(input_format) + if not CommentProcessor: + raise ValueError( + _("Unknown comment file format: %s") % input_format + ) + comments.extend(CommentProcessor(FilterBadChars(str_io), font_size)) + if progress_callback: + progress_callback(len(input_files), len(input_files)) + comments.sort() + return comments + + +@export +def GetCommentProcessor(input_file): + return CommentFormatMap.get(ProbeCommentFormat(input_file)) + + +def main(): + logging.basicConfig(format="%(levelname)s: %(message)s") + if len(sys.argv) == 1: + sys.argv.append("--help") + parser = argparse.ArgumentParser() + parser.add_argument( + "-f", + "--format", + metavar=_("FORMAT"), + help=_("Format of input file (autodetect|%s) [default: autodetect]") + % "|".join(i for i in CommentFormatMap), + default="autodetect", + ) + parser.add_argument("-o", "--output", metavar=_("OUTPUT"), help=_("Output file")) + parser.add_argument( + "-s", + "--size", + metavar=_("WIDTHxHEIGHT"), + required=True, + help=_("Stage size in pixels"), + ) + parser.add_argument( + "-fn", + "--font", + metavar=_("FONT"), + help=_("Specify font face [default: %s]") % _("(FONT) sans-serif")[7:], + default=_("(FONT) sans-serif")[7:], + ) + parser.add_argument( + "-fs", + "--fontsize", + metavar=_("SIZE"), + help=(_("Default font size [default: %s]") % 25), + type=float, + default=25.0, + ) + parser.add_argument( + "-a", + "--alpha", + metavar=_("ALPHA"), + help=_("Text opacity"), + type=float, + default=1.0, + ) + parser.add_argument( + "-dm", + "--duration-marquee", + metavar=_("SECONDS"), + help=_("Duration of scrolling comment display [default: %s]") % 5, + type=float, + default=5.0, + ) + parser.add_argument( + "-ds", + "--duration-still", + metavar=_("SECONDS"), + help=_("Duration of still comment display [default: %s]") % 5, + type=float, + default=5.0, + ) + parser.add_argument( + "-fl", "--filter", help=_("Regular expression to filter comments") + ) + parser.add_argument( + "-flf", + "--filter-file", + help=_("Regular expressions from file (one line one regex) to filter comments"), + ) + parser.add_argument( + "-p", + "--protect", + metavar=_("HEIGHT"), + help=_("Reserve blank on the bottom of the stage"), + type=int, + default=0, + ) + parser.add_argument( + "-r", + "--reduce", + action="store_true", + help=_("Reduce the amount of comments if stage is full"), + ) + parser.add_argument( + "file", metavar=_("FILE"), nargs="+", help=_("Comment file to be processed") + ) + args = parser.parse_args() + try: + width, height = str(args.size).split("x", 1) + width = int(width) + height = int(height) + except ValueError: + raise ValueError(_("Invalid stage size: %r") % args.size) + Danmaku2ASS( + args.file, + args.format, + args.output, + width, + height, + args.protect, + args.font, + args.fontsize, + args.alpha, + args.duration_marquee, + args.duration_still, + args.filter, + args.filter_file, + args.reduce, + ) + + +if __name__ == "__main__": + main() diff --git a/bilibili_api/utils/initial_state.py b/bilibili_api/utils/initial_state.py new file mode 100644 index 0000000000000000000000000000000000000000..d645e6b93971a17909a5a92d97a89ef71f6fbe4b --- /dev/null +++ b/bilibili_api/utils/initial_state.py @@ -0,0 +1,109 @@ +""" +bilibili_api.utils.initial_state + +用于获取页码的初始化信息 +""" +import re +import json +import httpx +from enum import Enum +from typing import Union + +from ..exceptions import * +from .short import get_real_url +from .credential import Credential +from .network import get_session + + +class InitialDataType(Enum): + """ + 识别返回类型 + """ + + INITIAL_STATE = "window.__INITIAL_STATE__" + NEXT_DATA = "__NEXT_DATA__" + + +async def get_initial_state( + url: str, credential: Credential = Credential() +) -> Union[dict, InitialDataType]: + """ + 异步获取初始化信息 + + Args: + url (str): 链接 + + credential (Credential, optional): 用户凭证. Defaults to Credential(). + """ + try: + session = get_session() + resp = await session.get( + url, + cookies=credential.get_cookies(), + headers={"User-Agent": "Mozilla/5.0"}, + follow_redirects=True, + ) + except Exception as e: + raise e + else: + content = resp.text + pattern = re.compile(r"window.__INITIAL_STATE__=(\{.*?\});") + match = re.search(pattern, content) + if match is None: + pattern = re.compile( + pattern=r'' + ) + match = re.search(pattern, content) + content_type = InitialDataType.NEXT_DATA + if match is None: + raise ApiException("未找到相关信息") + else: + content_type = InitialDataType.INITIAL_STATE + try: + content = json.loads(match.group(1)) + except json.JSONDecodeError: + raise ApiException("信息解析错误") + + return content, content_type + + +def get_initial_state_sync( + url: str, credential: Credential = Credential() +) -> Union[dict, InitialDataType]: + """ + 同步获取初始化信息 + + Args: + url (str): 链接 + + credential (Credential, optional): 用户凭证. Defaults to Credential(). + """ + try: + resp = httpx.get( + url, + cookies=credential.get_cookies(), + headers={"User-Agent": "Mozilla/5.0"}, + follow_redirects=True, + ) + except Exception as e: + raise e + else: + content = resp.text + pattern = re.compile(r"window.__INITIAL_STATE__=(\{.*?\});") + match = re.search(pattern, content) + if match is None: + pattern = re.compile( + pattern=r'' + ) + match = re.search(pattern, content) + content_type = InitialDataType.NEXT_DATA + if match is None: + raise ApiException("未找到相关信息") + else: + content_type = InitialDataType.INITIAL_STATE + try: + content = json.loads(match.group(1)) + except json.JSONDecodeError: + raise ApiException("信息解析错误") + + return content, content_type diff --git a/bilibili_api/utils/json2srt.py b/bilibili_api/utils/json2srt.py new file mode 100644 index 0000000000000000000000000000000000000000..3231d70fbf2f2c27663f10fdd116006509adb528 --- /dev/null +++ b/bilibili_api/utils/json2srt.py @@ -0,0 +1,59 @@ +# https://blog.csdn.net/mondaiji/article/details/104294430 +# author: 皓空Fly +# 此文件采用 CC 4.0 BY-SA 协议开源。 + +import os +import json +import math + + +def json2srt(doc: str, out: str): + """ + Args: + doc: 文件 + + Returns: + None + """ + file = "" # 这个变量用来保存数据 + i = 1 + with open(doc, encoding="utf-8") as f: + datas = json.load(f) # 加载文件数据 + f.close() + for data in datas["body"]: + start = data["from"] # 获取开始时间 + stop = data["to"] # 获取结束时F间 + content = data["content"] # 获取字幕内容 + file += "{}\n".format(i) # 加入序号 + hour = math.floor(start) // 3600 + minute = (math.floor(start) - hour * 3600) // 60 + sec = math.floor(start) - hour * 3600 - minute * 60 + minisec = int(math.modf(start)[0] * 100) # 处理开始时间 + file += ( + str(hour).zfill(2) + + ":" + + str(minute).zfill(2) + + ":" + + str(sec).zfill(2) + + "," + + str(minisec).zfill(2) + ) # 将数字填充0并按照格式写入 + file += " --> " + hour = math.floor(stop) // 3600 + minute = (math.floor(stop) - hour * 3600) // 60 + sec = math.floor(stop) - hour * 3600 - minute * 60 + minisec = abs(int(math.modf(stop)[0] * 100 - 1)) # 此处减1是为了防止两个字幕同时出现 + file += ( + str(hour).zfill(2) + + ":" + + str(minute).zfill(2) + + ":" + + str(sec).zfill(2) + + "," + + str(minisec).zfill(2) + ) + file += "\n" + content + "\n\n" # 加入字幕文字 + i += 1 + with open(out, "w+", encoding="utf-8") as f: + f.write(file) # 将数据写入文件 + f.close() diff --git a/bilibili_api/utils/network.py b/bilibili_api/utils/network.py new file mode 100644 index 0000000000000000000000000000000000000000..3d061e4973886d7949092a3a6f40dc646a8c0ef0 --- /dev/null +++ b/bilibili_api/utils/network.py @@ -0,0 +1,922 @@ +""" +bilibili_api.utils.network + +与网络请求相关的模块。能对会话进行管理(复用 TCP 连接)。 +""" + +import re +import json +import time +import atexit +import asyncio +import hashlib +import hmac +from functools import reduce +from urllib.parse import urlencode +from dataclasses import field, dataclass +from typing import Any, Dict, Union, Coroutine, Type +from inspect import iscoroutinefunction as isAsync +from urllib.parse import quote + +import httpx +import aiohttp + +from .sync import sync +from .. import settings +from .utils import get_api +from .credential import Credential +from ..exceptions import ApiException, ResponseCodeException, NetworkException + +__httpx_session_pool: Dict[asyncio.AbstractEventLoop, httpx.AsyncClient] = {} +__aiohttp_session_pool: Dict[asyncio.AbstractEventLoop, aiohttp.ClientSession] = {} +__httpx_sync_session: httpx.Client = None +last_proxy = "" +wbi_mixin_key = "" +buvid3 = "" + +# 获取密钥时的申必数组 +OE = [ + 46, + 47, + 18, + 2, + 53, + 8, + 23, + 32, + 15, + 50, + 10, + 31, + 58, + 3, + 45, + 35, + 27, + 43, + 5, + 49, + 33, + 9, + 42, + 19, + 29, + 28, + 14, + 39, + 12, + 38, + 41, + 13, + 37, + 48, + 7, + 16, + 24, + 55, + 40, + 61, + 26, + 17, + 0, + 1, + 60, + 51, + 30, + 4, + 22, + 25, + 54, + 21, + 56, + 59, + 6, + 63, + 57, + 62, + 11, + 36, + 20, + 34, + 44, + 52, +] +# 使用 Referer 和 UA 请求头以绕过反爬虫机制 +HEADERS = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.54", + "Referer": "https://www.bilibili.com", +} +API = get_api("credential") + + +def retry_sync(times: int = 3): + """ + 重试装饰器 + + Args: + times (int): 最大重试次数 默认 3 次 负数则一直重试直到成功 + + Returns: + Any: 原函数调用结果 + """ + + def wrapper(func): + def inner(*args, **kwargs): + # 这里必须新建一个变量用来计数!!不能直接对 times 操作!!! + nonlocal times + loop = times + while loop != 0: + if loop != times and settings.request_log: + settings.logger.info("第 %d 次重试", times - loop) + loop -= 1 + try: + return func(*args, **kwargs) + except json.decoder.JSONDecodeError: + # json 解析错误 说明数据获取有误 再给次机会 + continue + except ResponseCodeException as e: + # -403 时尝试重新获取 wbi_mixin_key 可能过期了 + if e.code == -403: + global wbi_mixin_key + wbi_mixin_key = "" + continue + # 不是 -403 错误直接报错 + raise + raise ApiException("重试达到最大次数") + + return inner + + return wrapper + + +def retry(times: int = 3): + """ + 重试装饰器 + + Args: + times (int): 最大重试次数 默认 3 次 负数则一直重试直到成功 + + Returns: + Any: 原函数调用结果 + """ + + def wrapper(func: Coroutine): + async def inner(*args, **kwargs): + # 这里必须新建一个变量用来计数!!不能直接对 times 操作!!! + nonlocal times + loop = times + while loop != 0: + if loop != times and settings.request_log: + settings.logger.info("第 %d 次重试", times - loop) + loop -= 1 + try: + return await func(*args, **kwargs) + except json.decoder.JSONDecodeError: + # json 解析错误 说明数据获取有误 再给次机会 + continue + except ResponseCodeException as e: + # -403 时尝试重新获取 wbi_mixin_key 可能过期了 + if e.code == -403: + global wbi_mixin_key + wbi_mixin_key = "" + continue + # 不是 -403 错误直接报错 + raise + raise ApiException("重试达到最大次数") + + return inner + + if isAsync(times): + # 防呆不防傻 防止有人 @retry() 不打括号 + func = times + times = 3 + return wrapper(func) + + return wrapper + + +@dataclass +class Api: + """ + 用于请求的 Api 类 + + Args: + url (str): 请求地址 + + method (str): 请求方法 + + comment (str, optional): 注释. Defaults to "". + + wbi (bool, optional): 是否使用 wbi 鉴权. Defaults to False. + + verify (bool, optional): 是否验证凭据. Defaults to False. + + no_csrf (bool, optional): 是否不使用 csrf. Defaults to False. + + json_body (bool, optional): 是否使用 json 作为载荷. Defaults to False. + + ignore_code (bool, optional): 是否忽略返回值 code 的检验. Defaults to False. + + data (dict, optional): 请求载荷. Defaults to {}. + + params (dict, optional): 请求参数. Defaults to {}. + + credential (Credential, optional): 凭据. Defaults to Credential(). + """ + + url: str + method: str + comment: str = "" + wbi: bool = False + verify: bool = False + no_csrf: bool = False + json_body: bool = False + ignore_code: bool = False + data: dict = field(default_factory=dict) + params: dict = field(default_factory=dict) + files: dict = field(default_factory=dict) + headers: dict = field(default_factory=dict) + credential: Credential = field(default_factory=Credential) + + def __post_init__(self) -> None: + self.method = self.method.upper() + self.original_data = self.data.copy() + self.original_params = self.params.copy() + self.data = {k: "" for k in self.data.keys()} + self.params = {k: "" for k in self.params.keys()} + self.files = {k: "" for k in self.files.keys()} + self.headers = {k: "" for k in self.headers.keys()} + if self.credential is None: + self.credential = Credential() + self.__result = None + + def __setattr__(self, __name: str, __value: Any) -> None: + """ + 每次更新参数都要把 __result 清除 + """ + if self.initialized and __name != "_Api__result": + self.__result = None + return super().__setattr__(__name, __value) + + @property + def initialized(self): + return "_Api__result" in self.__dict__ + + @property + async def result(self) -> Union[int, str, dict]: + """ + 异步获取请求结果 + + `self.__result` 用来暂存数据 参数不变时获取结果不变 + """ + if self.__result is None: + self.__result = await self.request() + return self.__result + + @property + def result_sync(self) -> Union[int, str, dict]: + """ + 通过 `sync` 同步获取请求结果 + + 一般用于非协程内同步获取数据 + + `self.__result` 用来暂存数据 参数不变时获取结果不变 + """ + if self.__result is None: + self.__result = self.request_sync() + return self.__result + + def update_data(self, **kwargs) -> "Api": + """ + 毫无亮点的更新 data + """ + self.data = kwargs + self.__result = None + return self + + def update_params(self, **kwargs) -> "Api": + """ + 毫无亮点的更新 params + """ + self.params = kwargs + self.__result = None + return self + + def update_files(self, **kwargs) -> "Api": + """ + 毫无亮点的更新 files + """ + self.files = kwargs + self.__result = None + return self + + def update_headers(self, **kwargs) -> "Api": + """ + 毫无亮点的更新 headers + """ + self.headers = kwargs + self.__result = None + return self + + def update(self, **kwargs) -> "Api": + """ + 毫无亮点的自动选择更新,不包括 files, headers + """ + if self.method == "GET": + return self.update_params(**kwargs) + else: + return self.update_data(**kwargs) + + def _prepare_params_data(self) -> None: + new_params, new_data = {}, {} + for key, value in self.params.items(): + if isinstance(value, bool): + new_params[key] = int(value) + elif value != None: + new_params[key] = value + for key, value in self.data.items(): + if isinstance(value, bool): + new_params[key] = int(value) + elif value != None: + new_data[key] = value + self.params, self.data = new_params, new_data + + def _prepare_request_sync(self, **kwargs) -> dict: + """ + 准备请求的配置参数 + + Args: + **kwargs: 其他额外的请求配置参数 + + Returns: + dict: 包含请求的配置参数 + """ + # 如果接口需要 Credential 且未传入则报错 (默认值为 Credential()) + if self.verify: + self.credential.raise_for_no_sessdata() + + # 请求为非 GET 且 no_csrf 不为 True 时要求 bili_jct + if self.method != "GET" and not self.no_csrf: + self.credential.raise_for_no_bili_jct() + + if settings.request_log: + settings.logger.info(self) + + # jsonp + if self.params.get("jsonp") == "jsonp": + self.params["callback"] = "callback" + + if self.wbi: + global wbi_mixin_key + if wbi_mixin_key == "": + wbi_mixin_key = get_mixin_key_sync() + enc_wbi(self.params, wbi_mixin_key) + + # 自动添加 csrf + if ( + not self.no_csrf + and self.verify + and self.method in ["POST", "DELETE", "PATCH"] + ): + self.data["csrf"] = self.credential.bili_jct + self.data["csrf_token"] = self.credential.bili_jct + + cookies = self.credential.get_cookies() + + if self.credential.buvid3 is None: + global buvid3 + if buvid3 == "" and self.url != API["info"]["spi"]["url"]: + buvid3 = get_spi_buvid_sync()["b_3"] + cookies["buvid3"] = buvid3 + else: + cookies["buvid3"] = self.credential.buvid3 + # cookies["Domain"] = ".bilibili.com" + + config = { + "url": self.url, + "method": self.method, + "data": self.data, + "params": self.params, + "files": self.files, + "cookies": cookies, + "headers": HEADERS.copy() if len(self.headers) == 0 else self.headers, + "timeout": settings.timeout, + } + config.update(kwargs) + + if self.json_body: + config["headers"]["Content-Type"] = "application/json" + config["data"] = json.dumps(config["data"]) + + return config + + async def _prepare_request(self, **kwargs) -> dict: + """ + 准备请求的配置参数 + + Args: + **kwargs: 其他额外的请求配置参数 + + Returns: + dict: 包含请求的配置参数 + """ + # 如果接口需要 Credential 且未传入则报错 (默认值为 Credential()) + if self.verify: + self.credential.raise_for_no_sessdata() + + # 请求为非 GET 且 no_csrf 不为 True 时要求 bili_jct + if self.method != "GET" and not self.no_csrf: + self.credential.raise_for_no_bili_jct() + + if settings.request_log: + settings.logger.info(self) + + # jsonp + if self.params.get("jsonp") == "jsonp": + self.params["callback"] = "callback" + + if self.wbi: + global wbi_mixin_key + if wbi_mixin_key == "": + wbi_mixin_key = await get_mixin_key() + enc_wbi(self.params, wbi_mixin_key) + + # 自动添加 csrf + if ( + not self.no_csrf + and self.verify + and self.method in ["POST", "DELETE", "PATCH"] + ): + self.data["csrf"] = self.credential.bili_jct + self.data["csrf_token"] = self.credential.bili_jct + + cookies = self.credential.get_cookies() + + if self.credential.buvid3 is None: + global buvid3 + if buvid3 == "" and self.url != API["info"]["spi"]["url"]: + buvid3 = (await get_spi_buvid())["b_3"] + cookies["buvid3"] = buvid3 + else: + cookies["buvid3"] = self.credential.buvid3 + # cookies["Domain"] = ".bilibili.com" + + config = { + "url": self.url, + "method": self.method, + "data": self.data, + "params": self.params, + "files": self.files, + "cookies": cookies, + "headers": HEADERS.copy() if len(self.headers) == 0 else self.headers, + "timeout": settings.timeout + if settings.http_client == settings.HTTPClient.HTTPX + else aiohttp.ClientTimeout(total=settings.timeout), + } + config.update(kwargs) + + if self.json_body: + config["headers"]["Content-Type"] = "application/json" + config["data"] = json.dumps(config["data"]) + + if settings.http_client == settings.HTTPClient.AIOHTTP and not self.json_body: + config["data"].update(config["files"]) + if config["data"] != {}: + data = aiohttp.FormData() + for key, val in config["data"].items(): + data.add_field(key, val) + config["data"] = data + config.pop("files") + if settings.proxy != "": + config["proxy"] = settings.proxy + elif settings.http_client == settings.HTTPClient.AIOHTTP: + # 舍去 files + config.pop("files") + + return config + + def _get_resp_text_sync(self, resp: httpx.Response): + return resp.text + + async def _get_resp_text(self, resp: Union[httpx.Response, aiohttp.ClientResponse]): + if isinstance(resp, httpx.Response): + return resp.text + else: + return await resp.text() + + @retry_sync(times=settings.wbi_retry_times) + def request_sync(self, raw: bool = False, **kwargs) -> Union[int, str, dict]: + """ + 向接口发送请求。 + + Returns: + 接口未返回数据时,返回 None,否则返回该接口提供的 data 或 result 字段的数据。 + """ + self._prepare_params_data() + config = self._prepare_request_sync(**kwargs) + session = get_httpx_sync_session() + resp = session.request(**config) + try: + resp.raise_for_status() + except httpx.HTTPStatusError as e: + raise NetworkException(resp.status_code, str(resp.status_code)) + real_data = self._process_response( + resp, self._get_resp_text_sync(resp), raw=raw + ) + return real_data + + @retry(times=settings.wbi_retry_times) + async def request(self, raw: bool = False, **kwargs) -> Union[int, str, dict]: + """ + 向接口发送请求。 + + Returns: + 接口未返回数据时,返回 None,否则返回该接口提供的 data 或 result 字段的数据。 + """ + self._prepare_params_data() + config = await self._prepare_request(**kwargs) + session: Union[httpx.AsyncClient, aiohttp.ClientSession] + # 判断http_client的类型 + if settings.http_client == settings.HTTPClient.HTTPX: + session = get_session() + resp = await session.request(**config) + try: + resp.raise_for_status() + except httpx.HTTPStatusError as e: + raise NetworkException(resp.status_code, str(resp.status_code)) + real_data = self._process_response( + resp, await self._get_resp_text(resp), raw=raw + ) + return real_data + elif settings.http_client == settings.HTTPClient.AIOHTTP: + session = get_aiohttp_session() + async with session.request(**config) as resp: + try: + resp.raise_for_status() + except aiohttp.ClientResponseError as e: + raise NetworkException(e.status, e.message) + real_data = self._process_response( + resp, await self._get_resp_text(resp), raw=raw + ) + return real_data + + def _process_response( + self, + resp: Union[httpx.Response, aiohttp.ClientResponse], + resp_text: str, + raw: bool = False, + ) -> Union[int, str, dict]: + """ + 处理接口的响应数据 + """ + # 检查响应头 Content-Length + content_length = resp.headers.get("content-length") + if content_length and int(content_length) == 0: + return None + + if "callback" in self.params: + # JSONP 请求 + resp_data: dict = json.loads( + re.match("^.*?({.*}).*$", resp_text, re.S).group(1) + ) + else: + # JSON + resp_data: dict = json.loads(resp_text) + + if raw: + return resp_data + + OK = resp_data.get("OK") + + # 检查 code + if not self.ignore_code: + if OK is None: + code = resp_data.get("code") + if code is None: + raise ResponseCodeException(-1, "API 返回数据未含 code 字段", resp_data) + if code != 0: + msg = resp_data.get("msg") + if msg is None: + msg = resp_data.get("message") + if msg is None: + msg = "接口未返回错误信息" + raise ResponseCodeException(code, msg, resp_data) + elif OK != 1: + raise ResponseCodeException(-1, "API 返回数据 OK 不为 1", resp_data) + elif settings.request_log: + settings.logger.info(resp_data) + + real_data = resp_data.get("data") if OK is None else resp_data + if real_data is None: + real_data = resp_data.get("result") + return real_data + + @classmethod + def from_file(cls, path: str, credential: Union[Credential, None] = None): + """ + 以 json 文件生成对象 + + Args: + path (str): 例如 user.info.info + + credential (Credential, Optional): 凭据类. Defaults to None. + + Returns: + api (Api): 从文件中读取的 api 信息 + """ + path_list = path.split(".") + api = get_api(path_list.pop(0)) + for key in path_list: + api = api.get(key) + return cls(credential=credential, **api) + + +async def check_valid(credential: Credential) -> bool: + """ + 检查 cookies 是否有效 + + Args: + credential (Credential): 凭据类 + + Returns: + bool: cookies 是否有效 + """ + data = await get_nav(credential) + return data["isLogin"] + + +async def get_spi_buvid() -> dict: + """ + 获取 buvid3 / buvid4 + + Returns: + dict: 账号相关信息 + """ + return await Api(**API["info"]["spi"]).result + + +def get_spi_buvid_sync() -> dict: + """ + 同步获取 buvid3 / buvid4 + + Returns: + dict: 账号相关信息 + """ + return Api(**API["info"]["spi"]).result_sync + + +def get_nav_sync(credential: Union[Credential, None] = None): + """ + 获取导航 + + Args: + credential (Credential, Optional): 凭据类. Defaults to None + + Returns: + dict: 账号相关信息 + """ + return Api(credential=credential, **API["info"]["valid"]).result_sync + + +async def get_nav(credential: Union[Credential, None] = None): + """ + 获取导航 + + Args: + credential (Credential, Optional): 凭据类. Defaults to None + + Returns: + dict: 账号相关信息 + """ + return await Api(credential=credential, **API["info"]["valid"]).result + + +def get_mixin_key_sync() -> str: + """ + 获取混合密钥 + + Returns: + str: 新获取的密钥 + """ + data = get_nav_sync() + wbi_img: Dict[str, str] = data["wbi_img"] + + # 为什么要把里的 lambda 表达式换成函数 这不是一样的吗 + # split = lambda key: wbi_img.get(key).split("/")[-1].split(".")[0] + def split(key): + return wbi_img.get(key).split("/")[-1].split(".")[0] + + ae = split("img_url") + split("sub_url") + le = reduce(lambda s, i: s + (ae[i] if i < len(ae) else ""), OE, "") + return le[:32] + + +async def get_mixin_key() -> str: + """ + 获取混合密钥 + + Returns: + str: 新获取的密钥 + """ + data = await get_nav() + wbi_img: Dict[str, str] = data["wbi_img"] + + # 为什么要把里的 lambda 表达式换成函数 这不是一样的吗 + # split = lambda key: wbi_img.get(key).split("/")[-1].split(".")[0] + def split(key): + return wbi_img.get(key).split("/")[-1].split(".")[0] + + ae = split("img_url") + split("sub_url") + le = reduce(lambda s, i: s + (ae[i] if i < len(ae) else ""), OE, "") + return le[:32] + + +def enc_wbi(params: dict, mixin_key: str): + """ + 更新请求参数 + + Args: + params (dict): 原请求参数 + + mixin_key (str): 混合密钥 + """ + params.pop("w_rid", None) # 重试时先把原有 w_rid 去除 + params["wts"] = int(time.time()) + # web_location 因为没被列入参数可能炸一些接口 比如 video.get_ai_conclusion + params["web_location"] = 1550101 + Ae = urlencode(sorted(params.items())) + params["w_rid"] = hashlib.md5((Ae + mixin_key).encode(encoding="utf-8")).hexdigest() + + +def hmac_sha256(key: str, message: str) -> str: + """ + 使用HMAC-SHA256算法对给定的消息进行加密 + :param key: 密钥 + :param message: 要加密的消息 + :return: 加密后的哈希值 + """ + # 将密钥和消息转换为字节串 + key = key.encode("utf-8") + message = message.encode("utf-8") + + # 创建HMAC对象,使用SHA256哈希算法 + hmac_obj = hmac.new(key, message, hashlib.sha256) + + # 计算哈希值 + hash_value = hmac_obj.digest() + + # 将哈希值转换为十六进制字符串 + hash_hex = hash_value.hex() + + return hash_hex + + +async def get_bili_ticket() -> str: + """ + 获取 bili_ticket,但目前没用到,暂时不启用 + + https://github.com/SocialSisterYi/bilibili-API-collect/issues/903 + + Returns: + str: bili_ticket + """ + o = hmac_sha256("XgwSnGZ1p", f"ts{int(time.time())}") + url = "https://api.bilibili.com/bapis/bilibili.api.ticket.v1.Ticket/GenWebTicket" + params = { + "key_id": "ec02", + "hexsign": o, + "context[ts]": f"{int(time.time())}", + "csrf": "", + } + return ( + await Api(method="POST", url=url, no_csrf=True).update_params(**params).result + )["ticket"] + + +def get_httpx_sync_session() -> httpx.Client: + """ + 获取当前模块的 httpx.Client 对象,用于自定义请求 + + Returns: + httpx.Client + """ + global __httpx_sync_session + global last_proxy + + if __httpx_sync_session is None or last_proxy != settings.proxy: + if settings.proxy != "": + last_proxy = settings.proxy + proxies = {"all://": settings.proxy} + session = httpx.Client(proxies=proxies) # type: ignore + else: + last_proxy = "" + session = httpx.Client() + __httpx_sync_session = session + + return __httpx_sync_session + + +def set_httpx_sync_session(session: httpx.Client) -> None: + """ + 用户手动设置 Session + + Args: + session (httpx.Client): httpx.Client 实例。 + """ + global __httpx_sync_session + __httpx_sync_session = session + + +def get_session() -> httpx.AsyncClient: + """ + 获取当前模块的 httpx.AsyncClient 对象,用于自定义请求 + + Returns: + httpx.AsyncClient + """ + global __httpx_session_pool, last_proxy + loop = asyncio.get_event_loop() + session = __httpx_session_pool.get(loop, None) + if session is None or last_proxy != settings.proxy: + if settings.proxy != "": + last_proxy = settings.proxy + proxies = {"all://": settings.proxy} + session = httpx.AsyncClient(proxies=proxies) # type: ignore + else: + last_proxy = "" + session = httpx.AsyncClient() + __httpx_session_pool[loop] = session + + return session + + +def set_session(session: httpx.AsyncClient) -> None: + """ + 用户手动设置 Session + + Args: + session (httpx.AsyncClient): httpx.AsyncClient 实例。 + """ + loop = asyncio.get_event_loop() + __httpx_session_pool[loop] = session + + +def get_aiohttp_session() -> aiohttp.ClientSession: + """ + 获取当前模块的 aiohttp.ClientSession 对象,用于自定义请求 + + Returns: + aiohttp.ClientSession + """ + loop = asyncio.get_event_loop() + session = __aiohttp_session_pool.get(loop, None) + if session is None: + session = aiohttp.ClientSession( + loop=loop, connector=aiohttp.TCPConnector(), trust_env=True + ) + __aiohttp_session_pool[loop] = session + + return session + + +def set_aiohttp_session(session: aiohttp.ClientSession) -> None: + """ + 用户手动设置 Session + + Args: + session (aiohttp.ClientSession): aiohttp.ClientSession 实例。 + """ + loop = asyncio.get_event_loop() + __aiohttp_session_pool[loop] = session + + +def to_form_urlencoded(data: dict) -> str: + temp = [] + for [k, v] in data.items(): + temp.append(f'{k}={quote(str(v)).replace("/", "%2F")}') + + return "&".join(temp) + + +@atexit.register +def __clean() -> None: + """ + 程序退出清理操作。 + """ + try: + loop = asyncio.get_event_loop() + except RuntimeError: + return + + async def __clean_task(): + s0 = __aiohttp_session_pool.get(loop, None) + if s0 is not None: + await s0.close() + s1 = __httpx_session_pool.get(loop, None) + if s1 is not None: + await s1.aclose() + + if loop.is_closed(): + loop.run_until_complete(__clean_task()) + else: + loop.create_task(__clean_task()) diff --git a/bilibili_api/utils/parse_link.py b/bilibili_api/utils/parse_link.py new file mode 100644 index 0000000000000000000000000000000000000000..25b181a15c5ee9334a5e170b078608da0e3341bf --- /dev/null +++ b/bilibili_api/utils/parse_link.py @@ -0,0 +1,661 @@ +""" +bilibili_api.utils.parse_link + +链接资源解析。 +""" + +import re +import json +from enum import Enum +from typing import Tuple, Union, Literal + +import httpx +from yarl import URL + +from .network import Api +from ..game import Game +from ..manga import Manga +from ..topic import Topic +from ..video import Video +from ..exceptions import * +from .utils import get_api +from ..live import LiveRoom +from ..dynamic import Dynamic +from .short import get_real_url +from ..note import Note, NoteType +from ..black_room import BlackRoom +from .credential import Credential +from ..audio import Audio, AudioList +from ..bangumi import Bangumi, Episode +from ..article import Article, ArticleList +from ..cheese import CheeseList, CheeseVideo +from ..interactive_video import InteractiveVideo +from ..favorite_list import FavoriteList, FavoriteListType +from ..user import User, ChannelSeries, ChannelSeriesType, get_self_info + +from .initial_state import get_initial_state + + +class ResourceType(Enum): + """ + 链接类型类。 + + + VIDEO: 视频 + + BANGUMI: 番剧 + + EPISODE: 番剧剧集 + + FAVORITE_LIST: 视频收藏夹 + + CHEESE: 课程 + + CHEESE_VIDEO: 课程视频 + + AUDIO: 音频 + + AUDIO_LIST: 歌单 + + ARTICLE: 专栏 + + USER: 用户 + + LIVE: 直播间 + + CHANNEL_SERIES: 合集与列表 + + BLACK_ROOM: 小黑屋 + + GAME: 游戏 + + TOPIC: 话题 + + MANGA: 漫画 + + NOTE: 笔记 + + FAILED: 错误 + """ + + VIDEO = "video" + INTERACTIVE_VIDEO = "interactive_video" + BANGUMI = "bangumi" + EPISODE = "episode" + FAVORITE_LIST = "favorite_list" + CHEESE_VIDEO = "cheese_video" + AUDIO = "audio" + AUDIO_LIST = "audio_list" + ARTICLE = "article" + USER = "user" + LIVE = "live" + CHANNEL_SERIES = "channel_series" + ARTICLE_LIST = "article_list" + DYNAMIC = "dynamic" + BLACK_ROOM = "black_room" + GAME = "game" + TOPIC = "topic" + MANGA = "manga" + NOTE = "note" + FAILED = "failed" + + +async def parse_link( + url: str, credential: Union[Credential, None] = None +) -> Union[ + Tuple[Video, Literal[ResourceType.VIDEO]], + Tuple[InteractiveVideo, Literal[ResourceType.INTERACTIVE_VIDEO]], + Tuple[Bangumi, Literal[ResourceType.BANGUMI]], + Tuple[Episode, Literal[ResourceType.EPISODE]], + Tuple[FavoriteList, Literal[ResourceType.FAVORITE_LIST]], + Tuple[CheeseVideo, Literal[ResourceType.CHEESE_VIDEO]], + Tuple[Audio, Literal[ResourceType.AUDIO]], + Tuple[AudioList, Literal[ResourceType.AUDIO_LIST]], + Tuple[Article, Literal[ResourceType.ARTICLE]], + Tuple[User, Literal[ResourceType.USER]], + Tuple[LiveRoom, Literal[ResourceType.LIVE]], + Tuple[ChannelSeries, Literal[ResourceType.CHANNEL_SERIES]], + Tuple[ArticleList, Literal[ResourceType.ARTICLE_LIST]], + Tuple[Dynamic, Literal[ResourceType.DYNAMIC]], + Tuple[BlackRoom, Literal[ResourceType.BLACK_ROOM]], + Tuple[Game, Literal[ResourceType.GAME]], + Tuple[Topic, Literal[ResourceType.TOPIC]], + Tuple[Manga, Literal[ResourceType.MANGA]], + Tuple[Note, Literal[ResourceType.NOTE]], + Tuple[Literal[-1], Literal[ResourceType.FAILED]], +]: + """ + 调用 yarl 解析 bilibili url 的函数。 + + Args: + url(str) : 链接 + + credential(Credential): 凭据类 + + Returns: + Tuple[obj, ResourceType]: (对象,类型) 或 -1,-1 表示出错 + """ + credential = credential if credential else Credential() + url = url.replace("\\", "/") # 说多了都是泪 + try: + obj = None + + # 排除 bvxxxxxxxxxx 等缩写 + sobj = await check_short_name(url, credential) + if sobj != -1: + sobj[0].credential = credential + return sobj + + # 删去首尾部空格 + url = url.strip() + # 添加 https: 协议头 + if url.lstrip("https:") == url: + url = "https:" + url + + # 转换为 yarl + url = URL(url) # type: ignore + + # 排除小黑屋 + black_room = parse_black_room(url, credential) # type: ignore + if not black_room == -1: + obj = (black_room, ResourceType.BLACK_ROOM) + return obj # type: ignore + + # 过滤 https://space.bilibili.com/ + if url.host == "space.bilibili.com" and url.path == "/" or url.path == "": # type: ignore + try: + info = await get_self_info(credential) + except Exception as e: + return (-1, ResourceType.FAILED) + else: + return (User(info["mid"], credential=credential), ResourceType.USER) + + channel = parse_season_series( + url, credential # type: ignore + ) # 不需要 real_url,提前处理 + if channel != -1: + return (channel, ResourceType.CHANNEL_SERIES) # type: ignore + + url = await get_real_url(str(url)) # type: ignore + url = URL(url) # type: ignore + + fl_space = parse_space_favorite_list(url, credential) # type: ignore + if fl_space != -1: + return fl_space # type: ignore + game = parse_game(url, credential) # type: ignore + if game != -1: + game.credential = credential # type: ignore + return (game, ResourceType.GAME) # type: ignore + topic = parse_topic(url, credential) # type: ignore + if topic != -1: + topic.credential = credential # type: ignore + return (topic, ResourceType.TOPIC) # type: ignore + festival_video = await parse_festival(url, credential) # type: ignore + if festival_video != -1: + festival_video.credential = credential # type: ignore + return (festival_video, ResourceType.VIDEO) # type: ignore + note = parse_note(url, credential) # type: ignore + if note != -1: + return (note, ResourceType.NOTE) # type: ignore + + obj = None + video = await parse_video(url, credential) # type: ignore + if not video == -1: + obj = video # auto_convert_video 会判断类型 + bangumi = parse_bangumi(url, credential) # type: ignore + if not bangumi == -1: + obj = (bangumi, ResourceType.BANGUMI) + episode = await parse_episode(url, credential) # type: ignore + if not episode == -1: + obj = (episode, ResourceType.EPISODE) + favorite_list = parse_favorite_list(url, credential) # type: ignore + if not favorite_list == -1: + obj = (favorite_list, ResourceType.FAVORITE_LIST) + cheese_video = await parse_cheese_video(url, credential) # type: ignore + if not cheese_video == -1: + obj = (cheese_video, ResourceType.CHEESE_VIDEO) + audio = parse_audio(url, credential) # type: ignore + if not audio == -1: + obj = (audio, ResourceType.AUDIO) + audio_list = parse_audio_list(url, credential) # type: ignore + if not audio_list == -1: + obj = (audio_list, ResourceType.AUDIO_LIST) + article = parse_article(url, credential) # type: ignore + if not article == -1: + obj = (article, ResourceType.ARTICLE) + article_list = parse_article_list(url, credential) # type: ignore + if not article_list == -1: + obj = (article_list, ResourceType.ARTICLE_LIST) + user = parse_user(url, credential) # type: ignore + if not user == -1: + obj = (user, ResourceType.USER) + live = parse_live(url, credential) # type: ignore + if not live == -1: + obj = (live, ResourceType.LIVE) + dynamic = parse_dynamic(url, credential) # type: ignore + if not dynamic == -1: + obj = (dynamic, ResourceType.DYNAMIC) + manga = parse_manga(url, credential) # type: ignore + if not manga == -1: + obj = (manga, ResourceType.MANGA) + opus_dynamic = parse_opus_dynamic(url, credential) # type: ignore + if not opus_dynamic == -1: + obj = (opus_dynamic, ResourceType.DYNAMIC) + + if obj == None or obj[0] == None: + return (-1, ResourceType.FAILED) + else: + obj[0].credential = credential # type: ignore + return obj # type: ignore + except Exception as e: + raise e + # return (-1, ResourceType.FAILED) + + +async def auto_convert_video( + video: Video, credential: Union[Credential, None] = None +) -> Tuple[Union[Video, Episode, InteractiveVideo], ResourceType]: + # check interactive video + video_info = await video.get_info() + if video_info["rights"]["is_stein_gate"] == 1: + return ( + InteractiveVideo(video.get_bvid(), credential=credential), + ResourceType.INTERACTIVE_VIDEO, + ) + + # check episode + if "redirect_url" in video_info: + reparse_link = await parse_link( + await get_real_url(video_info["redirect_url"]), credential=credential + ) # type: ignore + return reparse_link # type: ignore + + # return video + return (video, ResourceType.VIDEO) + + +async def check_short_name( + name: str, credential: Credential +) -> Union[ + Tuple[Video, Literal[ResourceType.VIDEO]], + Tuple[Episode, Literal[ResourceType.EPISODE]], + Tuple[CheeseVideo, Literal[ResourceType.CHEESE_VIDEO]], + Tuple[FavoriteList, Literal[ResourceType.FAVORITE_LIST]], + Tuple[User, Literal[ResourceType.USER]], + Tuple[Article, Literal[ResourceType.ARTICLE]], + Tuple[Audio, Literal[ResourceType.AUDIO]], + Tuple[AudioList, Literal[ResourceType.AUDIO_LIST]], + Tuple[ArticleList, Literal[ResourceType.ARTICLE_LIST]], + Literal[-1], +]: + """ + 解析: + - mlxxxxxxxxxx + - uidxxxxxxxxx + - cvxxxxxxxxxx + - auxxxxxxxxxx + - amxxxxxxxxxx + - rlxxxxxxxxxx + """ + if name[:2].upper() == "AV": + v = Video(aid=int(name[2:]), credential=credential) + return await auto_convert_video(v, credential=credential) # type: ignore + elif name[:2].upper() == "BV": + v = Video(bvid=name, credential=credential) + return await auto_convert_video(v, credential=credential) # type: ignore + elif name[:2].upper() == "ML": + return ( + FavoriteList(FavoriteListType.VIDEO, int(name[2:]), credential=credential), + ResourceType.FAVORITE_LIST, + ) + elif name[:3].upper() == "UID": + return (User(int(name[3:]), credential=credential), ResourceType.USER) + elif name[:2].upper() == "CV": + return (Article(int(name[2:]), credential=credential), ResourceType.ARTICLE) + elif name[:2].upper() == "AU": + return (Audio(int(name[2:]), credential=credential), ResourceType.AUDIO) + elif name[:2].upper() == "AM": + return ( + AudioList(int(name[2:]), credential=credential), + ResourceType.AUDIO_LIST, + ) + elif name[:2].upper() == "RL": + return ( + ArticleList(int(name[2:]), credential=credential), + ResourceType.ARTICLE_LIST, + ) + else: + return -1 + + +async def parse_video( + url: URL, credential: Credential +) -> Union[Tuple[Union[Video, Episode, InteractiveVideo], ResourceType], Literal[-1]]: + """ + 解析视频,如果不是返回 -1,否则返回对应类 + """ + if url.host == "www.bilibili.com" and url.parts[1] == "video": + raw_video_id = url.parts[2] + if raw_video_id[:2].upper() == "AV": + aid = int(raw_video_id[2:]) + v = Video(aid=aid, credential=credential) + elif raw_video_id[:2].upper() == "BV": + v = Video(bvid=raw_video_id, credential=credential) + else: + return -1 + return await auto_convert_video(v, credential=credential) + else: + return -1 + + +def parse_bangumi(url: URL, credential: Credential) -> Union[Bangumi, int]: + """ + 解析番剧,如果不是返回 -1,否则返回对应类 + """ + if url.host == "www.bilibili.com" and len(url.parts) >= 4: + if url.parts[:3] == ("/", "bangumi", "media"): + media_id = int(url.parts[3][2:]) + return Bangumi(media_id=media_id, credential=credential) + return -1 + + +async def parse_episode(url: URL, credential: Credential) -> Union[Episode, int]: + """ + 解析番剧剧集,如果不是返回 -1,否则返回对应类 + """ + if url.host == "www.bilibili.com" and len(url.parts) >= 3: + if url.parts[1] == "bangumi" and url.parts[2] == "play": + video_short_id = url.parts[3] + + if video_short_id[:2].upper() == "EP": + epid = int(video_short_id[2:]) + return Episode(epid=epid) + elif video_short_id[:2].upper() == "SS": + bangumi = Bangumi(ssid=int(video_short_id[2:])) + epid = (await bangumi.get_episodes())[0].get_epid() + return Episode(epid=epid) + return -1 + + +def parse_favorite_list(url: URL, credential: Credential) -> Union[FavoriteList, int]: + """ + 解析收藏夹,如果不是返回 -1,否则返回对应类 + """ + if url.host == "www.bilibili.com" and len(url.parts) >= 4: + if url.parts[:3] == ("/", "medialist", "detail"): + media_id = int(url.parts[3][2:]) + return FavoriteList(media_id=media_id, credential=credential) + return -1 + + +async def parse_cheese_video( + url: URL, credential: Credential +) -> Union[CheeseVideo, int]: + """ + 解析课程视频,如果不是返回 -1,否则返回对应类 + """ + if url.host == "www.bilibili.com" and len(url.parts) >= 4: + if url.parts[1] == "cheese" and url.parts[2] == "play": + if url.parts[3][:2].upper() == "EP": + epid = int(url.parts[3][2:]) + return CheeseVideo(epid=epid, credential=credential) + elif url.parts[3][:2].upper() == "SS": + clid = int(url.parts[3][2:]) + cl = CheeseList(season_id=clid, credential=credential) + return CheeseVideo( + epid=(await cl.get_list_raw())["items"][0]["id"], + credential=credential, + ) + return -1 + + +def parse_audio(url: URL, credential: Credential) -> Union[Audio, int]: + """ + 解析音频,如果不是返回 -1,否则返回对应类 + """ + if url.host == "www.bilibili.com" and url.parts[1] == "audio": + if url.parts[2][:2].upper() == "AU": + auid = int(url.parts[2][2:]) + return Audio(auid=auid, credential=credential) + return -1 + + +def parse_audio_list(url: URL, credential: Credential) -> Union[AudioList, int]: + """ + 解析歌单,如果不是返回 -1,否则返回对应类 + """ + if url.host == "www.bilibili.com" and url.parts[1] == "audio": + if url.parts[2][:2].upper() == "AM": + amid = int(url.parts[2][2:]) + return AudioList(amid=amid, credential=credential) + return -1 + + +def parse_article(url: URL, credential: Credential) -> Union[Article, int]: + """ + 解析专栏,如果不是返回 -1,否则返回对应类 + """ + if url.host == "www.bilibili.com" and len(url.parts) >= 3: + if url.parts[1] == "read" and url.parts[2][:2].upper() == "CV": + cvid = int(url.parts[2][2:]) + return Article(cvid=cvid, credential=credential) + return -1 + + +def parse_user(url: URL, credential: Credential) -> Union[User, int]: + if url.host == "space.bilibili.com": + if len(url.parts) >= 2: + uid = url.parts[1] + return User(uid=int(uid), credential=credential) + return -1 + + +def parse_live(url: URL, credential: Credential) -> Union[LiveRoom, int]: + if url.host == "live.bilibili.com": + if len(url.parts) >= 2: + room_display_id = int(url.parts[1]) + return LiveRoom(room_display_id=room_display_id, credential=credential) + return -1 + + +def parse_season_series(url: URL, credential: Credential) -> Union[ChannelSeries, int]: + if url.host == "space.bilibili.com": + if len(url.parts) >= 2: # path 存在 uid + try: + uid = int(url.parts[1]) + except: + pass # uid 无效 + else: + if len(url.parts) >= 4: # path 存在 collectiondetail 或者 seriesdetail + if url.parts[3] == "collectiondetail": + # https://space.bilibili.com/51537052/channel/collectiondetail?sid=22780&ctype=0 + if url.query.get("sid") is not None: + sid = int(url.query["sid"]) + return ChannelSeries( + uid, + ChannelSeriesType.SEASON, + id_=sid, + credential=credential, + ) + elif url.parts[3] == "seriesdetail": + # https://space.bilibili.com/558830935/channel/seriesdetail?sid=2972810&ctype=0 + if url.query.get("sid") is not None: + sid = int(url.query["sid"]) + return ChannelSeries( + uid, + ChannelSeriesType.SERIES, + id_=sid, + credential=credential, + ) + elif url.host == "www.bilibili.com": + if url.parts[1] == "list": + # https://www.bilibili.com/list/660303135?sid=2908236 旧版合集,不需要 real_url + if len(url.parts) >= 3: + uid = int(url.parts[2]) + if "sid" in url.query: + sid = int(url.query["sid"]) + return ChannelSeries( + uid, ChannelSeriesType.SERIES, id_=sid, credential=credential + ) + # https://www.bilibili.com/medialist/play/660303135?business=space 新版合集 + elif url.parts[1] == "medialist" and url.parts[2] == "play": + if len(url.parts) >= 4: + uid = int(url.parts[3]) + if "business_id" in url.query: + sid = int(url.query["business_id"]) + return ChannelSeries( + uid, ChannelSeriesType.SERIES, id_=sid, credential=credential + ) + return -1 + + +def parse_space_favorite_list( + url: URL, credential: Credential +) -> Union[ + Tuple[FavoriteList, ResourceType], Tuple[ChannelSeries, ResourceType], Literal[-1] +]: + if url.host == "space.bilibili.com": + uid = url.parts[1] # 获取 uid + if len(url.parts) >= 3: # path 存在 favlist + if url.parts[2] == "favlist": + if ( + len(url.parts) == 3 and url.query.get("fid") == None + ): # query 中不存在 fid 则返回默认收藏夹 + api = get_api("favorite-list")["info"]["list_list"] + params = {"up_mid": uid, "type": 2} + favorite_lists = ( + Api(**api, credential=credential) + .update_params(**params) + .result_sync + ) + + if favorite_lists == None: + return -1 + else: + default_favorite_id = int(favorite_lists["list"][0]["id"]) + return ( + FavoriteList( + media_id=default_favorite_id, credential=credential + ), + ResourceType.FAVORITE_LIST, + ) + + elif len(url.query) != 0: + fid = url.query.get("fid") # 未知数据类型 + ctype = url.query.get("ctype") + try: # 尝试转换为 int 类型并设置 fid_is_int + fid = int(fid) # type: ignore + fid_is_int = True + except: + fid_is_int = False + if ctype is None and fid_is_int: + # 我的视频收藏夹 + fid = int(fid) # type: ignore + return (FavoriteList(media_id=fid), ResourceType.FAVORITE_LIST) + elif fid_is_int: + if int(ctype) == 11: # type: ignore + fid = int(fid) # 转换为 int 类型 # type: ignore + fid_is_int = True + return ( + FavoriteList(media_id=fid, credential=credential), + ResourceType.FAVORITE_LIST, + ) + elif int(ctype) == 21: # type: ignore + fid = int(fid) # type: ignore + fid_is_int = True + return ( + ChannelSeries( + id_=fid, + type_=ChannelSeriesType.SEASON, + credential=credential, + ), + ResourceType.CHANNEL_SERIES, + ) + elif fid_is_int == False: + # ctype 不存在且 fid 非 int 类型 + if fid == FavoriteListType.ARTICLE.value: + return ( + FavoriteList( + FavoriteListType.ARTICLE, credential=credential + ), + ResourceType.FAVORITE_LIST, + ) + elif fid == FavoriteListType.CHEESE.value: + return ( + FavoriteList( + FavoriteListType.CHEESE, credential=credential + ), + ResourceType.FAVORITE_LIST, + ) + return -1 + + +def parse_article_list(url: URL, credential: Credential) -> Union[ArticleList, int]: + if url.host == "www.bilibili.com" and len(url.parts) >= 3: + if url.parts[:3] == ("/", "read", "readlist"): + rlid = int(url.parts[3][2:]) + return ArticleList(rlid=rlid, credential=credential) + return -1 + + +def parse_dynamic(url: URL, credential: Credential) -> Union[Dynamic, int]: + if url.host == "t.bilibili.com": + if len(url.parts) >= 2: + dynamic_id = int(url.parts[1]) + return Dynamic(dynamic_id, credential=credential) + return -1 + + +def parse_black_room(url: URL, credential: Credential) -> Union[BlackRoom, int]: + if len(url.parts) >= 3: + if url.parts[:3] == ("/", "blackroom", "ban"): + if len(url.parts) >= 4: # 存在 id + return BlackRoom(int(url.parts[3]), credential=credential) + return -1 + + +def parse_game(url: URL, credential: Credential) -> Union[Game, int]: + if ( + url.host == "www.biligame.com" + and url.parts[1] == "detail" + and url.query.get("id") is not None + ): + return Game(int(url.query["id"]), credential=credential) + return -1 + + +def parse_topic(url: URL, credential: Credential) -> Union[Topic, int]: + if url.host == "www.bilibili.com" and len(url.parts) >= 4: + if ( + url.parts[:4] == ("/", "v", "topic", "detail") + and url.query.get("topic_id") is not None + ): + return Topic(int(url.query["topic_id"]), credential=credential) + return -1 + + +def parse_manga(url: URL, credential: Credential) -> Union[Manga, int]: + if url.host == "manga.bilibili.com" and url.parts[1] == "detail": + return Manga(int(url.parts[2][2:]), credential=credential) + return -1 + + +async def parse_festival(url: URL, credential: Credential) -> Union[Video, int]: + bvid = url.query.get("bvid") + if bvid is not None: # get bvid if provided + return Video(bvid, credential=credential) + + if ( + url.host == "www.bilibili.com" and url.parts[1] == "festival" + ): # use __initial_state__ to fetch + content, content_type = await get_initial_state( + url=str(url), credential=credential + ) + return Video( + content["videoSections"][0]["episodes"][0]["bvid"], credential=credential + ) # 返回当前第一个视频 + return -1 + + +def parse_note(url: URL, credential: Credential) -> Union[Note, int]: + # https://www.bilibili.com/h5/note-app/view?cvid=21385583 + if url.host == "www.bilibili.com" and url.parts[1:4] == ("h5", "note-app", "view"): + if url.query.get("cvid") == None: + return -1 + return Note(cvid=int(url.query.get("cvid")), note_type=NoteType.PUBLIC, credential=credential) # type: ignore + return -1 + + +def parse_nianshizhiwang(url: URL) -> None: + # https://www.bilibili.com/festival/nianshizhiwang?bvid=BV1yt4y1Q7SS&spm_id_from=trigger_reload + pass + # 貌似 parse_bnj 已经可以判断了 + + +def parse_opus_dynamic(url: URL, credential: Credential) -> Union[Dynamic, int]: + # https://www.bilibili.com/opus/767674573455884292 + if url.host == "www.bilibili.com" and url.parts[:2] == ("/", "opus"): + return Dynamic(dynamic_id=int(url.parts[-1]), credential=credential) + return -1 diff --git a/bilibili_api/utils/picture.py b/bilibili_api/utils/picture.py new file mode 100644 index 0000000000000000000000000000000000000000..ce12cc7b6745300909f5c42f0aa4a11f207b9837 --- /dev/null +++ b/bilibili_api/utils/picture.py @@ -0,0 +1,244 @@ +import os +import tempfile +from typing import Any +from dataclasses import dataclass + +import httpx +from yarl import URL +from PIL import Image +import io + +from .utils import get_api +from .credential import Credential + + +@dataclass +class Picture: + """ + (@dataclasses.dataclass) + + 图片类,包含图片链接、尺寸以及下载操作。 + + Args: + height (int) : 高度 + + imageType (str) : 格式,例如: png + + size (Any) : 尺寸 + + url (str) : 图片链接 + + width (int) : 宽度 + + content (bytes): 图片内容 + + 可以不实例化,用 `from_url`, `from_content` 或 `from_file` 加载图片。 + """ + + height: int = -1 + imageType: str = "" + size: Any = "" + url: str = "" + width: int = -1 + content: bytes = b"" + + def __repr__(self) -> str: + # no content... + return f"Picture(height='{self.height}', width='{self.width}', imageType='{self.imageType}', size={self.size}, url='{self.url}')" + + def __set_picture_meta_from_bytes(self, imgtype: str) -> None: + tmp_dir = tempfile.gettempdir() + img_path = os.path.join(tmp_dir, "test." + imgtype) + with open(img_path, "wb+") as file: + file.write(self.content) + img = Image.open(img_path) + self.size = int(round(os.path.getsize(img_path) / 1024, 0)) + self.height = img.height + self.width = img.width + self.imageType = imgtype + + @staticmethod + async def async_load_url(url: str) -> "Picture": + """ + 加载网络图片。(async 方法) + + Args: + url (str): 图片链接 + + Returns: + Picture: 加载后的图片对象 + """ + if URL(url).scheme == "": + url = "https:" + url + obj = Picture() + session = httpx.AsyncClient() + resp = await session.get( + url, + headers={"User-Agent": "Mozilla/5.0"}, + ) + obj.content = resp.read() + obj.url = url + obj.__set_picture_meta_from_bytes(url.split("/")[-1].split(".")[1]) + return obj + + @staticmethod + def from_url(url: str) -> "Picture": + """ + 加载网络图片。 + + Args: + url (str): 图片链接 + + Returns: + Picture: 加载后的图片对象 + """ + if URL(url).scheme == "": + url = "https:" + url + obj = Picture() + session = httpx.Client() + resp = session.get( + url, + headers={"User-Agent": "Mozilla/5.0"}, + ) + obj.content = resp.read() + obj.url = url + obj.__set_picture_meta_from_bytes(url.split("/")[-1].split(".")[1]) + return obj + + @staticmethod + def from_file(path: str) -> "Picture": + """ + 加载本地图片。 + + Args: + path (str): 图片地址 + + Returns: + Picture: 加载后的图片对象 + """ + obj = Picture() + with open(path, "rb") as file: + obj.content = file.read() + obj.url = "file://" + path + obj.__set_picture_meta_from_bytes(os.path.basename(path).split(".")[1]) + return obj + + @staticmethod + def from_content(content: bytes, format: str) -> "Picture": + """ + 加载字节数据 + + Args: + content (str): 图片内容 + + format (str): 图片后缀名,如 `webp`, `jpg`, `ico` + + Returns: + Picture: 加载后的图片对象 + """ + obj = Picture() + obj.content = content + obj.url = "bytes://" + content.decode("utf-8", errors="ignore") + obj.__set_picture_meta_from_bytes(format) + return obj + + def _write_to_temp_file(self): + tmp_dir = tempfile.gettempdir() + img_path = os.path.join(tmp_dir, "test." + self.imageType) + open(img_path, "wb").write(self.content) + return img_path + + async def upload_file(self, credential: Credential, data: dict = None) -> "Picture": + """ + 上传图片至 B 站。 + + Args: + credential (Credential): 凭据类。 + + Returns: + Picture: `self` + """ + from ..dynamic import upload_image + + res = await upload_image(self, credential, data) + self.height = res["image_height"] + self.width = res["image_width"] + self.url = res["image_url"] + self.content = self.from_url(self.url).content + return self + + def upload_file_sync(self, credential: Credential) -> "Picture": + """ + 上传图片至 B 站。(同步函数) + + Args: + credential (Credential): 凭据类。 + + Returns: + Picture: `self` + """ + from ..dynamic import upload_image_sync + + res = upload_image_sync(self, credential) + self.url = res["image_url"] + self.height = res["image_height"] + self.width = res["image_width"] + self.content = self.from_url(self.url).content + return self + + def download_sync(self, path: str) -> "Picture": + """ + 下载图片至本地。(同步函数) + + Args: + path (str): 下载地址。 + + Returns: + Picture: `self` + """ + tmp_dir = tempfile.gettempdir() + img_path = os.path.join(tmp_dir, "test." + self.imageType) + open(img_path, "wb").write(self.content) + img = Image.open(img_path) + img.save(path, save_all=(True if self.imageType in ["webp", "gif"] else False)) + self.url = "file://" + path + return self + + def convert_format(self, new_format: str) -> "Picture": + """ + 将图片转换为另一种格式。 + + Args: + new_format (str): 新的格式。例:`png`, `ico`, `webp`. + + Returns: + Picture: `self` + """ + tmp_dir = tempfile.gettempdir() + img_path = os.path.join(tmp_dir, "test." + self.imageType) + open(img_path, "wb").write(self.content) + img = Image.open(img_path) + new_img_path = os.path.join(tmp_dir, "test." + new_format) + img.save(new_img_path) + with open(new_img_path, "rb") as file: + self.content = file.read() + self.__set_picture_meta_from_bytes(new_format) + return self + + async def download(self, path: str) -> "Picture": + """ + 下载图片至本地。 + + Args: + path (str): 下载地址。 + + Returns: + Picture: `self` + """ + tmp_dir = tempfile.gettempdir() + img_path = os.path.join(tmp_dir, "test." + self.imageType) + open(img_path, "wb").write(self.content) + img = Image.open(img_path) + img.save(path, save_all=(True if self.imageType in ["webp", "gif"] else False)) + self.url = "file://" + path + return self diff --git a/bilibili_api/utils/safecenter_captcha.py b/bilibili_api/utils/safecenter_captcha.py new file mode 100644 index 0000000000000000000000000000000000000000..def64e701db43227e8bfe9f8d63392ffe0cce71f --- /dev/null +++ b/bilibili_api/utils/safecenter_captcha.py @@ -0,0 +1,279 @@ +""" +bilibili_api.utils.safecenter_captcha + +验证人机测试 +""" +import os +import copy +import json +import time + +from .utils import get_api +from .network import Api + +validate = None +seccode = None +gt = None +challenge = None +key = None +server = None +thread = None + +API = get_api("login") + + +def set_gt_challenge(gee_gt: str, gee_challenge: str): + global gt, challenge + gt = gee_gt + challenge = gee_challenge + + +def _geetest_urlhandler(url: str, content_type: str): + """ + 极验验证服务器 html 源获取函数 + """ + global gt, challenge, key + url = url[1:] + if url[:7] == "result/": + global validate, seccode + datas = url[7:] + datas = datas.split("&") + for data in datas: + if data[:8] == "validate": + validate = data[9:] + elif data[:7] == "seccode": + seccode = data[8:].replace("%7C", "|") + with open( + os.path.abspath( + os.path.join( + os.path.dirname(__file__), "..", "data", "geetest", "done.html" + ) + ), + encoding="utf8", + ) as f: + html_source_bytes = f.read() + return html_source_bytes + elif url[:7] == "": + api = API["safecenter"]["captcha"] + json_data = Api(**api).result_sync + gt = json_data["gee_gt"] + challenge = json_data["gee_challenge"] + key = json_data["recaptcha_token"] + with open( + os.path.abspath( + os.path.join( + os.path.dirname(__file__), "..", "data", "geetest", "captcha.html" + ) + ), + encoding="utf8", + ) as f: + html_source_bytes = ( + f.read() + .replace("{ Python_Interface: GT }", f'"{gt}"') + .replace("{ Python_Interface: CHALLENGE }", f'"{challenge}"') + ) + return html_source_bytes + else: + return "" + + +def _start_server(urlhandler, hostname, port): + """Start an HTTP server thread on a specific port. + + Start an HTML/text server thread, so HTML or text documents can be + browsed dynamically and interactively with a web browser. Example use: + + >>> import time + >>> import pydoc + + Define a URL handler. To determine what the client is asking + for, check the URL and content_type. + + Then get or generate some text or HTML code and return it. + + >>> def my_url_handler(url, content_type): + ... text = 'the URL sent was: (%s, %s)' % (url, content_type) + ... return text + + Start server thread on port 0. + If you use port 0, the server will pick a random port number. + You can then use serverthread.port to get the port number. + + >>> port = 0 + >>> serverthread = pydoc._start_server(my_url_handler, port) + + Check that the server is really started. If it is, open browser + and get first page. Use serverthread.url as the starting page. + + >>> if serverthread.serving: + ... import webbrowser + + The next two lines are commented out so a browser doesn't open if + doctest is run on this module. + + #... webbrowser.open(serverthread.url) + #True + + Let the server do its thing. We just need to monitor its status. + Use time.sleep so the loop doesn't hog the CPU. + + >>> starttime = time.monotonic() + >>> timeout = 1 #seconds + + This is a short timeout for testing purposes. + + >>> while serverthread.serving: + ... time.sleep(.01) + ... if serverthread.serving and time.monotonic() - starttime > timeout: + ... serverthread.stop() + ... break + + Print any errors that may have occurred. + + >>> print(serverthread.error) + None + """ + import select + import threading + import http.server + import email.message + + class DocHandler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + """Process a request from an HTML browser. + + The URL received is in self.path. + Get an HTML page from self.urlhandler and send it. + """ + if self.path.endswith(".css"): + content_type = "text/css" + else: + content_type = "text/html" + self.send_response(200) + self.send_header("Content-Type", "%s; charset=UTF-8" % content_type) + self.end_headers() + self.wfile.write(self.urlhandler(self.path, content_type).encode("utf-8")) # type: ignore + + def log_message(self, *args): + # Don't log messages. + pass + + class DocServer(http.server.HTTPServer): + def __init__(self, host, port, callback): + self.host = host + self.address = (self.host, port) + self.callback = callback + self.base.__init__(self, self.address, self.handler) # type: ignore + self.quit = False + + def serve_until_quit(self): + while not self.quit: + rd, wr, ex = select.select([self.socket.fileno()], [], [], 1) + if rd: + self.handle_request() + self.server_close() + + def server_activate(self): + self.base.server_activate(self) # type: ignore + if self.callback: + self.callback(self) + + class ServerThread(threading.Thread): + def __init__(self, urlhandler, host, port): + self.urlhandler = urlhandler + self.host = host + self.port = int(port) + threading.Thread.__init__(self) + self.serving = False + self.error = None + + def run(self): + """Start the server.""" + try: + DocServer.base = http.server.HTTPServer # type: ignore + DocServer.handler = DocHandler # type: ignore + DocHandler.MessageClass = email.message.Message # type: ignore + DocHandler.urlhandler = staticmethod(self.urlhandler) # type: ignore + docsvr = DocServer(self.host, self.port, self.ready) + self.docserver = docsvr + docsvr.serve_until_quit() + except Exception as e: + self.error = e + + def ready(self, server): + self.serving = True + self.host = server.host + self.port = server.server_port + self.url = "http://%s:%d/" % (self.host, self.port) + + def stop(self): + """Stop the server and this thread nicely""" + if self.docserver != None: + self.docserver.quit = True + self.join() + # explicitly break a reference cycle: DocServer.callback + # has indirectly a reference to ServerThread. + self.docserver = None + self.serving = False + self.url = None + + thread = ServerThread(urlhandler, hostname, port) + thread.start() + # Wait until thread.serving is True to make sure we are + # really up before returning. + while not thread.error and not thread.serving: + time.sleep(0.01) + return thread + + +def start_server(): + """ + 验证码服务打开服务器 + + Returns: + ServerThread: 服务进程 + + 返回值内函数及属性: + - url (str) : 验证码服务地址 + - start (Callable): 开启进程 + - stop (Callable): 结束进程 + """ + global thread + thread = _start_server(_geetest_urlhandler, "127.0.0.1", 0) + print("请打开 " + thread.url + " 进行验证。") # type: ignore + return thread + + +def close_server(): + """ + 关闭服务器 + """ + global thread + thread.stop() # type: ignore + + +def get_result(): + """ + 获取结果 + + Returns: + dict: 验证结果 + """ + global validate, seccode, challenge, gt, key + if ( + validate is None + or seccode is None + or gt is None + or challenge is None + or key is None + ): + return -1 + else: + dct = { + "gt": copy.copy(gt), + "challenge": copy.copy(challenge), + "validate": copy.copy(validate), + "seccode": copy.copy(seccode), + "token": copy.copy(key), + } + return dct diff --git a/bilibili_api/utils/short.py b/bilibili_api/utils/short.py new file mode 100644 index 0000000000000000000000000000000000000000..e7ef47bac9b23e5502e53e38524acb4a4e666bb9 --- /dev/null +++ b/bilibili_api/utils/short.py @@ -0,0 +1,40 @@ +""" +bilibili_api.utils.short + +一个很简单的处理短链接的模块,主要是读取跳转链接。 +""" +from typing import Optional + +from .. import settings +from .credential import Credential +from .network import get_session, get_aiohttp_session + + +async def get_real_url(short_url: str, credential: Optional[Credential] = None) -> str: + """ + 获取短链接跳转目标,以进行操作。 + + Args: + short_url(str): 短链接。 + + credential(Credential \| None): 凭据类。 + + Returns: + 目标链接(如果不是有效的链接会报错) + + 返回值为原 url 类型 + """ + credential = credential if credential else Credential() + + try: + if settings.http_client == settings.HTTPClient.HTTPX: + resp = await get_session().head(url=str(short_url), follow_redirects=True) + else: + resp = await get_aiohttp_session().head( + url=str(short_url), allow_redirects=True + ) + u = resp.url + + return str(u) + except Exception as e: + raise e diff --git a/bilibili_api/utils/srt2ass.py b/bilibili_api/utils/srt2ass.py new file mode 100644 index 0000000000000000000000000000000000000000..f298be8c84c5a425cb7e3b5412a1183d528ce8de --- /dev/null +++ b/bilibili_api/utils/srt2ass.py @@ -0,0 +1,154 @@ +# type: ignore +# pylint: skip-file + +import io +import os +import re +import sys +import getopt + +DEBUG = False + + +def fileopen(input_file): + encodings = ["utf-32", "utf-16", "utf-8", "cp1252", "gb2312", "gbk", "big5"] + tmp = "" + for enc in encodings: + try: + with io.open(input_file, mode="r", encoding=enc) as fd: + tmp = fd.read() + break + except: + if DEBUG: + print(enc + " failed") + continue + return [tmp, enc] + + +def srt2ass(input_file, out_file, sub_type): + if ".ass" in input_file: + return input_file + + src = fileopen(input_file) + tmp = src[0] + encoding = src[1] + src = "" + utf8bom = "" + + if "\ufeff" in tmp: + tmp = tmp.replace("\ufeff", "") + utf8bom = "\ufeff" + + tmp = tmp.replace("\r", "") + lines = [x.strip() for x in tmp.split("\n") if x.strip()] + subLines = "" + tmpLines = "" + lineCount = 0 + output_file = out_file + + for ln in range(len(lines)): + line = lines[ln] + if ( + line.isdigit() + and ln + 1 < len(lines) + and re.match("-?\d\d:\d\d:\d\d", lines[(ln + 1)]) + ): + if tmpLines: + subLines += tmpLines + "\n" + tmpLines = "" + lineCount = 0 + continue + else: + if re.match("-?\d\d:\d\d:\d\d", line): + line = line.replace("-0", "0").replace(",", ".") + tmpLines += "Dialogue: 0," + line + ",Default,,0,0,0,," + else: + if lineCount < 2: + tmpLines += line + else: + tmpLines += "\\N" + line + lineCount += 1 + ln += 1 + + subLines += tmpLines + "\n" + + subLines = re.sub(r"\d(\d:\d{2}:\d{2}),(\d{2})\d", "\\1.\\2", subLines) + subLines = re.sub(r"\s+-->\s+", ",", subLines) + # replace style + subLines = re.sub(r"<([ubi])>", "{\\\\\g<1>1}", subLines) + subLines = re.sub(r"", "{\\\\\g<1>0}", subLines) + subLines = re.sub( + r'', "{\\\\c&H\\3\\2\\1&}", subLines + ) + subLines = re.sub(r"", "", subLines) + + if sub_type == "movie" or sub_type == "serie": + head_str = """[Script Info] +; This is an Advanced Sub Station Alpha v4+ script. +Title: Default Aegisub file +ScriptType: v4.00+ +Collisions: Normal +PlayDepth: 0 +PlayResX: 1920 +PlayResY: 1080 +[V4+ Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding +Style: Default,Verdana,60,&H00FFFFFF,&H000000FF,&H00282828,&H00000000,-1,0,0,0,100,100,0,0,1,3.75,0,2,0,0,70,1 +[Events] +Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text""" + + if sub_type == "anime": + head_str = """[Script Info] +; This is an Advanced Sub Station Alpha v4+ script. +Title: Default Aegisub file +ScriptType: v4.00+ +WrapStyle: 0 +ScaledBorderAndShadow: yes +YCbCr Matrix: TV.709 +PlayResX: 1920 +PlayResY: 1080 +[V4+ Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding +Style: Default,Verdana,60,&H00FFFFFF,&H000000FF,&H00282828,&H00000000,-1,0,0,0,100.2,100,0,0,1,3.75,0,2,0,0,79,1 +Style: BottomRight,Arial,30,&H00FFFFFF,&H000000FF,&H00282828,&H00000000,0,0,0,0,100,100,0,0,1,2,2,3,10,10,10,1 +Style: TopLeft,Arial,30,&H00FFFFFF,&H000000FF,&H00282828,&H00000000,0,0,0,0,100,100,0,0,1,2,2,7,10,10,10,1 +Style: TopRight,Arial,30,&H00FFFFFF,&H000000FF,&H00282828,&H00000000,0,0,0,0,100,100,0,0,1,2,2,9,10,10,10,1 +[Events] +Format: Layer, Start, End, Style, Actor, MarginL, MarginR, MarginV, Effect, Text""" + output_str = utf8bom + head_str + "\n" + subLines + # output_str = output_str.encode(encoding) + + with io.open(output_file, "w", encoding="utf8") as output: + output.write(output_str) + + output_file = output_file.replace("\\", "\\\\") + output_file = output_file.replace("/", "//") + return output_file + + +def print_helper(): + print("str2ass.py -t -i inputfile") + print("Available types: anime, serie, movie") + + +if __name__ == "__main__": + try: + opts, args = getopt.getopt(sys.argv[1:], "t:i:", ["type=", "input="]) + except getopt.GetoptError: + print_helper() + sys.exit(2) + + sub_type = "anime" + input_file = None + for opt, arg in opts: + if opt in ("-t", "--type"): + sub_type = arg + elif opt in ("-i", "--input"): + input_file = arg + + if not input_file: + print_helper() + sys.exit(2) + + filenameOutput = srt2ass(input_file, sub_type) + print("Output: " + filenameOutput) diff --git a/bilibili_api/utils/sync.py b/bilibili_api/utils/sync.py new file mode 100644 index 0000000000000000000000000000000000000000..068df4cdb0ad43231b613dff64cea8c20c036472 --- /dev/null +++ b/bilibili_api/utils/sync.py @@ -0,0 +1,33 @@ +""" +bilibili_api.utils.sync + +同步执行异步函数 +""" + +import asyncio +from typing import Any, TypeVar, Coroutine + +T = TypeVar("T") + + +def __ensure_event_loop() -> None: + try: + asyncio.get_event_loop() + + except: + asyncio.set_event_loop(asyncio.new_event_loop()) + + +def sync(coroutine: Coroutine[Any, Any, T]) -> T: + """ + 同步执行异步函数,使用可参考 [同步执行异步代码](https://nemo2011.github.io/bilibili-api/#/sync-executor) + + Args: + coroutine (Coroutine): 异步函数 + + Returns: + 该异步函数的返回值 + """ + __ensure_event_loop() + loop = asyncio.get_event_loop() + return loop.run_until_complete(coroutine) diff --git a/bilibili_api/utils/upos.py b/bilibili_api/utils/upos.py new file mode 100644 index 0000000000000000000000000000000000000000..7d46a7b173ffecbed5299a7fb8a7925237c49435 --- /dev/null +++ b/bilibili_api/utils/upos.py @@ -0,0 +1,237 @@ +""" +bilibili_api.utils.upos +""" +import os +import json +import httpx +import asyncio +from asyncio.tasks import create_task + +from .utils import get_api +from .network import get_session +from ..exceptions.NetworkException import NetworkException +from ..exceptions.ResponseCodeException import ResponseCodeException +from ..exceptions.ApiException import ApiException + + +class UposFile: + """ + Upos 文件对象 + """ + + path: str + size: int + + def __init__(self, path: str) -> None: + self.path = path + self.size = self._get_size() + + def _get_size(self) -> int: + """ + 获取文件大小 + + Returns: + int: 文件大小 + """ + + size: int = 0 + stream = open(self.path, "rb") + while True: + s: bytes = stream.read(1024) + + if not s: + break + + size += len(s) + + stream.close() + return size + + +class UposFileUploader: + """ + Upos 文件上传 + """ + + _upload_id: str + _upload_url: str + _session: httpx.AsyncClient + + def __init__(self, file: UposFile, preupload: dict) -> None: + self.file = file + self.preupload = preupload + self._upload_id = preupload["upload_id"] + self._upload_url = f'https:{preupload["endpoint"]}/{preupload["upos_uri"].removeprefix("upos://")}' + self._session = get_session() + + async def upload(self) -> dict: + """ + 上传文件 + + Returns: + dict: filename, cid + """ + page_size = self.file.size + # 所有分块起始位置 + chunk_offset_list = list(range(0, page_size, self.preupload["chunk_size"])) + # 分块总数 + total_chunk_count = len(chunk_offset_list) + # 并发上传分块 + chunk_number = 0 + # 上传队列 + chunks_pending = [] + + for offset in chunk_offset_list: + chunks_pending.insert( + 0, + self._upload_chunk(offset, chunk_number, total_chunk_count), + ) + chunk_number += 1 + + while chunks_pending: + tasks = [] + + while len(tasks) < self.preupload["threads"] and len(chunks_pending) > 0: + tasks.append(create_task(chunks_pending.pop())) + + result = await asyncio.gather(*tasks) + + for r in result: + if not r["ok"]: + chunks_pending.insert( + 0, + self._upload_chunk( + r["offset"], + r["chunk_number"], + total_chunk_count, + ), + ) + + data = await self._complete_file(total_chunk_count) + + return data + + + + async def _upload_chunk( + self, + offset: int, + chunk_number: int, + total_chunk_count: int, + ) -> dict: + """ + 上传视频分块 + + Args: + offset (int): 分块起始位置 + + chunk_number (int): 分块编号 + + total_chunk_count (int): 总分块数 + + + Returns: + dict: 上传结果和分块信息。 + """ + chunk_event_callback_data = { + "offset": offset, + "chunk_number": chunk_number, + "total_chunk_count": total_chunk_count, + } + + stream = open(self.file.path, "rb") + stream.seek(offset) + chunk = stream.read(self.preupload["chunk_size"]) + stream.close() + + err_return = { + "ok": False, + "chunk_number": chunk_number, + "offset": offset, + } + + real_chunk_size = len(chunk) + + params = { + "partNumber": str(chunk_number + 1), + "uploadId": str(self._upload_id), + "chunk": str(chunk_number), + "chunks": str(total_chunk_count), + "size": str(real_chunk_size), + "start": str(offset), + "end": str(offset + real_chunk_size), + "total": self.file.size, + } + + ok_return = { + "ok": True, + "chunk_number": chunk_number, + "offset": offset, + } + + try: + resp = await self._session.put( + self._upload_url, + data=chunk, # type: ignore + params=params, + headers={"x-upos-auth": self.preupload["auth"]}, + ) + if resp.status_code >= 400: + chunk_event_callback_data["info"] = f"Status {resp.status_code}" + return err_return + + data = resp.text + + if data != "MULTIPART_PUT_SUCCESS" and data != "": + chunk_event_callback_data["info"] = "分块上传失败" + return err_return + + except Exception as e: + chunk_event_callback_data["info"] = str(e) + return err_return + + return ok_return + + async def _complete_file(self, chunks: int) -> dict: + """ + 提交文件 + + Args: + chunks (int): 分块数量 + + Returns: + dict: filename: 该分 P 的标识符,用于最后提交视频。cid: 分 P 的 cid + """ + + data = { + "parts": list( + map(lambda x: {"partNumber": x, "eTag": "etag"}, range(1, chunks + 1)) + ) + } + + params = { + "output": "json", + "name": os.path.basename(os.path.split(self.file.path)[1]), + "profile": "ugcfx/bup", + "uploadId": self._upload_id, + "biz_id": self.preupload["biz_id"], + } + + resp = await self._session.post( + url=self._upload_url, + data=json.dumps(data), # type: ignore + headers={ + "x-upos-auth": self.preupload["auth"], + "content-type": "application/json; charset=UTF-8", + }, + params=params, + ) + if resp.status_code >= 400: + err = NetworkException(resp.status_code, "状态码错误,提交分 P 失败") + raise err + + data = json.loads(resp.read()) + + if data["OK"] != 1: + err = ResponseCodeException(-1, f'提交分 P 失败,原因: {data["message"]}') + raise err \ No newline at end of file diff --git a/bilibili_api/utils/utils.py b/bilibili_api/utils/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..bab054ad4b1510017aa71dc21e6e96d0a979e5c2 --- /dev/null +++ b/bilibili_api/utils/utils.py @@ -0,0 +1,212 @@ +""" +bilibili_api.utils.utils + +通用工具库。 +""" + +import json +import os +import random +from typing import List, TypeVar +from ..exceptions import StatementException + + +def get_api(field: str, *args) -> dict: + """ + 获取 API。 + + Args: + field (str): API 所属分类,即 data/api 下的文件名(不含后缀名) + + Returns: + dict, 该 API 的内容。 + """ + path = os.path.abspath( + os.path.join( + os.path.dirname(__file__), "..", "data", "api", f"{field.lower()}.json" + ) + ) + if os.path.exists(path): + with open(path, encoding="utf8") as f: + data = json.load(f) + for arg in args: + data = data[arg] + return data + else: + return {} + + +def crack_uid(crc32: str): + """ + 弹幕中的 CRC32 ID 转换成用户 UID。 + + 警告,破解后的 UID 不一定准确,有存在误差,仅供参考。 + + 代码翻译自:https://github.com/esterTion/BiliBili_crc2mid。 + + Args: + crc32 (str): crc32 计算摘要后的 UID。 + + Returns: + int, 真实用户 UID,不一定准确。 + """ + __CRCPOLYNOMIAL = 0xEDB88320 + __crctable = [None] * 256 + __index = [None] * 4 + + def __create_table(): + for i in range(256): + crcreg = i + for j in range(8): + if (crcreg & 1) != 0: + crcreg = __CRCPOLYNOMIAL ^ (crcreg >> 1) + else: + crcreg >>= 1 + __crctable[i] = crcreg + + __create_table() + + def __crc32(input_): + if type(input_) != str: + input_ = str(input_) + crcstart = 0xFFFFFFFF + len_ = len(input_) + for i in range(len_): + index = (crcstart ^ ord(input_[i])) & 0xFF + crcstart = (crcstart >> 8) ^ __crctable[index] + return crcstart + + def __crc32lastindex(input_): + if type(input_) != str: + input_ = str(input_) + crcstart = 0xFFFFFFFF + len_ = len(input_) + index = None + for i in range(len_): + index = (crcstart ^ ord(input_[i])) & 0xFF + crcstart = (crcstart >> 8) ^ __crctable[index] + return index + + def __getcrcindex(t): + for i in range(256): + if __crctable[i] >> 24 == t: + return i + return -1 + + def __deepCheck(i, index): + tc = 0x00 + str_ = "" + hash_ = __crc32(i) + tc = hash_ & 0xFF ^ index[2] + if not (57 >= tc >= 48): + return [0] + str_ += str(tc - 48) + hash_ = __crctable[index[2]] ^ (hash_ >> 8) + + tc = hash_ & 0xFF ^ index[1] + if not (57 >= tc >= 48): + return [0] + str_ += str(tc - 48) + hash_ = __crctable[index[1]] ^ (hash_ >> 8) + + tc = hash_ & 0xFF ^ index[0] + if not (57 >= tc >= 48): + return [0] + str_ += str(tc - 48) + hash_ = __crctable[index[0]] ^ (hash_ >> 8) + + return [1, str_] + + ht = int(crc32, 16) ^ 0xFFFFFFFF + i = 3 + while i >= 0: + __index[3 - i] = __getcrcindex(ht >> (i * 8)) + # pylint: disable=invalid-sequence-index + snum = __crctable[__index[3 - i]] + ht ^= snum >> ((3 - i) * 8) + i -= 1 + for i in range(10000000): + lastindex = __crc32lastindex(i) + if lastindex == __index[3]: + deepCheckData = __deepCheck(i, __index) + if deepCheckData[0]: + break + if i == 10000000: + return -1 + return str(i) + deepCheckData[1] + + +def join(seperator: str, array: list): + """ + 用指定字符连接数组 + + Args: + seperator (str) : 分隔字符 + + array (list): 数组 + + Returns: + str: 连接结果 + """ + return seperator.join(map(lambda x: str(x), array)) + + +ChunkT = TypeVar("ChunkT", List, List) + + +def chunk(arr: ChunkT, size: int) -> List[ChunkT]: + if size <= 0: + raise Exception('Parameter "size" must greater than 0') + + result = [] + temp = [] + + for i in range(len(arr)): + temp.append(arr[i]) + + if i % size == size - 1: + result.append(temp) + temp = [] + + if temp: + result.append(temp) + + return result + + +def get_deviceid(separator: str = "-", is_lowercase: bool = False) -> str: + """ + 获取随机 deviceid (dev_id) + + Args: + separator (str) : 分隔符 默认为 "-" + + is_lowercase (bool) : 是否以小写形式 默认为False + + 参考: https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/message/private_msg.md#发送私信web端 + + Returns: + str: device_id + """ + template = ["xxxxxxxx", "xxxx", "4xxx", "yxxx", "xxxxxxxxxxxx"] + dev_id_group = [] + for i in range(len(template)): + s = "" + group = template[i] + for k in group: + rand: int = int(16 * random.random()) + if k in "xy": + if k == "x": + s += hex(rand)[2:] + else: + s += hex(3 & rand | 8)[2:] + else: + s += "4" + dev_id_group.append(s) + res = join(separator, dev_id_group) + return res if is_lowercase else res.upper() + + +def raise_for_statement(statement: bool, msg: str="未满足条件") -> None: + if not statement: + raise StatementException(msg=msg) diff --git a/bilibili_api/utils/varint.py b/bilibili_api/utils/varint.py new file mode 100644 index 0000000000000000000000000000000000000000..069ba375b13ebd64589ca14054b8a4f2a7860cfa --- /dev/null +++ b/bilibili_api/utils/varint.py @@ -0,0 +1,32 @@ +""" +bilibili_api.utils.varint + +变长数字字节相关。 +""" + +from typing import Tuple + + +def read_varint(stream: bytes) -> Tuple[int, int]: + """ + 读取 varint。 + + Args: + stream (bytes): 字节流。 + + Returns: + Tuple[int, int],真实值和占用长度。 + """ + value = 0 + position = 0 + shift = 0 + while True: + if position >= len(stream): + break + byte = stream[position] + value += (byte & 0b01111111) << shift + if byte & 0b10000000 == 0: + break + position += 1 + shift += 7 + return value, position + 1 diff --git a/bilibili_api/video.py b/bilibili_api/video.py new file mode 100644 index 0000000000000000000000000000000000000000..a15bbaa6efd301c9fe6b7586fa47f033fb519ab1 --- /dev/null +++ b/bilibili_api/video.py @@ -0,0 +1,2517 @@ +""" +bilibili_api.video + +视频相关操作 + +注意,同时存在 page_index 和 cid 的参数,两者至少提供一个。 +""" + +import re +import os +import json +import struct +import asyncio +import logging +import datetime +from enum import Enum +from inspect import isfunction +from functools import cmp_to_key +from dataclasses import dataclass +from typing import Any, List, Union, Optional + +import httpx +import aiohttp + +from . import settings +from .utils.aid_bvid_transformer import bvid2aid, aid2bvid +from .utils.utils import get_api +from .utils.AsyncEvent import AsyncEvent +from .utils.credential import Credential +from .utils.BytesReader import BytesReader +from .utils.danmaku import Danmaku, SpecialDanmaku +from .utils.network import get_aiohttp_session, Api, get_session +from .exceptions import ( + ArgsException, + NetworkException, + ResponseException, + DanmakuClosedException, +) + +API = get_api("video") + + +def get_cid_info_sync(cid: int): + """ + 获取 cid 信息 (对应的视频,具体分 P 序号,up 等) + + Returns: + dict: 调用 https://hd.biliplus.com 的 API 返回的结果 + """ + api = API["info"]["cid_info"] + params = {"cid": cid} + return Api(**api).update_params(**params).result_sync + + +async def get_cid_info(cid: int): + """ + 获取 cid 信息 (对应的视频,具体分 P 序号,up 等) + + Returns: + dict: 调用 https://hd.biliplus.com 的 API 返回的结果 + """ + api = API["info"]["cid_info"] + params = {"cid": cid} + return await Api(**api).update_params(**params).result + + +class DanmakuOperatorType(Enum): + """ + 弹幕操作枚举 + + + DELETE - 删除弹幕 + + PROTECT - 保护弹幕 + + UNPROTECT - 取消保护弹幕 + """ + + DELETE = 1 + PROTECT = 2 + UNPROTECT = 3 + + +class VideoAppealReasonType: + """ + 视频投诉原因枚举 + + 注意: 每一项均为函数,部分项有参数,没有参数的函数无需调用函数,直接传入即可,有参数的函数请调用结果之后传入。 + + - ILLEGAL(): 违法违禁 + - PRON(): 色情 + - VULGAR(): 低俗 + - GAMBLED_SCAMS(): 赌博诈骗 + - VIOLENT(): 血腥暴力 + - PERSONAL_ATTACK(): 人身攻击 + - PLAGIARISM(bvid: str): 与站内其他视频撞车 + - BAD_FOR_YOUNGS(): 青少年不良信息 + - CLICKBAIT(): 不良封面/标题 + - POLITICAL_RUMORS(): 涉政谣言 + - SOCIAL_RUMORS(): 涉社会事件谣言 + - COVID_RUMORS(): 疫情谣言 + - UNREAL_EVENT(): 虚假不实消息 + - OTHER(): 有其他问题 + - LEAD_WAR(): 引战 + - CANNOT_CHARGE(): 不能参加充电 + - UNREAL_COPYRIGHT(source: str): 转载/自制类型错误 + """ + + ILLEGAL = lambda: 2 + PRON = lambda: 3 + VULGAR = lambda: 4 + GAMBLED_SCAMS = lambda: 5 + VIOLENT = lambda: 6 + PERSONAL_ATTACK = lambda: 7 + BAD_FOR_YOUNGS = lambda: 10000 + CLICKBAIT = lambda: 10013 + POLITICAL_RUMORS = lambda: 10014 + SOCIAL_RUMORS = lambda: 10015 + COVID_RUMORS = lambda: 10016 + UNREAL_EVENT = lambda: 10017 + OTHER = lambda: 1 + LEAD_WAR = lambda: 9 + CANNOT_CHARGE = lambda: 10 + + @staticmethod + def PLAGIARISM(bvid: str): + """ + 与站内其他视频撞车 + + Args: + bvid (str): 撞车对象 + """ + return {"tid": 8, "撞车对象": bvid} + + @staticmethod + def UNREAL_COPYRIGHT(source: str): + """ + 转载/自制类型错误 + + Args: + source (str): 原创视频出处 + """ + return {"tid": 52, "出处": source} + + +class Video: + """ + 视频类,各种对视频的操作均在里面。 + """ + + def __init__( + self, + bvid: Union[None, str] = None, + aid: Union[None, int] = None, + credential: Union[None, Credential] = None, + ): + """ + Args: + bvid (str | None, optional) : BV 号. bvid 和 aid 必须提供其中之一。 + + aid (int | None, optional) : AV 号. bvid 和 aid 必须提供其中之一。 + + credential (Credential | None, optional): Credential 类. Defaults to None. + """ + # ID 检查 + if bvid is not None: + self.set_bvid(bvid) + elif aid is not None: + self.set_aid(aid) + else: + # 未提供任一 ID + raise ArgsException("请至少提供 bvid 和 aid 中的其中一个参数。") + + # 未提供 credential 时初始化该类 + self.credential: Credential = Credential() if credential is None else credential + + # 用于存储视频信息,避免接口依赖视频信息时重复调用 + self.__info: Union[dict, None] = None + + def set_bvid(self, bvid: str) -> None: + """ + 设置 bvid。 + + Args: + bvid (str): 要设置的 bvid。 + """ + # 检查 bvid 是否有效 + if not re.search("^BV[a-zA-Z0-9]{10}$", bvid): + raise ArgsException("bvid 提供错误,必须是以 BV 开头的纯字母和数字组成的 12 位字符串(大小写敏感)。") + self.__bvid = bvid + self.__aid = bvid2aid(bvid) + + def get_bvid(self) -> str: + """ + 获取 BVID。 + + Returns: + str: BVID。 + """ + return self.__bvid + + def set_aid(self, aid: int) -> None: + """ + 设置 aid。 + + Args: + aid (int): AV 号。 + """ + if aid <= 0: + raise ArgsException("aid 不能小于或等于 0。") + + self.__aid = aid + self.__bvid = aid2bvid(aid) + + def get_aid(self) -> int: + """ + 获取 AID。 + + Returns: + int: aid。 + """ + return self.__aid + + async def get_info(self) -> dict: + """ + 获取视频信息。 + + Returns: + dict: 调用 API 返回的结果。 + """ + api = API["info"]["info"] + params = {"bvid": self.get_bvid(), "aid": self.get_aid()} + resp = ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + # 存入 self.__info 中以备后续调用 + self.__info = resp + return resp + + async def get_detail(self) -> dict: + """ + 获取视频详细信息 + + Returns: + dict: 调用 API 返回的结果。 + """ + api = API["info"]["detail"] + params = { + "bvid": self.get_bvid(), + "aid": self.get_aid(), + "need_operation_card": 0, + "need_elec": 0, + } + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def __get_info_cached(self) -> dict: + """ + 获取视频信息,如果已获取过则使用之前获取的信息,没有则重新获取。 + + Returns: + dict: 调用 API 返回的结果。 + """ + if self.__info is None: + return await self.get_info() + return self.__info + + # get_stat 403/404 https://github.com/SocialSisterYi/bilibili-API-collect/issues/797 等恢复 + # async def get_stat(self) -> dict: + # """ + # 获取视频统计数据(播放量,点赞数等)。 + + # Returns: + # dict: 调用 API 返回的结果。 + # """ + # api = API["info"]["stat"] + # params = {"bvid": self.get_bvid(), "aid": self.get_aid()} + # return await Api(**api, credential=self.credential).update_params(**params).result + + async def get_up_mid(self) -> int: + """ + 获取视频 up 主的 mid。 + + Returns: + int: up_mid + """ + info = await self.__get_info_cached() + return info["owner"]["mid"] + + async def get_tags( + self, page_index: Union[int, None] = 0, cid: Union[int, None] = None + ) -> List[dict]: + """ + 获取视频标签。 + + Args: + page_index (int | None): 分 P 序号. Defaults to 0. + + cid (int | None): 分 P 编码. Defaults to None. + + Returns: + List[dict]: 调用 API 返回的结果。 + """ + if cid == None: + if page_index == None: + raise ArgsException("page_index 和 cid 至少提供一个。") + + cid = await self.get_cid(page_index=page_index) + api = API["info"]["tags"] + params = {"bvid": self.get_bvid(), "aid": self.get_aid(), "cid": cid} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_chargers(self) -> dict: + """ + 获取视频充电用户。 + + Returns: + dict: 调用 API 返回的结果。 + """ + info = await self.__get_info_cached() + mid = info["owner"]["mid"] + api = API["info"]["chargers"] + params = {"aid": self.get_aid(), "bvid": self.get_bvid(), "mid": mid} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_pages(self) -> List[dict]: + """ + 获取分 P 信息。 + + Returns: + dict: 调用 API 返回的结果。 + """ + api = API["info"]["pages"] + params = {"aid": self.get_aid(), "bvid": self.get_bvid()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def __get_cid_by_index(self, page_index: int) -> int: + """ + 根据分 p 号获取 cid。 + + Args: + page_index (int): 分 P 号,从 0 开始。 + + Returns: + int: cid 分 P 的唯一 ID。 + """ + if page_index < 0: + raise ArgsException("分 p 号必须大于或等于 0。") + + info = await self.__get_info_cached() + pages = info["pages"] + + if len(pages) <= page_index: + raise ArgsException("不存在该分 p。") + + page = pages[page_index] + cid = page["cid"] + return cid + + async def get_video_snapshot( + self, + cid: Union[int, None] = None, + json_index: bool = False, + pvideo: bool = True, + ) -> dict: + """ + 获取视频快照(视频各个时间段的截图拼图) + + Args: + cid(int): 分 P CID(可选) + + json_index(bool): json 数组截取时间表 True 为需要,False 不需要 + + pvideo(bool): 是否只获取预览 + + Returns: + dict: 调用 API 返回的结果,数据中 Url 没有 http 头 + """ + params: dict[str, Any] = {"aid": self.get_aid()} + if pvideo: + api = API["info"]["video_snapshot_pvideo"] + else: + params["bvid"] = self.get_bvid() + if json_index: + params["index"] = 1 + if cid: + params["cid"] = cid + api = API["info"]["video_snapshot"] + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_cid(self, page_index: int) -> int: + """ + 获取稿件 cid + + Args: + page_index(int): 分 P + + Returns: + int: cid + """ + return await self.__get_cid_by_index(page_index) + + async def get_download_url( + self, + page_index: Union[int, None] = None, + cid: Union[int, None] = None, + html5: bool = False, + ) -> dict: + """ + 获取视频下载信息。 + + 返回结果可以传入 `VideoDownloadURLDataDetecter` 进行解析。 + + page_index 和 cid 至少提供其中一个,其中 cid 优先级最高 + + Args: + page_index (int | None, optional) : 分 P 号,从 0 开始。Defaults to None + + cid (int | None, optional) : 分 P 的 ID。Defaults to None + + html5 (bool, optional): 是否以 html5 平台访问,这样子能直接在网页中播放,但是链接少。 + + Returns: + dict: 调用 API 返回的结果。 + """ + if cid is None: + if page_index is None: + raise ArgsException("page_index 和 cid 至少提供一个。") + + cid = await self.__get_cid_by_index(page_index) + + api = API["info"]["playurl"] + if html5: + params = { + "avid": self.get_aid(), + "cid": cid, + "qn": "127", + "otype": "json", + "fnval": 4048, + "fourk": 1, + "platform": "html5", + "high_quality": "1", + } + else: + params = { + "avid": self.get_aid(), + "cid": cid, + "qn": "127", + "otype": "json", + "fnval": 4048, + "fourk": 1, + } + result = ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + result.update({"is_html5": True} if html5 else {}) + return result + + async def get_related(self) -> dict: + """ + 获取相关视频信息。 + + Returns: + dict: 调用 API 返回的结果。 + """ + api = API["info"]["related"] + params = {"aid": self.get_aid(), "bvid": self.get_bvid()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_relation(self) -> dict: + """ + 获取用户与视频的关系 + + Returns: + dict: 调用 API 返回的结果。 + """ + self.credential.raise_for_no_sessdata() + api = API["info"]["relation"] + params = {"aid": self.get_aid(), "bvid": self.get_bvid()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def has_liked(self) -> bool: + """ + 视频是否点赞过。 + + Returns: + bool: 视频是否点赞过。 + """ + self.credential.raise_for_no_sessdata() + + api = API["info"]["has_liked"] + params = {"bvid": self.get_bvid(), "aid": self.get_aid()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_pay_coins(self) -> int: + """ + 获取视频已投币数量。 + + Returns: + int: 视频已投币数量。 + """ + self.credential.raise_for_no_sessdata() + + api = API["info"]["get_pay_coins"] + params = {"bvid": self.get_bvid(), "aid": self.get_aid()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + )["multiply"] + + async def has_favoured(self) -> bool: + """ + 是否已收藏。 + + Returns: + bool: 视频是否已收藏。 + """ + self.credential.raise_for_no_sessdata() + + api = API["info"]["has_favoured"] + params = {"bvid": self.get_bvid(), "aid": self.get_aid()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + )["favoured"] + + async def is_forbid_note(self) -> bool: + """ + 是否禁止笔记。 + + Returns: + bool: 是否禁止笔记。 + """ + api = API["info"]["is_forbid"] + params = {"aid": self.get_aid()} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + )["forbid_note_entrance"] + + async def get_private_notes_list(self) -> list: + """ + 获取稿件私有笔记列表。 + + Returns: + list: note_Ids。 + """ + self.credential.raise_for_no_sessdata() + + api = API["info"]["private_notes"] + params = {"oid": self.get_aid(), "oid_type": 0} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + )["noteIds"] + + async def get_public_notes_list(self, pn: int, ps: int) -> dict: + """ + 获取稿件公开笔记列表。 + + Args: + pn (int): 页码 + + ps (int): 每页项数 + + Returns: + dict: 调用 API 返回的结果。 + """ + + api = API["info"]["public_notes"] + params = {"oid": self.get_aid(), "oid_type": 0, "pn": pn, "ps": ps} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_ai_conclusion(self, cid: Optional[int] = None, page_index: Optional[int] = None, + up_mid: Optional[int] = None) -> dict: + """ + 获取稿件 AI 总结结果。 + + cid 和 page_index 至少提供其中一个,其中 cid 优先级最高 + + Args: + cid (Optional, int): 分 P 的 cid。 + + page_index (Optional, int): 分 P 号,从 0 开始。 + + up_mid (Optional, int): up 主的 mid。 + + Returns: + dict: 调用 API 返回的结果。 + """ + if cid is None: + if page_index is None: + raise ArgsException("page_index 和 cid 至少提供一个。") + + cid = await self.__get_cid_by_index(page_index) + + api = API["info"]["ai_conclusion"] + params = {"aid": self.get_aid(), "bvid": self.get_bvid(), "cid": cid, + "up_mid": await self.get_up_mid() if up_mid is None else up_mid} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def get_danmaku_view( + self, page_index: Union[int, None] = None, cid: Union[int, None] = None + ) -> dict: + """ + 获取弹幕设置、特殊弹幕、弹幕数量、弹幕分段等信息。 + + Args: + page_index (int, optional): 分 P 号,从 0 开始。Defaults to None + + cid (int, optional): 分 P 的 ID。Defaults to None + + Returns: + dict: 调用 API 返回的结果。 + """ + if cid is None: + if page_index is None: + raise ArgsException("page_index 和 cid 至少提供一个。") + + cid = await self.__get_cid_by_index(page_index) + + session = get_session() + api = API["danmaku"]["view"] + + config = {} + config["url"] = api["url"] + config["params"] = {"type": 1, "oid": cid, "pid": self.get_aid()} + config["cookies"] = self.credential.get_cookies() + config["headers"] = { + "Referer": "https://www.bilibili.com", + "User-Agent": "Mozilla/5.0", + } + + try: + resp = await session.get(**config) + except Exception as e: + raise NetworkException(-1, str(e)) + + resp_data = resp.read() + json_data = {} + reader = BytesReader(resp_data) + + # 解析二进制数据流 + + def read_dm_seg(stream: bytes): + reader_ = BytesReader(stream) + data = {} + while not reader_.has_end(): + t = reader_.varint() >> 3 + if t == 1: + data["page_size"] = reader_.varint() + elif t == 2: + data["total"] = reader_.varint() + else: + continue + return data + + def read_flag(stream: bytes): + reader_ = BytesReader(stream) + data = {} + while not reader_.has_end(): + t = reader_.varint() >> 3 + if t == 1: + data["rec_flag"] = reader_.varint() + elif t == 2: + data["rec_text"] = reader_.string() + elif t == 3: + data["rec_switch"] = reader_.varint() + else: + continue + return data + + def read_command_danmakus(stream: bytes): + reader_ = BytesReader(stream) + data = {} + while not reader_.has_end(): + t = reader_.varint() >> 3 + if t == 1: + data["id"] = reader_.varint() + elif t == 2: + data["oid"] = reader_.varint() + elif t == 3: + data["mid"] = reader_.varint() + elif t == 4: + data["commend"] = reader_.string() + elif t == 5: + data["content"] = reader_.string() + elif t == 6: + data["progress"] = reader_.varint() + elif t == 7: + data["ctime"] = reader_.string() + elif t == 8: + data["mtime"] = reader_.string() + elif t == 9: + data["extra"] = json.loads(reader_.string()) + + elif t == 10: + data["id_str"] = reader_.string() + else: + continue + return data + + def read_settings(stream: bytes): + reader_ = BytesReader(stream) + data = {} + while not reader_.has_end(): + t = reader_.varint() >> 3 + + if t == 1: + data["dm_switch"] = reader_.bool() + elif t == 2: + data["ai_switch"] = reader_.bool() + elif t == 3: + data["ai_level"] = reader_.varint() + elif t == 4: + data["enable_top"] = reader_.bool() + elif t == 5: + data["enable_scroll"] = reader_.bool() + elif t == 6: + data["enable_bottom"] = reader_.bool() + elif t == 7: + data["enable_color"] = reader_.bool() + elif t == 8: + data["enable_special"] = reader_.bool() + elif t == 9: + data["prevent_shade"] = reader_.bool() + elif t == 10: + data["dmask"] = reader_.bool() + elif t == 11: + data["opacity"] = reader_.float(True) + elif t == 12: + data["dm_area"] = reader_.varint() + elif t == 13: + data["speed_plus"] = reader_.float(True) + elif t == 14: + data["font_size"] = reader_.float(True) + elif t == 15: + data["screen_sync"] = reader_.bool() + elif t == 16: + data["speed_sync"] = reader_.bool() + elif t == 17: + data["font_family"] = reader_.string() + elif t == 18: + data["bold"] = reader_.bool() + elif t == 19: + data["font_border"] = reader_.varint() + elif t == 20: + data["draw_type"] = reader_.string() + else: + continue + return data + + def read_image_danmakus(string: bytes): + image_list = [] + reader_ = BytesReader(string) + while not reader_.has_end(): + type_ = reader_.varint() >> 3 + if type_ == 1: + details_dict: dict[Any, Any] = {"texts": []} + img_details = reader_.bytes_string() + reader_details = BytesReader(img_details) + while not reader_details.has_end(): + type_details = reader_details.varint() >> 3 + if type_details == 1: + details_dict["texts"].append(reader_details.string()) + elif type_details == 2: + details_dict["image"] = reader_details.string() + elif type_details == 3: + id_string = reader_details.bytes_string() + id_reader = BytesReader(id_string) + while not id_reader.has_end(): + type_id = id_reader.varint() >> 3 + if type_id == 2: + details_dict["id"] = id_reader.varint() + else: + raise ResponseException("解析响应数据错误") + image_list.append(details_dict) + else: + raise ResponseException("解析响应数据错误") + return image_list + + while not reader.has_end(): + type_ = reader.varint() >> 3 + + if type_ == 1: + json_data["state"] = reader.varint() + elif type_ == 2: + json_data["text"] = reader.string() + elif type_ == 3: + json_data["text_side"] = reader.string() + elif type_ == 4: + json_data["dm_seg"] = read_dm_seg(reader.bytes_string()) + elif type_ == 5: + json_data["flag"] = read_flag(reader.bytes_string()) + elif type_ == 6: + if "special_dms" not in json_data: + json_data["special_dms"] = [] + json_data["special_dms"].append(reader.string()) + elif type_ == 7: + json_data["check_box"] = reader.bool() + elif type_ == 8: + json_data["count"] = reader.varint() + elif type_ == 9: + if "command_dms" not in json_data: + json_data["command_dms"] = [] + json_data["command_dms"].append( + read_command_danmakus(reader.bytes_string()) + ) + elif type_ == 10: + json_data["dm_setting"] = read_settings(reader.bytes_string()) + elif type_ == 12: + json_data["image_dms"] = read_image_danmakus(reader.bytes_string()) + else: + continue + return json_data + + async def get_danmakus( + self, + page_index: int = 0, + date: Union[datetime.date, None] = None, + cid: Union[int, None] = None, + from_seg: Union[int, None] = None, + to_seg: Union[int, None] = None, + ) -> List[Danmaku]: + """ + 获取弹幕。 + + Args: + page_index (int, optional): 分 P 号,从 0 开始。Defaults to None + + date (datetime.Date | None, optional): 指定日期后为获取历史弹幕,精确到年月日。Defaults to None. + + cid (int | None, optional): 分 P 的 ID。Defaults to None + + from_seg (int, optional): 从第几段开始(0 开始编号,None 为从第一段开始,一段 6 分钟). Defaults to None. + + to_seg (int, optional): 到第几段结束(0 开始编号,None 为到最后一段,包含编号的段,一段 6 分钟). Defaults to None. + + 注意: + - 1. 段数可以使用 `get_danmaku_view()["dm_seg"]["total"]` 查询。 + - 2. `from_seg` 和 `to_seg` 仅对 `date == None` 的时候有效果。 + - 3. 例:取前 `12` 分钟的弹幕:`from_seg=0, to_seg=1` + + Returns: + List[Danmaku]: Danmaku 类的列表。 + """ + if date is not None: + self.credential.raise_for_no_sessdata() + + if cid is None: + if page_index is None: + raise ArgsException("page_index 和 cid 至少提供一个。") + + cid = await self.__get_cid_by_index(page_index) + + session = get_session() + aid = self.get_aid() + params: dict[str, Any] = {"oid": cid, "type": 1, "pid": aid} + if date is not None: + # 获取历史弹幕 + api = API["danmaku"]["get_history_danmaku"] + params["date"] = date.strftime("%Y-%m-%d") + params["type"] = 1 + from_seg = to_seg = 0 + else: + api = API["danmaku"]["get_danmaku"] + if from_seg == None: + from_seg = 0 + if to_seg == None: + view = await self.get_danmaku_view(cid=cid) + to_seg = view["dm_seg"]["total"] - 1 + + danmakus = [] + + for seg in range(from_seg, to_seg + 1): + if date is None: + # 仅当获取当前弹幕时需要该参数 + params["segment_index"] = seg + 1 + + config = {} + config["url"] = api["url"] + config["params"] = params + config["headers"] = { + "Referer": "https://www.bilibili.com", + "User-Agent": "Mozilla/5.0", + } + config["cookies"] = self.credential.get_cookies() + + try: + req = await session.get(**config) + except Exception as e: + raise NetworkException(-1, str(e)) + + if "content-type" not in req.headers.keys(): + break + else: + content_type = req.headers["content-type"] + if content_type != "application/octet-stream": + raise ResponseException("返回数据类型错误:") + + # 解析二进制流数据 + data = req.read() + if data == b"\x10\x01": + # 视频弹幕被关闭 + raise DanmakuClosedException() + + reader = BytesReader(data) + while not reader.has_end(): + type_ = reader.varint() >> 3 + if type_ != 1: + if type_ == 4: + reader.bytes_string() + # 什么鬼?我用 protoc 解析出乱码! + elif type_ == 5: + # 大会员专属颜色 + reader.varint() + reader.varint() + reader.varint() + reader.bytes_string() + elif type_ == 13: + # ??? + continue + else: + raise ResponseException("解析响应数据错误") + + dm = Danmaku("") + dm_pack_data = reader.bytes_string() + dm_reader = BytesReader(dm_pack_data) + + while not dm_reader.has_end(): + data_type = dm_reader.varint() >> 3 + + if data_type == 1: + dm.id_ = dm_reader.varint() + elif data_type == 2: + dm.dm_time = dm_reader.varint() / 1000 + elif data_type == 3: + dm.mode = dm_reader.varint() + elif data_type == 4: + dm.font_size = dm_reader.varint() + elif data_type == 5: + color = dm_reader.varint() + if color != 60001: + color = hex(color)[2:] + else: + color = "special" + dm.color = color + elif data_type == 6: + dm.crc32_id = dm_reader.string() + elif data_type == 7: + dm.text = dm_reader.string() + elif data_type == 8: + dm.send_time = dm_reader.varint() + elif data_type == 9: + dm.weight = dm_reader.varint() + elif data_type == 10: + dm.action = str(dm_reader.string()) + elif data_type == 11: + dm.pool = dm_reader.varint() + elif data_type == 12: + dm.id_str = dm_reader.string() + elif data_type == 13: + dm.attr = dm_reader.varint() + elif data_type == 14: + dm.uid = dm_reader.varint() + elif data_type == 15: + dm_reader.varint() + elif data_type == 20: + dm_reader.bytes_string() + elif data_type == 21: + dm_reader.bytes_string() + elif data_type == 22: + dm_reader.bytes_string() + elif data_type == 25: + dm_reader.varint() + elif data_type == 26: + dm_reader.varint() + else: + break + danmakus.append(dm) + return danmakus + + async def get_special_dms( + self, page_index: int = 0, cid: Union[int, None] = None + ) -> List[SpecialDanmaku]: + """ + 获取特殊弹幕 + + Args: + page_index (int, optional) : 分 P 号. Defaults to 0. + + cid (int | None, optional): 分 P id. Defaults to None. + + Returns: + List[SpecialDanmaku]: 调用接口解析后的结果 + """ + if cid is None: + if page_index is None: + raise ArgsException("page_index 和 cid 至少提供一个。") + + cid = await self.__get_cid_by_index(page_index) + + view = await self.get_danmaku_view(cid=cid) + special_dms = view["special_dms"][0] + if settings.proxy != "": + sess = httpx.AsyncClient(proxies={"all://": settings.proxy}) + else: + sess = httpx.AsyncClient() + dm_content = await sess.get(special_dms, cookies=self.credential.get_cookies()) + dm_content.raise_for_status() + reader = BytesReader(dm_content.content) + dms: List[SpecialDanmaku] = [] + while not reader.has_end(): + spec_dm = SpecialDanmaku("") + type_ = reader.varint() >> 3 + if type_ == 1: + reader_ = BytesReader(reader.bytes_string()) + while not reader_.has_end(): + type__ = reader_.varint() >> 3 + if type__ == 1: + spec_dm.id_ = reader_.varint() + elif type__ == 3: + spec_dm.mode = reader_.varint() + elif type__ == 4: + reader_.varint() + elif type__ == 5: + reader_.varint() + elif type__ == 6: + reader_.string() + elif type__ == 7: + spec_dm.content = reader_.string() + elif type__ == 8: + reader_.varint() + elif type__ == 11: + spec_dm.pool = reader_.varint() + elif type__ == 12: + spec_dm.id_str = reader_.string() + else: + continue + else: + continue + dms.append(spec_dm) + return dms + + async def get_history_danmaku_index( + self, + page_index: Union[int, None] = None, + date: Union[datetime.date, None] = None, + cid: Union[int, None] = None, + ) -> Union[None, List[str]]: + """ + 获取特定月份存在历史弹幕的日期。 + + Args: + page_index (int | None, optional): 分 P 号,从 0 开始。Defaults to None + + date (datetime.date | None): 精确到年月. Defaults to None。 + + cid (int | None, optional): 分 P 的 ID。Defaults to None + + Returns: + None | List[str]: 调用 API 返回的结果。不存在时为 None。 + """ + if date is None: + raise ArgsException("请提供 date 参数") + + self.credential.raise_for_no_sessdata() + + if cid is None: + if page_index is None: + raise ArgsException("page_index 和 cid 至少提供一个。") + + cid = await self.__get_cid_by_index(page_index) + + api = API["danmaku"]["get_history_danmaku_index"] + params = {"oid": cid, "month": date.strftime("%Y-%m"), "type": 1} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def has_liked_danmakus( + self, + page_index: Union[int, None] = None, + ids: Union[List[int], None] = None, + cid: Union[int, None] = None, + ) -> dict: + """ + 是否已点赞弹幕。 + + Args: + page_index (int | None, optional): 分 P 号,从 0 开始。Defaults to None + + ids (List[int] | None): 要查询的弹幕 ID 列表。 + + cid (int | None, optional): 分 P 的 ID。Defaults to None + + Returns: + dict: 调用 API 返回的结果。 + """ + if ids is None or len(ids) == 0: + raise ArgsException("请提供 ids 参数并至少有一个元素") + + self.credential.raise_for_no_sessdata() + + if cid is None: + if page_index is None: + raise ArgsException("page_index 和 cid 至少提供一个。") + + cid = await self.__get_cid_by_index(page_index) + + api = API["danmaku"]["has_liked_danmaku"] + params = {"oid": cid, "ids": ",".join(ids)} # type: ignore + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def send_danmaku( + self, + page_index: Union[int, None] = None, + danmaku: Union[Danmaku, None] = None, + cid: Union[int, None] = None, + ) -> dict: + """ + 发送弹幕。 + + Args: + page_index (int | None, optional): 分 P 号,从 0 开始。Defaults to None + + danmaku (Danmaku | None) : Danmaku 类。 + + cid (int | None, optional): 分 P 的 ID。Defaults to None + + Returns: + dict: 调用 API 返回的结果。 + """ + + if danmaku is None: + raise ArgsException("请提供 danmaku 参数") + + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + if cid is None: + if page_index is None: + raise ArgsException("page_index 和 cid 至少提供一个。") + + cid = await self.__get_cid_by_index(page_index) + + api = API["danmaku"]["send_danmaku"] + + if danmaku.is_sub: + pool = 1 + else: + pool = 0 + data = { + "type": 1, + "oid": cid, + "msg": danmaku.text, + "aid": self.get_aid(), + "bvid": self.get_bvid(), + "progress": int(danmaku.dm_time * 1000), + "color": int(danmaku.color, 16), + "fontsize": danmaku.font_size, + "pool": pool, + "mode": danmaku.mode, + "plat": 1, + } + return await Api(**api, credential=self.credential).update_data(**data).result + + async def get_danmaku_xml( + self, page_index: Union[int, None] = None, cid: Union[int, None] = None + ) -> str: + """ + 获取所有弹幕的 xml 源文件(非装填) + + Args: + page_index (int, optional) : 分 P 序号. Defaults to 0. + + cid (int | None, optional): cid. Defaults to None. + + Return: + xml 文件源 + """ + if cid is None: + if page_index is None: + raise ArgsException("page_index 和 cid 至少提供一个。") + + cid = await self.__get_cid_by_index(page_index) + url = f"https://comment.bilibili.com/{cid}.xml" + sess = get_session() + config: dict[Any, Any] = {"url": url} + # 代理 + if settings.proxy: + config["proxies"] = {"all://", settings.proxy} + resp = await sess.get(**config) + return resp.content.decode("utf-8") + + async def like_danmaku( + self, + page_index: Union[int, None] = None, + dmid: Union[int, None] = None, + status: Union[bool, None] = True, + cid: Union[int, None] = None, + ) -> dict: + """ + 点赞弹幕。 + + Args: + page_index (int | None, optional) : 分 P 号,从 0 开始。Defaults to None + + dmid (int | None) : 弹幕 ID。 + + status (bool | None, optional): 点赞状态。Defaults to True + + cid (int | None, optional) : 分 P 的 ID。Defaults to None + + Returns: + dict: 调用 API 返回的结果。 + """ + if dmid is None: + raise ArgsException("请提供 dmid 参数") + + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + if cid is None: + if page_index is None: + raise ArgsException("page_index 和 cid 至少提供一个。") + + cid = await self.__get_cid_by_index(page_index) + + api = API["danmaku"]["like_danmaku"] + + data = { + "dmid": dmid, + "oid": cid, + "op": 1 if status else 2, + "platform": "web_player", + } + return await Api(**api, credential=self.credential).update_data(**data).result + + async def get_online(self, cid: Optional[int] = None, page_index: Optional[int] = 0) -> dict: + """ + 获取实时在线人数 + + Returns: + dict: 调用 API 返回的结果。 + """ + api = API["info"]["online"] + params = {"aid": self.get_aid(), "bvid": self.get_bvid(), + "cid": cid if cid is not None else await self.get_cid(page_index=page_index)} + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def operate_danmaku( + self, + page_index: Union[int, None] = None, + dmids: Union[List[int], None] = None, + cid: Union[int, None] = None, + type_: Union[DanmakuOperatorType, None] = None, + ) -> dict: + """ + 操作弹幕 + + Args: + page_index (int | None, optional) : 分 P 号,从 0 开始。Defaults to None + + dmids (List[int] | None) : 弹幕 ID 列表。 + + cid (int | None, optional) : 分 P 的 ID。Defaults to None + + type_ (DanmakuOperatorType | None): 操作类型 + + Returns: + dict: 调用 API 返回的结果。 + """ + + if dmids is None or len(dmids) == 0: + raise ArgsException("请提供 dmid 参数") + + if type_ is None: + raise ArgsException("请提供 type_ 参数") + + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + if cid is None: + if page_index is None: + raise ArgsException("page_index 和 cid 至少提供一个。") + + cid = await self.__get_cid_by_index(page_index) + + api = API["danmaku"]["edit_danmaku"] + + data = { + "type": 1, + "dmids": ",".join(map(lambda x: str(x), dmids)), + "oid": cid, + "state": type_.value, + } + + return await Api(**api, credential=self.credential).update_data(**data).result + + async def like(self, status: bool = True) -> dict: + """ + 点赞视频。 + + Args: + status (bool, optional): 点赞状态。Defaults to True. + + Returns: + dict: 调用 API 返回的结果。 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["operate"]["like"] + data = {"aid": self.get_aid(), "like": 1 if status else 2} + return await Api(**api, credential=self.credential).update_data(**data).result + + async def pay_coin(self, num: int = 1, like: bool = False) -> dict: + """ + 投币。 + + Args: + num (int, optional) : 硬币数量,为 1 ~ 2 个。Defaults to 1. + + like (bool, optional): 是否同时点赞。Defaults to False. + + Returns: + dict: 调用 API 返回的结果。 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + if num not in (1, 2): + raise ArgsException("投币数量只能是 1 ~ 2 个。") + + api = API["operate"]["coin"] + data = { + "aid": self.get_aid(), + "bvid": self.get_bvid(), + "multiply": num, + "like": 1 if like else 0, + } + return await Api(**api, credential=self.credential).update_data(**data).result + + async def share(self) -> int: + """ + 分享视频 + + Returns: + int: 当前分享数 + """ + api = API["operate"]["share"] + data = { + "bvid": self.get_bvid(), + "aid": self.get_aid(), + "csrf": self.credential.bili_jct, + } + return await Api(**api, credential=self.credential).update_data(**data).result + + async def triple(self) -> dict: + """ + 给阿婆主送上一键三连 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["operate"]["yjsl"] + data = {"bvid": self.get_bvid(), "aid": self.get_aid()} + return await Api(**api, credential=self.credential).update_data(**data).result + + async def add_tag(self, name: str) -> dict: + """ + 添加标签。 + + Args: + name (str): 标签名字。 + + Returns: + dict: 调用 API 返回的结果。会返回标签 ID。 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["operate"]["add_tag"] + data = {"aid": self.get_aid(), "bvid": self.get_bvid(), "tag_name": name} + return await Api(**api, credential=self.credential).update_data(**data).result + + async def delete_tag(self, tag_id: int) -> dict: + """ + 删除标签。 + + Args: + tag_id (int): 标签 ID。 + + Returns: + dict: 调用 API 返回的结果。 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["operate"]["del_tag"] + + data = {"tag_id": tag_id, "aid": self.get_aid(), "bvid": self.get_bvid()} + return await Api(**api, credential=self.credential).update_data(**data).result + + async def appeal(self, reason: Any, detail: str): + """ + 投诉稿件 + + Args: + reason (Any): 投诉类型。传入 VideoAppealReasonType 中的项目即可。 + + detail (str): 详情信息。 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["operate"]["appeal"] + data = {"aid": self.get_aid(), "desc": detail} + if isfunction(reason): + reason = reason() + if isinstance(reason, int): + reason = {"tid": reason} + data.update(reason) + # XXX: 暂不支持上传附件 + return await Api(**api, credential=self.credential).update_data(**data).result + + async def set_favorite( + self, add_media_ids: List[int] = [], del_media_ids: List[int] = [] + ) -> dict: + """ + 设置视频收藏状况。 + + Args: + add_media_ids (List[int], optional): 要添加到的收藏夹 ID. Defaults to []. + + del_media_ids (List[int], optional): 要移出的收藏夹 ID. Defaults to []. + + Returns: + dict: 调用 API 返回结果。 + """ + if len(add_media_ids) + len(del_media_ids) == 0: + raise ArgsException("对收藏夹无修改。请至少提供 add_media_ids 和 del_media_ids 中的其中一个。") + + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["operate"]["favorite"] + data = { + "rid": self.get_aid(), + "type": 2, + "add_media_ids": ",".join(map(lambda x: str(x), add_media_ids)), + "del_media_ids": ",".join(map(lambda x: str(x), del_media_ids)), + } + return await Api(**api, credential=self.credential).update_data(**data).result + + async def get_subtitle( + self, + cid: Union[int, None] = None, + ) -> dict: + """ + 获取视频上一次播放的记录,字幕和地区信息。需要分集的 cid, 返回数据中含有json字幕的链接 + + Args: + cid (int | None): 分 P ID,从视频信息中获取 + + Returns: + 调用 API 返回的结果 + """ + if cid is None: + raise ArgsException("需要 cid") + + return (await self.get_player_info(cid=cid)).get("subtitle") + + async def get_player_info( + self, + cid: Union[int, None] = None, + epid: Union[int, None] = None, + ) -> dict: + """ + 获取字幕信息 + + Args: + cid (int | None): 分 P ID,从视频信息中获取 + + epid (int | None): 番剧分集 ID,从番剧信息中获取 + + Returns: + 调用 API 返回的结果 + """ + if cid is None: + raise ArgsException("需要 cid") + api = API["info"]["get_player_info"] + + params = { + "aid": self.get_aid(), + "cid": cid, + } + + if epid: + params["epid"] = epid + + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def submit_subtitle( + self, + lan: str, + data: dict, + submit: bool, + sign: bool, + page_index: Union[int, None] = None, + cid: Union[int, None] = None, + ) -> dict: + """ + 上传字幕 + + 字幕数据 data 参考: + + ```json + { + "font_size": "float: 字体大小,默认 0.4", + "font_color": "str: 字体颜色,默认 \"#FFFFFF\"", + "background_alpha": "float: 背景不透明度,默认 0.5", + "background_color": "str: 背景颜色,默认 \"#9C27B0\"", + "Stroke": "str: 描边,目前作用未知,默认为 \"none\"", + "body": [ + { + "from": "int: 字幕开始时间(秒)", + "to": "int: 字幕结束时间(秒)", + "location": "int: 字幕位置,默认为 2", + "content": "str: 字幕内容" + } + ] + } + ``` + + Args: + lan (str) : 字幕语言代码,参考 https://s1.hdslb.com/bfs/subtitle/subtitle_lan.json + + data (dict) : 字幕数据 + + submit (bool) : 是否提交,不提交为草稿 + + sign (bool) : 是否署名 + + page_index (int | None, optional): 分 P 索引. Defaults to None. + + cid (int | None, optional): 分 P id. Defaults to None. + + Returns: + dict: API 调用返回结果 + + """ + if cid is None: + if page_index is None: + raise ArgsException("page_index 和 cid 至少提供一个。") + + cid = await self.__get_cid_by_index(page_index) + + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["operate"]["submit_subtitle"] + + # lan check,应该是这里面的语言代码 + with open( + os.path.join(os.path.dirname(__file__), "data/subtitle_lan.json"), + encoding="utf-8", + ) as f: + subtitle_lans = json.load(f) + for lan in subtitle_lans: + if lan["lan"] == lan: + break + else: + raise ArgsException("lan 参数错误,请参见 https://s1.hdslb.com/bfs/subtitle/subtitle_lan.json") + + payload = { + "type": 1, + "oid": cid, + "lan": lan, + "data": json.dumps(data), + "submit": submit, + "sign": sign, + "bvid": self.get_bvid(), + } + + return await Api(**api, credential=self.credential).update_data(**payload).result + + async def get_danmaku_snapshot(self) -> dict: + """ + 获取弹幕快照 + + Returns: + 调用 API 返回的结果 + """ + api = API["danmaku"]["snapshot"] + + params = {"aid": self.get_aid()} + + return ( + await Api(**api, credential=self.credential).update_params(**params).result + ) + + async def recall_danmaku( + self, + page_index: Union[int, None] = None, + dmid: int = 0, + cid: Union[int, None] = None, + ) -> dict: + """ + 撤回弹幕 + + Args: + page_index(int | None, optional): 分 P 号 + + dmid(int) : 弹幕 id + + cid(int | None, optional) : 分 P 编码 + Returns: + 调用 API 返回的结果 + """ + if cid is None: + if page_index is None: + raise ArgsException("page_index 和 cid 至少提供一个。") + + cid = await self.__get_cid_by_index(page_index) + + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API["danmaku"]["recall"] + data = {"dmid": dmid, "cid": cid} + + return await Api(**api, credential=self.credential).update_data(**data).result + + async def get_pbp( + self, page_index: Union[int, None] = None, cid: Union[int, None] = None + ) -> dict: + """ + 获取高能进度条 + + Args: + page_index(int | None): 分 P 号 + + cid(int | None) : 分 P 编码 + + Returns: + 调用 API 返回的结果 + """ + if cid is None: + if page_index is None: + raise ArgsException("page_index 和 cid 至少提供一个。") + + cid = await self.__get_cid_by_index(page_index) + + api = API["info"]["pbp"] + + params = {"cid": cid} + + session = get_session() + + return json.loads( + ( + await session.get( + api["url"], params=params, cookies=self.credential.get_cookies() + ) + ).text + ) + + async def add_to_toview(self) -> dict: + """ + 添加视频至稍后再看列表 + + Returns: + 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + api = get_api("toview")["operate"]["add"] + datas = { + "aid": self.get_aid(), + } + return await Api(**api, credential=self.credential).update_data(**datas).result + + async def delete_from_toview(self) -> dict: + """ + 从稍后再看列表删除视频 + + Returns: + 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + api = get_api("toview")["operate"]["del"] + datas = {"viewed": "false", "aid": self.get_aid()} + return await Api(**api, credential=self.credential).update_data(**datas).result + + +class VideoOnlineMonitor(AsyncEvent): + """ + 视频在线人数实时监测。 + + 示例代码: + + ```python + import asyncio + from bilibili_api import video + + # 实例化 + r = video.VideoOnlineMonitor("BV1Bf4y1Q7QP") + + # 装饰器方法注册事件监听器 + @r.on("ONLINE") + async def handler(data): + print(data) + + # 函数方法注册事件监听器 + async def handler2(data): + print(data) + + r.add_event_listener("ONLINE", handler2) + + asyncio.get_event_loop().run_until_complete(r.connect()) + + ``` + + Extends: AsyncEvent + + Events: + ONLINE: 在线人数更新。 CallbackData: dict。 + DANMAKU: 收到实时弹幕。 CallbackData: Danmaku。 + DISCONNECTED: 正常断开连接。 CallbackData: None。 + ERROR: 发生错误。 CallbackData: aiohttp.ClientWebSocketResponse。 + CONNECTED: 成功连接。 CallbackData: None。 + """ + + class Datapack(Enum): + """ + 数据包类型枚举。 + + + CLIENT_VERIFY : 客户端发送验证信息。 + + SERVER_VERIFY : 服务端响应验证信息。 + + CLIENT_HEARTBEAT: 客户端发送心跳包。 + + SERVER_HEARTBEAT: 服务端响应心跳包。 + + DANMAKU : 实时弹幕更新。 + """ + + CLIENT_VERIFY = 0x7 + SERVER_VERIFY = 0x8 + CLIENT_HEARTBEAT = 0x2 + SERVER_HEARTBEAT = 0x3 + DANMAKU = 0x3E8 + + def __init__( + self, + bvid: Union[str, None] = None, + aid: Union[int, None] = None, + page_index: int = 0, + credential: Union[Credential, None] = None, + debug: bool = False, + ): + """ + Args: + bvid (str | None, optional) : BVID. Defaults to None. + + aid (int | None, optional) : AID. Defaults to None. + + page_index (int, optional) : 分 P 序号. Defaults to 0. + + credential (Credential | None, optional): Credential 类. Defaults to None. + + debug (bool, optional) : 调试模式,将输出更详细信息. Defaults to False. + """ + super().__init__() + self.credential = credential + self.__video = Video(bvid, aid, credential=credential) + + # 智能选择在 log 中展示的 ID。 + id_showed = None + if bvid is not None: + id_showed = bvid + else: + id_showed = aid + + # logger 初始化 + self.logger = logging.getLogger(f"VideoOnlineMonitor-{id_showed}") + if not self.logger.handlers: + handler = logging.StreamHandler() + handler.setFormatter( + logging.Formatter( + "[" + str(id_showed) + "][%(asctime)s][%(levelname)s] %(message)s" + ) + ) + self.logger.addHandler(handler) + self.logger.setLevel(logging.INFO if not debug else logging.DEBUG) + + self.__page_index = page_index + self.__tasks = [] + + async def connect(self): + """ + 连接服务器 + """ + await self.__main() + + async def disconnect(self): + """ + 断开服务器 + """ + self.logger.info("主动断开连接。") + self.dispatch("DISCONNECTED") + await self.__cancel_all_tasks() + await self.__ws.close() + + async def __main(self): + """ + 入口。 + """ + # 获取分 P id + pages = await self.__video.get_pages() + if self.__page_index >= len(pages): + raise ArgsException("不存在该分 P。") + cid = pages[self.__page_index]["cid"] + + # 获取服务器信息 + self.logger.debug(f"准备连接:{self.__video.get_bvid()}") + self.logger.debug(f"获取服务器信息中...") + + api = API["info"]["video_online_broadcast_servers"] + resp = await Api(**api, credential=self.credential).result + + uri = f"wss://{resp['domain']}:{resp['wss_port']}/sub" + self.__heartbeat_interval = resp["heartbeat"] + self.logger.debug(f"服务器信息获取成功,URI:{uri}") + + # 连接服务器 + self.logger.debug("准备连接服务器...") + session = get_aiohttp_session() + async with session.ws_connect(uri) as ws: + self.__ws = ws + + # 发送认证信息 + self.logger.debug("服务器连接成功,准备发送认证信息...") + verify_info = { + "room_id": f"video://{self.__video.get_aid()}/{cid}", + "platform": "web", + "accepts": [1000, 1015], + } + verify_info = json.dumps(verify_info, separators=(",", ":")) + await ws.send_bytes( + self.__pack( + VideoOnlineMonitor.Datapack.CLIENT_VERIFY, 1, verify_info.encode() + ) + ) + + # 循环接收消息 + async for msg in ws: + if msg.type == aiohttp.WSMsgType.BINARY: + data = self.__unpack(msg.data) + self.logger.debug(f"收到消息:{data}") + await self.__handle_data(data) # type: ignore + + elif msg.type == aiohttp.WSMsgType.ERROR: + self.logger.warning("连接被异常断开") + await self.__cancel_all_tasks() + self.dispatch("ERROR", msg) + break + + async def __handle_data(self, data: List[dict]): + """ + 处理数据。 + + Args: + data (List[dict]): 收到的数据(已解析好)。 + """ + for d in data: + if d["type"] == VideoOnlineMonitor.Datapack.SERVER_VERIFY.value: + # 服务器认证反馈。 + if d["data"]["code"] == 0: + # 创建心跳 Task + heartbeat = asyncio.create_task(self.__heartbeat_task()) + self.__tasks.append(heartbeat) + + self.logger.info("连接服务器并验证成功") + + elif d["type"] == VideoOnlineMonitor.Datapack.SERVER_HEARTBEAT.value: + # 心跳包反馈,同时包含在线人数。 + self.logger.debug(f'收到服务器心跳包反馈,编号:{d["number"]}') + self.logger.info(f'实时观看人数:{d["data"]["data"]["room"]["online"]}') + self.dispatch("ONLINE", d["data"]) + + elif d["type"] == VideoOnlineMonitor.Datapack.DANMAKU.value: + # 实时弹幕。 + info = d["data"][0].split(",") + text = d["data"][1] + if info[5] == "0": + is_sub = False + else: + is_sub = True + dm = Danmaku( + dm_time=float(info[0]), + send_time=int(info[4]), + crc32_id=info[6], + color=info[3], + mode=info[1], + font_size=info[2], + is_sub=is_sub, + text=text, + ) + self.logger.info(f"收到实时弹幕:{dm.text}") + self.dispatch("DANMAKU", dm) + + else: + # 未知类型数据包 + self.logger.warning("收到未知的数据包类型,无法解析:" + json.dumps(d)) + + async def __heartbeat_task(self): + """ + 心跳 Task。 + """ + index = 2 + while True: + self.logger.debug(f"发送心跳包,编号:{index}") + await self.__ws.send_bytes( + self.__pack( + VideoOnlineMonitor.Datapack.CLIENT_HEARTBEAT, + index, + b"[object Object]", + ) + ) + index += 1 + await asyncio.sleep(self.__heartbeat_interval) + + async def __cancel_all_tasks(self): + """ + 取消所有 Task。 + """ + for task in self.__tasks: + task.cancel() + + @staticmethod + def __pack(data_type: Datapack, number: int, data: bytes): + """ + 打包数据。 + + # 数据包格式: + + 16B 头部: + + | offset(bytes) | length(bytes) | type | description | + | ------------- | ------------- | ---- | ------------------- | + | 0 | 4 | I | 数据包长度 | + | 4 | 4 | I | 固定 0x00120001 | + | 8 | 4 | I | 数据包类型 | + | 12 | 4 | I | 递增数据包编号 | + | 16 | 2 | H | 固定 0x0000 | + + 之后是有效载荷。 + + # 数据包类型表: + + + 0x7 客户端发送认证信息 + + 0x8 服务端回应认证结果 + + 0x2 客户端发送心跳包,有效载荷:'[object Object]' + + 0x3 服务端回应心跳包,会带上在线人数等信息,返回 JSON + + 0x3e8 实时弹幕更新,返回列表,[0]弹幕信息,[1]弹幕文本 + + Args: + data_type (VideoOnlineMonitor.DataType): 数据包类型枚举。 + + Returns: + bytes: 打包好的数据。 + """ + packed_data = bytearray() + packed_data += struct.pack(">I", 0x00120001) + packed_data += struct.pack(">I", data_type.value) + packed_data += struct.pack(">I", number) + packed_data += struct.pack(">H", 0) + packed_data += data + packed_data = struct.pack(">I", len(packed_data) + 4) + packed_data + return bytes(packed_data) + + @staticmethod + def __unpack(data: bytes): + """ + 解包数据。 + + Args: + data (bytes): 原始数据。 + + Returns: + tuple(dict): 解包后的数据。 + """ + offset = 0 + real_data = [] + while offset < len(data): + region_header = struct.unpack(">IIII", data[:16]) + region_data = data[offset: offset + region_header[0]] + real_data.append( + { + "type": region_header[2], + "number": region_header[3], + "data": json.loads( + region_data[offset + 18: offset + 18 + (region_header[0] - 16)] + ), + } + ) + offset += region_header[0] + return tuple(real_data) + + +class VideoQuality(Enum): + """ + 视频的视频流分辨率枚举 + + - _360P: 流畅 360P + - _480P: 清晰 480P + - _720P: 高清 720P60 + - _1080P: 高清 1080P + - _1080P_PLUS: 高清 1080P 高码率 + - _1080P_60: 高清 1080P 60 帧码率 + - _4K: 超清 4K + - HDR: 真彩 HDR + - DOLBY: 杜比视界 + - _8K: 超高清 8K + """ + + _360P = 16 + _480P = 32 + _720P = 64 + _1080P = 80 + _1080P_PLUS = 112 + _1080P_60 = 116 + _4K = 120 + HDR = 125 + DOLBY = 126 + _8K = 127 + + +class VideoCodecs(Enum): + """ + 视频的视频流编码枚举 + + - HEV: HEVC(H.265) + - AVC: AVC(H.264) + - AV1: AV1 + """ + + HEV = "hev" + AVC = "avc" + AV1 = "av01" + + +class AudioQuality(Enum): + """ + 视频的音频流清晰度枚举 + + - _64K: 64K + - _132K: 132K + - _192K: 192K + - HI_RES: Hi-Res 无损 + - DOLBY: 杜比全景声 + """ + + _64K = 30216 + _132K = 30232 + DOLBY = 30250 + HI_RES = 30251 + _192K = 30280 + + +@dataclass +class VideoStreamDownloadURL: + """ + (@dataclass) + + 视频流 URL 类 + + Attributes: + url (str) : 视频流 url + video_quality (VideoQuality): 视频流清晰度 + video_codecs (VideoCodecs) : 视频流编码 + """ + + url: str + video_quality: VideoQuality + video_codecs: VideoCodecs + + +@dataclass +class AudioStreamDownloadURL: + """ + (@dataclass) + + 音频流 URL 类 + + Attributes: + url (str) : 音频流 url + audio_quality (AudioQuality): 音频流清晰度 + """ + + url: str + audio_quality: AudioQuality + + +@dataclass +class FLVStreamDownloadURL: + """ + (@dataclass) + + FLV 视频流 + + Attributes: + url (str): FLV 流 url + """ + + url: str + + +@dataclass +class HTML5MP4DownloadURL: + """ + (@dataclass) + + 可供 HTML5 播放的 mp4 视频流 + + Attributes: + url (str): HTML5 mp4 视频流 + """ + + url: str + + +@dataclass +class EpisodeTryMP4DownloadURL: + """ + (@dataclass) + + 番剧/课程试看的 mp4 播放流 + + Attributes: + url (str): 番剧试看的 mp4 播放流 + """ + + url: str + + +class VideoDownloadURLDataDetecter: + """ + `Video.get_download_url` 返回结果解析类。 + + 在调用 `Video.get_download_url` 之后可以将代入 `VideoDownloadURLDataDetecter`,此类将一键解析。 + + 目前支持: + - 视频清晰度: 360P, 480P, 720P, 1080P, 1080P 高码率, 1080P 60 帧, 4K, HDR, 杜比视界, 8K + - 视频编码: HEVC(H.265), AVC(H.264), AV1 + - 音频清晰度: 64K, 132K, Hi-Res 无损音效, 杜比全景声, 192K + - FLV 视频流 + - 番剧/课程试看视频流 + """ + + def __init__(self, data: dict): + """ + Args: + data (dict): `Video.get_download_url` 返回的结果 + """ + self.__data = data + + def check_video_and_audio_stream(self) -> bool: + """ + 判断是否为音视频分离流 + + Returns: + bool: 是否为音视频分离流 + """ + if "dash" in self.__data.keys(): + return True + return False + + def check_flv_stream(self) -> bool: + """ + 判断是否为 FLV 视频流 + + Returns: + bool: 是否为 FLV 视频流 + """ + if "durl" in self.__data.keys(): + if self.__data["format"].startswith("flv"): + return True + return False + + def check_html5_mp4_stream(self) -> bool: + """ + 判断是否为 HTML5 可播放的 mp4 视频流 + + Returns: + bool: 是否为 HTML5 可播放的 mp4 视频流 + """ + if "durl" in self.__data.keys(): + if self.__data["format"].startswith("mp4"): + if self.__data.get("is_html5") == True: + return True + return False + + def check_episode_try_mp4_stream(self): + """ + 判断是否为番剧/课程试看的 mp4 视频流 + + Returns: + bool: 是否为番剧试看的 mp4 视频流 + """ + if "durl" in self.__data.keys(): + if self.__data["format"].startswith("mp4"): + if self.__data.get("is_html5") != True: + return True + return False + + def detect_all(self): + """ + 解析并返回所有数据 + + Returns: + List[VideoStreamDownloadURL | AudioStreamDownloadURL | FLVStreamDownloadURL | HTML5MP4DownloadURL | EpisodeTryMP4DownloadURL]: 所有的视频/音频流 + """ + return self.detect() + + def detect( + self, + video_max_quality: VideoQuality = VideoQuality._8K, + audio_max_quality: AudioQuality = AudioQuality._192K, + video_min_quality: VideoQuality = VideoQuality._360P, + audio_min_quality: AudioQuality = AudioQuality._64K, + video_accepted_qualities: List[VideoQuality] = [ + item + for _, item in VideoQuality.__dict__.items() + if isinstance(item, VideoQuality) + ], + audio_accepted_qualities: List[AudioQuality] = [ + item + for _, item in AudioQuality.__dict__.items() + if isinstance(item, AudioQuality) + ], + codecs: List[VideoCodecs] = [VideoCodecs.AV1, VideoCodecs.AVC, VideoCodecs.HEV], + no_dolby_video: bool = False, + no_dolby_audio: bool = False, + no_hdr: bool = False, + no_hires: bool = False, + ) -> List[ + Union[ + VideoStreamDownloadURL, + AudioStreamDownloadURL, + FLVStreamDownloadURL, + HTML5MP4DownloadURL, + EpisodeTryMP4DownloadURL, + ] + ]: + """ + 解析数据 + + Args: + **以下参数仅能在音视频流分离的情况下产生作用,flv / mp4 试看流 / html5 mp4 流下以下参数均没有作用** + + video_max_quality (VideoQuality, optional) : 设置提取的视频流清晰度最大值,设置此参数绝对不会禁止 HDR/杜比. Defaults to VideoQuality._8K. + + audio_max_quality (AudioQuality, optional) : 设置提取的音频流清晰度最大值. 设置此参数绝对不会禁止 Hi-Res/杜比. Defaults to AudioQuality._192K. + + video_min_quality (VideoQuality, optional) : 设置提取的视频流清晰度最小值,设置此参数绝对不会禁止 HDR/杜比. Defaults to VideoQuality._360P. + + audio_min_quality (AudioQuality, optional) : 设置提取的音频流清晰度最小值. 设置此参数绝对不会禁止 Hi-Res/杜比. Defaults to AudioQuality._64K. + + video_accepted_qualities(List[VideoQuality], optional): 设置允许的所有视频流清晰度. Defaults to ALL. + + audio_accepted_qualities(List[AudioQuality], optional): 设置允许的所有音频清晰度. Defaults to ALL. + + codecs (List[VideoCodecs], optional) : 设置所有允许提取出来的视频编码. 此项不会忽略 HDR/杜比. Defaults to ALL codecs. + + no_dolby_video (bool, optional) : 是否禁止提取杜比视界视频流. Defaults to False. + + no_dolby_audio (bool, optional) : 是否禁止提取杜比全景声音频流. Defaults to False. + + no_hdr (bool, optional) : 是否禁止提取 HDR 视频流. Defaults to False. + + no_hires (bool, optional) : 是否禁止提取 Hi-Res 音频流. Defaults to False. + + Returns: + List[VideoStreamDownloadURL | AudioStreamDownloadURL | FLVStreamDownloadURL | HTML5MP4DownloadURL | EpisodeTryMP4DownloadURL]: 提取出来的视频/音频流 + """ + if "durl" in self.__data.keys(): + if self.__data["format"].startswith("flv"): + # FLV 视频流 + return [FLVStreamDownloadURL(url=self.__data["durl"][0]["url"])] + else: + if self.check_html5_mp4_stream(): + # HTML5 MP4 视频流 + return [HTML5MP4DownloadURL(url=self.__data["durl"][0]["url"])] + else: + # 会员番剧试看 MP4 流 + return [EpisodeTryMP4DownloadURL(url=self.__data["durl"][0]["url"])] + else: + # 正常情况 + streams = [] + videos_data = self.__data["dash"]["video"] + audios_data = self.__data["dash"]["audio"] + flac_data = self.__data["dash"]["flac"] + dolby_data = self.__data["dash"]["dolby"] + for video_data in videos_data: + video_stream_url = video_data["baseUrl"] + video_stream_quality = VideoQuality(video_data["id"]) + if video_stream_quality == VideoQuality.HDR and no_hdr: + continue + if video_stream_quality == VideoQuality.DOLBY and no_dolby_video: + continue + if ( + video_stream_quality != VideoQuality.DOLBY + and video_stream_quality != VideoQuality.HDR + and video_stream_quality.value > video_max_quality.value + ): + continue + if ( + video_stream_quality != VideoQuality.DOLBY + and video_stream_quality != VideoQuality.HDR + and video_stream_quality.value < video_min_quality.value + ): + continue + if ( + video_stream_quality != VideoQuality.DOLBY + and video_stream_quality != VideoQuality.HDR + and (not video_stream_quality in video_accepted_qualities) + ): + continue + video_stream_codecs = None + for val in VideoCodecs: + if val.value in video_data["codecs"]: + video_stream_codecs = val + if (not video_stream_codecs in codecs) and ( + video_stream_codecs != None + ): + continue + video_stream = VideoStreamDownloadURL( + url=video_stream_url, + video_quality=video_stream_quality, + video_codecs=video_stream_codecs, # type: ignore + ) + streams.append(video_stream) + if audios_data: + for audio_data in audios_data: + audio_stream_url = audio_data["baseUrl"] + audio_stream_quality = AudioQuality(audio_data["id"]) + if audio_stream_quality.value > audio_max_quality.value: + continue + if audio_stream_quality.value < audio_min_quality.value: + continue + if not audio_stream_quality in audio_accepted_qualities: + continue + audio_stream = AudioStreamDownloadURL( + url=audio_stream_url, audio_quality=audio_stream_quality + ) + streams.append(audio_stream) + if flac_data and (not no_hires): + if flac_data["audio"]: + flac_stream_url = flac_data["audio"]["baseUrl"] + flac_stream_quality = AudioQuality(flac_data["audio"]["id"]) + flac_stream = AudioStreamDownloadURL( + url=flac_stream_url, audio_quality=flac_stream_quality + ) + streams.append(flac_stream) + if dolby_data and (not no_dolby_audio): + if dolby_data["audio"]: + dolby_stream_data = dolby_data["audio"][0] + dolby_stream_url = dolby_stream_data["baseUrl"] + dolby_stream_quality = AudioQuality(dolby_stream_data["id"]) + dolby_stream = AudioStreamDownloadURL( + url=dolby_stream_url, audio_quality=dolby_stream_quality + ) + streams.append(dolby_stream) + return streams + + def detect_best_streams( + self, + video_max_quality: VideoQuality = VideoQuality._8K, + audio_max_quality: AudioQuality = AudioQuality._192K, + video_min_quality: VideoQuality = VideoQuality._360P, + audio_min_quality: AudioQuality = AudioQuality._64K, + video_accepted_qualities: List[VideoQuality] = [ + item + for _, item in VideoQuality.__dict__.items() + if isinstance(item, VideoQuality) + ], + audio_accepted_qualities: List[AudioQuality] = [ + item + for _, item in AudioQuality.__dict__.items() + if isinstance(item, AudioQuality) + ], + codecs: List[VideoCodecs] = [VideoCodecs.AV1, VideoCodecs.AVC, VideoCodecs.HEV], + no_dolby_video: bool = False, + no_dolby_audio: bool = False, + no_hdr: bool = False, + no_hires: bool = False, + ) -> Union[ + List[FLVStreamDownloadURL], + List[HTML5MP4DownloadURL], + List[EpisodeTryMP4DownloadURL], + List[Union[VideoStreamDownloadURL, AudioStreamDownloadURL, None]], + ]: + """ + 提取出分辨率、音质等信息最好的音视频流。 + + Args: + **以下参数仅能在音视频流分离的情况下产生作用,flv / mp4 试看流 / html5 mp4 流下以下参数均没有作用** + + video_max_quality (VideoQuality) : 设置提取的视频流清晰度最大值,设置此参数绝对不会禁止 HDR/杜比. Defaults to VideoQuality._8K. + + audio_max_quality (AudioQuality) : 设置提取的音频流清晰度最大值. 设置此参数绝对不会禁止 Hi-Res/杜比. Defaults to AudioQuality._192K. + + video_min_quality (VideoQuality, optional) : 设置提取的视频流清晰度最小值,设置此参数绝对不会禁止 HDR/杜比. Defaults to VideoQuality._360P. + + audio_min_quality (AudioQuality, optional) : 设置提取的音频流清晰度最小值. 设置此参数绝对不会禁止 Hi-Res/杜比. Defaults to AudioQuality._64K. + + video_accepted_qualities(List[VideoQuality], optional): 设置允许的所有视频流清晰度. Defaults to ALL. + + audio_accepted_qualities(List[AudioQuality], optional): 设置允许的所有音频清晰度. Defaults to ALL. + + codecs (List[VideoCodecs]) : 设置所有允许提取出来的视频编码. 在数组中越靠前的编码选择优先级越高. 此项不会忽略 HDR/杜比. Defaults to [VideoCodecs.AV1, VideoCodecs.AVC, VideoCodecs.HEV]. + + no_dolby_video (bool) : 是否禁止提取杜比视界视频流. Defaults to False. + + no_dolby_audio (bool) : 是否禁止提取杜比全景声音频流. Defaults to False. + + no_hdr (bool) : 是否禁止提取 HDR 视频流. Defaults to False. + + no_hires (bool) : 是否禁止提取 Hi-Res 音频流. Defaults to False. + + Returns: + List[VideoStreamDownloadURL | AudioStreamDownloadURL | FLVStreamDownloadURL | HTML5MP4DownloadURL | None]: FLV 视频流 / HTML5 MP4 视频流 / 番剧或课程试看 MP4 视频流返回 `[FLVStreamDownloadURL | HTML5MP4StreamDownloadURL | EpisodeTryMP4DownloadURL]`, 否则为 `[VideoStreamDownloadURL, AudioStreamDownloadURL]`, 如果未匹配上任何合适的流则对应的位置位 `None` + """ + if self.check_flv_stream(): + return self.detect_all() # type: ignore + elif self.check_html5_mp4_stream(): + return self.detect_all() # type: ignore + elif self.check_episode_try_mp4_stream(): + return self.detect_all() # type: ignore + else: + data = self.detect( + video_max_quality=video_max_quality, + audio_max_quality=audio_max_quality, + video_min_quality=video_min_quality, + audio_min_quality=audio_min_quality, + video_accepted_qualities=video_accepted_qualities, + audio_accepted_qualities=audio_accepted_qualities, + codecs=codecs, + ) + video_streams = [] + audio_streams = [] + for stream in data: + if isinstance(stream, VideoStreamDownloadURL): + video_streams.append(stream) + if isinstance(stream, AudioStreamDownloadURL): + audio_streams.append(stream) + + def video_stream_cmp( + s1: VideoStreamDownloadURL, s2: VideoStreamDownloadURL + ): + # 杜比/HDR 优先 + if s1.video_quality == VideoQuality.DOLBY and (not no_dolby_video): + return 1 + elif s2.video_quality == VideoQuality.DOLBY and (not no_dolby_video): + return -1 + elif s1.video_quality == VideoQuality.HDR and (not no_hdr): + return 1 + elif s2.video_quality == VideoQuality.HDR and (not no_hdr): + return -1 + if s1.video_quality.value != s2.video_quality.value: + return s1.video_quality.value - s2.video_quality.value + # Detect the high quality stream to the end. + elif s1.video_codecs.value != s2.video_codecs.value: + return codecs.index(s2.video_codecs) - codecs.index(s1.video_codecs) + return -1 + + def audio_stream_cmp( + s1: AudioStreamDownloadURL, s2: AudioStreamDownloadURL + ): + # 杜比/Hi-Res 优先 + if s1.audio_quality == AudioQuality.DOLBY and (not no_dolby_audio): + return 1 + if s2.audio_quality == AudioQuality.DOLBY and (not no_dolby_audio): + return -1 + if s1.audio_quality == AudioQuality.HI_RES and (not no_hires): + return 1 + if s2.audio_quality == AudioQuality.HI_RES and (not no_hires): + return -1 + return s1.audio_quality.value - s2.audio_quality.value + + video_streams.sort(key=cmp_to_key(video_stream_cmp), reverse=True) + audio_streams.sort(key=cmp_to_key(audio_stream_cmp), reverse=True) + if len(video_streams) == 0: + video_streams = [None] + if len(audio_streams) == 0: + audio_streams = [None] + return [video_streams[0], audio_streams[0]] diff --git a/bilibili_api/video_tag.py b/bilibili_api/video_tag.py new file mode 100644 index 0000000000000000000000000000000000000000..e951ae47b91c0d5a96533d00523090018c68d394 --- /dev/null +++ b/bilibili_api/video_tag.py @@ -0,0 +1,130 @@ +""" +bilibili_api.video_tag + +视频标签相关,部分的标签的 id 与同名的频道的 id 一模一样。 +""" + +from typing import Optional + +import httpx + +from .errors import * +from .utils.utils import get_api +from .utils.credential import Credential +from .utils.network import Api + +API = get_api("video_tag") +API_video = get_api("video") + + +class Tag: + """ + 标签类 + """ + + def __init__( + self, + tag_name: Optional[str] = None, + tag_id: Optional[int] = None, + credential: Optional[Credential] = None, + ): + """ + Args: + tag_name (str | None): 标签名. Defaults to None. + + tag_id (int | None): 标签 id. Defaults to None. + + credential (Credential): 凭据类. Defaults to None. + + 注意:tag_name 和 tag_id 任选一个传入即可。tag_id 优先使用。 + """ + if tag_id == None: + if tag_name == None: + raise ArgsException("tag_name 和 tag_id 需要提供一个。") + self.__tag_id = self.__get_tag_info_sync(tag_name)["tag_id"] + else: + self.__tag_id = tag_id + credential = credential if credential else Credential() + self.credential = credential + + def get_tag_id(self) -> int: + return self.__tag_id + + def __get_tag_info_sync(self, tag_name: str) -> dict: + api = API["info"]["tag_info"] + params = {"tag_name": tag_name} + return Api(**api).update_params(**params).result_sync + + async def get_tag_info(self) -> dict: + """ + 获取标签信息。 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["tag_info"] + params = {"tag_id": self.get_tag_id()} + return await Api(**api).update_params(**params).result + + async def get_similar_tags(self) -> dict: + """ + 获取相关的标签 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["get_similar"] + params = {"tag_id": self.get_tag_id()} + return await Api(**api).update_params(**params).result + + # async def get_cards(self) -> dict: + # """ + # 获取标签下的视频/动态 + + # Returns: + # dict: 调用 API 返回的结果 + # """ + # api = API["info"]["get_list"] + # params = {"topic_id": self.get_tag_id()} + # return await Api(**api).update_params(**params).result + + async def get_history_cards(self, offset_dynamic_id: int) -> dict: + """ + 获取标签下,指定dynamic_id的视频的后一个视频/动态作为起始的视频/动态 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["get_history_list"] + params = {"topic_id": self.get_tag_id(), "offset_dynamic_id": offset_dynamic_id} + return await Api(**api).update_params(**params).result + + async def subscribe_tag(self) -> dict: + """ + 关注标签。 + + Returns: + dict: 调用 API 返回的结果。 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API_video["operate"]["subscribe_tag"] + + data = {"tag_id": self.__tag_id} + return await Api(**api, credential=self.credential).update_data(**data).result + + async def unsubscribe_tag(self) -> dict: + """ + 取关标签。 + + Returns: + dict: 调用 API 返回的结果。 + """ + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + + api = API_video["operate"]["unsubscribe_tag"] + + data = {"tag_id": self.__tag_id} + return await Api(**api, credential=self.credential).update_data(**data).result diff --git a/bilibili_api/video_uploader.py b/bilibili_api/video_uploader.py new file mode 100644 index 0000000000000000000000000000000000000000..1c099963802cb0c3c30bc3eac88a68b190c0292f --- /dev/null +++ b/bilibili_api/video_uploader.py @@ -0,0 +1,1596 @@ +""" +bilibili_api.video_uploader + +视频上传 +""" +import os +import json +import time +import base64 +import re +import asyncio +import httpx +from enum import Enum +from typing import List, Union, Optional +from copy import copy, deepcopy +from asyncio.tasks import Task, create_task +from asyncio.exceptions import CancelledError +from datetime import datetime + +from .video import Video +from .topic import Topic +from .utils.utils import get_api +from .utils.picture import Picture +from .utils.AsyncEvent import AsyncEvent +from .utils.credential import Credential +from .utils.aid_bvid_transformer import bvid2aid +from .exceptions.ApiException import ApiException +from .utils.network import Api, get_session +from .exceptions.NetworkException import NetworkException +from .exceptions.ResponseCodeException import ResponseCodeException + +_API = get_api("video_uploader") + + +async def upload_cover(cover: Picture, credential: Credential) -> str: + """ + 上传封面 + + Returns: + str: 封面 URL + """ + credential.raise_for_no_bili_jct() + api = _API["cover_up"] + pic = cover if isinstance(cover, Picture) else Picture().from_file(cover) + cover = pic.convert_format("png") + data = { + "cover": f'data:image/png;base64,{base64.b64encode(pic.content).decode("utf-8")}' + } + return (await Api(**api, credential=credential).update_data(**data).result)["url"] + + +class Lines(Enum): + """ + 可选线路 + + bupfetch 模式下 kodo 目前弃用 `{'error': 'no such bucket'}` + + + BDA2:百度 + + QN:七牛 + + WS:网宿 + + BLDSA:bldsa + """ + + BDA2 = "bda2" + QN = "qn" + WS = "ws" + BLDSA = "bldsa" + + +with open( + os.path.join(os.path.dirname(__file__), "data/video_uploader_lines.json"), + encoding="utf8", +) as f: + LINES_INFO = json.loads(f.read()) + + +async def _probe() -> dict: + """ + 测试所有线路 + + 测速网页 https://member.bilibili.com/preupload?r=ping + """ + # api = _API["probe"] + # info = await Api(**api).update_params(r="probe").result # 不实时获取线路直接用 LINES_INFO + min_cost, fastest_line = 30, None + for line in LINES_INFO.values(): + start = time.perf_counter() + data = bytes(int(1024 * 0.1 * 1024)) # post 0.1MB + httpx.post(f'https:{line["probe_url"]}', data=data, timeout=30) + cost_time = time.perf_counter() - start + if cost_time < min_cost: + min_cost, fastest_line = cost_time, line + return fastest_line + + +async def _choose_line(line: Lines) -> dict: + """ + 选择线路,不存在则直接测速自动选择 + """ + if isinstance(line, Lines): + line_info = LINES_INFO.get(line.value) + if line_info is not None: + return line_info + return await _probe() + + +LINES_INFO = { + "bda2": { + "os": "upos", + "upcdn": "bda2", + "probe_version": 20221109, + "query": "probe_version=20221109&upcdn=bda2", + "probe_url": "//upos-cs-upcdnbda2.bilivideo.com/OK", + }, + "bldsa": { + "os": "upos", + "upcdn": "bldsa", + "probe_version": 20221109, + "query": "upcdn=bldsa&probe_version=20221109", + "probe_url": "//upos-cs-upcdnbldsa.bilivideo.com/OK", + }, + "qn": { + "os": "upos", + "upcdn": "qn", + "probe_version": 20221109, + "query": "probe_version=20221109&upcdn=qn", + "probe_url": "//upos-cs-upcdnqn.bilivideo.com/OK", + }, + "ws": { + "os": "upos", + "upcdn": "ws", + "probe_version": 20221109, + "query": "upcdn=ws&probe_version=20221109", + "probe_url": "//upos-cs-upcdnws.bilivideo.com/OK", + }, +} + + +async def _probe() -> dict: + """ + 测试所有线路 + + 测速网页 https://member.bilibili.com/preupload?r=ping + """ + # api = _API["probe"] + # info = await Api(**api).update_params(r="probe").result # 不实时获取线路直接用 LINES_INFO + min_cost, fastest_line = 30, None + for line in LINES_INFO.values(): + start = time.perf_counter() + data = bytes(int(1024 * 0.1 * 1024)) # post 0.1MB + httpx.post(f'https:{line["probe_url"]}', data=data, timeout=30) + cost_time = time.perf_counter() - start + if cost_time < min_cost: + min_cost, fastest_line = cost_time, line + return fastest_line + + +async def _choose_line(line: Lines) -> dict: + """ + 选择线路,不存在则直接测速自动选择 + """ + if isinstance(line, Lines): + line_info = LINES_INFO.get(line.value) + if line_info is not None: + return line_info + return await _probe() + + +class VideoUploaderPage: + """ + 分 P 对象 + """ + + def __init__(self, path: str, title: str, description: str = ""): + """ + Args: + path (str): 视频文件路径 + title (str) : 视频标题 + description (str, optional) : 视频简介. Defaults to "". + """ + self.path = path + self.title: str = title + self.description: str = description + + self.cached_size: Union[int, None] = None + + def get_size(self) -> int: + """ + 获取文件大小 + + Returns: + int: 文件大小 + """ + if self.cached_size is not None: + return self.cached_size + + size: int = 0 + stream = open(self.path, "rb") + while True: + s: bytes = stream.read(1024) + + if not s: + break + + size += len(s) + + stream.close() + + self.cached_size = size + return size + + +class VideoUploaderEvents(Enum): + """ + 上传事件枚举 + + Events: + + PRE_PAGE 上传分 P 前 + + PREUPLOAD 获取上传信息 + + PREUPLOAD_FAILED 获取上传信息失败 + + PRE_CHUNK 上传分块前 + + AFTER_CHUNK 上传分块后 + + CHUNK_FAILED 区块上传失败 + + PRE_PAGE_SUBMIT 提交分 P 前 + + PAGE_SUBMIT_FAILED 提交分 P 失败 + + AFTER_PAGE_SUBMIT 提交分 P 后 + + AFTER_PAGE 上传分 P 后 + + PRE_COVER 上传封面前 + + AFTER_COVER 上传封面后 + + COVER_FAILED 上传封面失败 + + PRE_SUBMIT 提交视频前 + + SUBMIT_FAILED 提交视频失败 + + AFTER_SUBMIT 提交视频后 + + COMPLETED 完成上传 + + ABORTED 用户中止 + + FAILED 上传失败 + """ + + PREUPLOAD = "PREUPLOAD" + PREUPLOAD_FAILED = "PREUPLOAD_FAILED" + PRE_PAGE = "PRE_PAGE" + + PRE_CHUNK = "PRE_CHUNK" + AFTER_CHUNK = "AFTER_CHUNK" + CHUNK_FAILED = "CHUNK_FAILED" + + PRE_PAGE_SUBMIT = "PRE_PAGE_SUBMIT" + PAGE_SUBMIT_FAILED = "PAGE_SUBMIT_FAILED" + AFTER_PAGE_SUBMIT = "AFTER_PAGE_SUBMIT" + + AFTER_PAGE = "AFTER_PAGE" + + PRE_COVER = "PRE_COVER" + AFTER_COVER = "AFTER_COVER" + COVER_FAILED = "COVER_FAILED" + + PRE_SUBMIT = "PRE_SUBMIT" + SUBMIT_FAILED = "SUBMIT_FAILED" + AFTER_SUBMIT = "AFTER_SUBMIT" + + COMPLETED = "COMPLETE" + ABORTED = "ABORTED" + FAILED = "FAILED" + + +async def get_available_topics(tid: int, credential: Credential) -> List[dict]: + """ + 获取可用 topic 列表 + """ + credential.raise_for_no_sessdata() + api = _API["available_topics"] + params = {"type_id": tid, "pn": 0, "ps": 200} # 一次性获取完 + return (await Api(**api, credential=credential).update_params(**params).result)[ + "topics" + ] + + +class VideoPorderType: + """ + 视频商业类型 + + + FIREWORK: 花火 + + OTHER: 其他 + """ + + FIREWORK = {"flow_id": 1} + OTHER = { + "flow_id": 1, + "industry_id": None, + "official": None, + "brand_name": None, + "show_type": [], + } + + +class VideoPorderIndustry(Enum): + """ + 商单行业 + + + MOBILE_GAME: 手游 + + CONSOLE_GAME: 主机游戏 + + WEB_GAME: 网页游戏 + + PC_GAME: PC单机游戏 + + PC_NETWORK_GAME: PC网络游戏 + + SOFTWARE_APPLICATION: 软件应用 + + DAILY_NECESSITIES_AND_COSMETICS: 日用品化妆品 + + CLOTHING_SHOES_AND_HATS: 服装鞋帽 + + LUGGAGE_AND_ACCESSORIES: 箱包饰品 + + FOOD_AND_BEVERAGE: 食品饮料 + + PUBLISHING_AND_MEDIA: 出版传媒 + + COMPUTER_HARDWARE: 电脑硬件 + + OTHER: 其他 + + MEDICAL: 医疗类 + + FINANCE: 金融 + """ + + MOBILE_GAME = 1 + CONSOLE_GAME = 20 + WEB_GAME = 21 + PC_GAME = 22 + PC_NETWORK_GAME = 23 + SOFTWARE_APPLICATION = 2 + DAILY_NECESSITIES_AND_COSMETICS = 3 + CLOTHING_SHOES_AND_HATS = 4 + LUGGAGE_AND_ACCESSORIES = 5 + FOOD_AND_BEVERAGE = 6 + PUBLISHING_AND_MEDIA = 7 + COMPUTER_HARDWARE = 8 + OTHER = 9 + MEDICAL = 213 + FINANCE = 214 + + +class VideoPorderShowType(Enum): + """ + 商单形式 + + + LOGO: Logo + + OTHER: 其他 + + SPOKEN_AD: 口播 + + PATCH: 贴片 + + TVC_IMBEDDED: TVC植入 + + CUSTOMIZED_AD: 定制软广 + + PROGRAM_SPONSORSHIP: 节目赞助 + + SLOGAN: SLOGAN + + QR_CODE: 二维码 + + SUBTITLE_PROMOTION: 字幕推广 + """ + + LOGO = 15 + OTHER = 10 + SPOKEN_AD = 11 + PATCH = 12 + TVC_IMBEDDED = 14 + CUSTOMIZED_AD = 19 + PROGRAM_SPONSORSHIP = 18 + SLOGAN = 17 + QR_CODE = 16 + SUBTITLE_PROMOTION = 13 + + +class VideoPorderMeta: + flow_id: int + industry_id: Optional[int] = None + official: Optional[int] = None + brand_name: Optional[str] = None + show_types: List[VideoPorderShowType] = [] + + __info: dict = None + + def __init__( + self, + porden_type: VideoPorderType = VideoPorderType.FIREWORK, + industry_type: Optional[VideoPorderIndustry] = None, + brand_name: Optional[str] = None, + show_types: List[VideoPorderShowType] = [], + ): + self.flow_id = 1 + self.__info = porden_type.value + if porden_type == VideoPorderType.OTHER: + self.__info["industry"] = industry_type.value + self.__info["brand_name"] = brand_name + self.__info["show_types"] = ",".join( + [show_type.value for show_type in show_types] + ) + + def __dict__(self) -> dict: + return self.__info + + +class VideoMeta: + tid: int # 分区 ID。可以使用 channel 模块进行查询。 + title: str # 视频标题 + desc: str # 视频简介。 + cover: Picture # 封面 URL + tags: Union[List[str], str] # 视频标签。使用英文半角逗号分隔的标签组。 + topic_id: Optional[int] = None # 可选,话题 ID。 + mission_id: Optional[int] = None # 可选,任务 ID。 + original: bool = True # 可选,是否为原创视频。 + source: Optional[str] = None # 可选,视频来源。 + recreate: Optional[bool] = False # 可选,是否允许重新上传。 + no_reprint: Optional[bool] = False # 可选,是否禁止转载。 + open_elec: Optional[bool] = False # 可选,是否展示充电信息。 + up_selection_reply: Optional[bool] = False # 可选,是否开启评论精选。 + up_close_danmu: Optional[bool] = False # 可选,是否关闭弹幕。 + up_close_reply: Optional[bool] = False # 可选,是否关闭评论。 + lossless_music: Optional[bool] = False # 可选,是否启用无损音乐。 + dolby: Optional[bool] = False # 可选,是否启用杜比音效。 + subtitle: Optional[dict] = None # 可选,字幕设置。 + dynamic: Optional[str] = None # 可选,动态信息。 + neutral_mark: Optional[str] = None # 可选,创作者声明。 + delay_time: Optional[Union[int, datetime]] = None # 可选,定时发布时间戳(秒)。 + porder: Optional[VideoPorderMeta] = None # 可选,商业相关参数。 + + __credential: Credential + __pre_info = dict + + def __init__( + self, + tid: int, # 分区 ID。可以使用 channel 模块进行查询。 + title: str, # 视频标题 + desc: str, # 视频简介。 + cover: Union[Picture, str], # 封面 URL + tags: Union[List[str], str], # 视频标签。使用英文半角逗号分隔的标签组。 + topic: Optional[Union[int, Topic]] = None, # 可选,话题 ID。 + mission_id: Optional[int] = None, # 可选,任务 ID。 + original: bool = True, # 可选,是否为原创视频。 + source: Optional[str] = None, # 可选,视频来源。 + recreate: Optional[bool] = False, # 可选,是否允许重新上传。 + no_reprint: Optional[bool] = False, # 可选,是否禁止转载。 + open_elec: Optional[bool] = False, # 可选,是否展示充电信息。 + up_selection_reply: Optional[bool] = False, # 可选,是否开启评论精选。 + up_close_danmu: Optional[bool] = False, # 可选,是否关闭弹幕。 + up_close_reply: Optional[bool] = False, # 可选,是否关闭评论。 + lossless_music: Optional[bool] = False, # 可选,是否启用无损音乐。 + dolby: Optional[bool] = False, # 可选,是否启用杜比音效。 + subtitle: Optional[dict] = None, # 可选,字幕设置。 + dynamic: Optional[str] = None, # 可选,动态信息。 + neutral_mark: Optional[str] = None, # 可选,中性化标签。 + delay_time: Optional[Union[int, datetime]] = None, # 可选,定时发布时间戳(秒)。 + porder: Optional[VideoPorderMeta] = None, # 可选,商业相关参数。 + ) -> None: + """ + 基本视频上传参数 + + 可调用 VideoMeta.verify() 验证部分参数是否可用 + + Args: + tid (int): 分区 id + + title (str): 视频标题,最多 80 字 + + desc (str): 视频简介,最多 2000 字 + + cover (Union[Picture, str]): 封面,可以传入路径 + + tags (List[str], str): 标签列表,传入 List 或者传入 str 以 "," 为分隔符,至少 1 个 Tag,最多 10 个 + + topic (Optional[Union[int, Topic]]): 活动主题,应该从 video_uploader.get_available_topics(tid) 获取,可选 + + mission_id (Optional[int]): 任务 id,与 topic 一同获取传入 + + original (bool): 是否原创,默认原创 + + source (Optional[str]): 转载来源,非原创应该提供 + + recreate (Optional[bool]): 是否允许转载. 可选,默认为不允许二创 + + no_reprint (Optional[bool]): 未经允许是否禁止转载. 可选,默认为允许转载 + + open_elec (Optional[bool]): 是否开启充电. 可选,默认为关闭充电 + + up_selection_reply (Optional[bool]): 是否开启评论精选. 可选,默认为关闭评论精选 + + up_close_danmu (Optional[bool]): 是否关闭弹幕. 可选,默认为开启弹幕 + + up_close_reply (Optional[bool]): 是否关闭评论. 可选,默认为开启评论 + + lossless_music (Optional[bool]): 是否开启无损音乐. 可选,默认为关闭无损音乐 + + dolby (Optional[bool]): 是否开启杜比音效. 可选,默认为关闭杜比音效 + + subtitle (Optional[dict]): 字幕信息,可选 + + dynamic (Optional[str]): 粉丝动态,可选,最多 233 字 + + neutral_mark (Optional[str]): 创作者声明,可选 + + delay_time (Optional[Union[int, datetime]]): 定时发布时间,可选 + + porder (Optional[VideoPorderMeta]): 商业相关参数,可选 + """ + if isinstance(tid, int): + self.tid = tid + if isinstance(title, str) and len(title) <= 80: + self.title = title + else: + raise ValueError("title 不合法或者大于 80 字") + + if tags is None: + raise ValueError("tags 不能为空") + elif isinstance(tags, str): + if "," in tags: + self.tags = tags.split(",") + else: + self.tags = [tags] + elif isinstance(tags, list) and len(tags) <= 10: + self.tags = tags + else: + raise ValueError("tags 不合法或者多于 10 个") + + if isinstance(cover, str): + self.cover = Picture().from_file(cover) + elif isinstance(cover, Picture): + self.cover = cover + if topic is not None: + self.mission_id = mission_id + if isinstance(topic, int): + self.topic_id = topic + elif isinstance(topic, Topic): + self.topic_id = topic.get_topic_id() + + if isinstance(desc, str) and len(desc) <= 2000: + self.desc = desc + else: + raise ValueError("desc 不合法或者大于 2000 字") + + self.original = original if isinstance(original, bool) else True + if not self.original: + if source is not None: + if isinstance(source, str) and len(source) <= 200: + self.source = source + else: + raise ValueError("source 不合法或者大于 200 字") + + self.recreate = recreate if isinstance(recreate, bool) else False + self.no_reprint = no_reprint if isinstance(no_reprint, bool) else False + self.open_elec = open_elec if isinstance(open_elec, bool) else False + self.up_selection_reply = ( + up_selection_reply if isinstance(up_selection_reply, bool) else False + ) + self.up_close_danmu = ( + up_close_danmu if isinstance(up_close_danmu, bool) else False + ) + self.up_close_reply = ( + up_close_reply if isinstance(up_close_reply, bool) else False + ) + self.lossless_music = ( + lossless_music if isinstance(lossless_music, bool) else False + ) + self.dolby = dolby if isinstance(dolby, bool) else False + self.subtitle = subtitle if isinstance(subtitle, dict) else None + self.dynamic = ( + dynamic if isinstance(dynamic, str) and len(dynamic) <= 233 else None + ) + self.neutral_mark = neutral_mark if isinstance(neutral_mark, str) else None + if isinstance(delay_time, int): + self.delay_time = delay_time + elif isinstance(delay_time, datetime): + self.delay_time = int(delay_time.timestamp()) + self.porder = porder if isinstance(porder, dict) else None + + def __dict__(self) -> dict: + meta = { + "title": self.title, + "copyright": 1 if self.original else 2, + "tid": self.tid, + "tag": ",".join(self.tags), + "mission_id": self.mission_id, # 根据 topic 对应任务 + "topic_id": self.topic_id, + "topic_detail": { + "from_topic_id": self.topic_id, + "from_source": "arc.web.recommend", + }, + "desc_format_id": 9999, + "desc": self.desc, + "dtime": self.delay_time, + "recreate": 1 if self.recreate else -1, + "dynamic": self.dynamic, + "interactive": 0, + "act_reserve_create": 0, # unknown + "no_disturbance": 0, # unknown + "porder": self.porder.__dict__(), + "adorder_type": 9, # unknown + "no_reprint": 1 if self.no_reprint else 0, + "subtitle": self.subtitle + if self.subtitle is not None + else { + "open": 0, + "lan": "", + }, # 莫名其妙没法上传 srt 字幕,显示格式错误,不校验 + "subtitle": self.subtitle, + "neutral_mark": self.neutral_mark, # 不知道能不能随便写文本 + "dolby": 1 if self.dolby else 0, + "lossless_music": 1 if self.lossless_music else 0, + "up_selection_reply": self.up_close_reply, + "up_close_reply": self.up_close_reply, + "up_close_danmu": self.up_close_danmu, + "web_os": 1, # const 1 + } + for k in copy(meta).keys(): + if meta[k] is None: + del meta[k] + return meta + + async def _pre(self) -> dict: + """ + 获取上传参数基本信息 + + 包括活动等在内,固定信息已经缓存于 data/video_uploader_meta_pre.json + """ + api = _API["pre"] + self.__pre_info = await Api(**api, credential=self.__credential).result + return self.__pre_info + + def _check_tid(self) -> bool: + """ + 检查 tid 是否合法 + """ + with open( + os.path.join( + os.path.dirname(__file__), "data/video_uploader_meta_pre.json" + ), + encoding="utf8", + ) as f: + self.__pre_info = json.load(f) + type_list = self.__pre_info["tid_list"] + for parent_type in type_list: + for child_type in parent_type["children"]: + if child_type["id"] == self.tid: + return True + return False + + async def _check_cover(self) -> bool: + """ + 检查封面是否合法 + """ + try: + await upload_cover(self.cover, self.__credential) + return True + except Exception: + return False + + @staticmethod + async def _check_tag_name(name: str, credential: Credential) -> bool: + """ + 检查 tag 是否合法 + + 需要登录 + """ + api = _API["check_tag_name"] + return ( + await Api(**api, credential=credential, ignore_code=True) + .update_params(t=name) + .result + )["code"] == 0 + + async def _check_tags(self) -> List[str]: + """ + 检查所有 tag 是否合法 + """ + return [ + tag + for tag in self.tags + if await self._check_tag_name(tag, self.__credential) + ] + + async def _check_topic_to_mission(self) -> Union[int, bool]: + """ + 检查 topic -> mission 是否存在 + """ + # 只知道能从这里获取...不确定其他地方的 topic -> mission 能否传入 + all_topic_info = await get_available_topics( + tid=self.tid, credential=self.__credential + ) + for topic in all_topic_info: + if topic["topic_id"] == self.topic_id: + return topic["mission_id"] + else: + return False + + async def verify(self, credential: Credential) -> bool: + """ + 验证参数是否可用,仅供参考 + + 检测 tags、delay_time、topic -> mission、cover 和 tid + + 验证失败会抛出异常 + """ + credential.raise_for_no_sessdata() + self.__credential = credential + + # await self._pre() # 缓存于 bilibili_api\data\video_uploader_meta_pre.json + error_tags = await self._check_tags() + if len(error_tags) != 0: + raise ValueError(f'以下 tags 不合法: {",".join(error_tags)}') + + if not self._check_tid(): + raise ValueError(f"tid {self.tid} 不合法") + + topic_to_mission = await self._check_topic_to_mission() + if isinstance(topic_to_mission, int): + self.mission_id = topic_to_mission + elif not topic_to_mission: + raise ValueError( + f"topic -> mission 不存在: {self.topic_id} -> {self.mission_id}" + ) + + if not await self._check_cover(): + raise ValueError(f"封面不合法 {self.cover.__repr__()}") + + if self.delay_time is not None: + if self.delay_time < int(time.time()) + 7200: + raise ValueError("delay_time 不能小于两小时") + if self.delay_time > int(time.time()) + 3600 * 24 * 15: + raise ValueError("delay_time 不能大于十五天") + return True + + +class VideoUploader(AsyncEvent): + """ + 视频上传 + + Attributes: + pages (List[VideoUploaderPage]): 分 P 列表 + + meta (VideoMeta, dict) : 视频信息 + + credential (Credential) : 凭据 + + cover_path (str) : 封面路径 + + line (Lines, Optional) : 线路. Defaults to None. 不选择则自动测速选择 + """ + + def __init__( + self, + pages: List[VideoUploaderPage], + meta: Union[VideoMeta, dict], + credential: Credential, + cover: Optional[Union[str, Picture]] = "", + line: Optional[Lines] = None, + ): + """ + Args: + pages (List[VideoUploaderPage]): 分 P 列表 + + meta (VideoMeta, dict) : 视频信息 + + credential (Credential) : 凭据 + + cover (Union[str, Picture]) : 封面路径或者封面对象. Defaults to "",传入 meta 类型为 VideoMeta 时可不传 + + line: (Lines, Optional) : 线路. Defaults to None. 不选择则自动测速选择 + + 建议传入 VideoMeta 对象,避免参数有误 + + meta 参数示例: + + ```json + { + "title": "", + "copyright": 1, + "tid": 130, + "tag": "", + "desc_format_id": 9999, + "desc": "", + "recreate": -1, + "dynamic": "", + "interactive": 0, + "act_reserve_create": 0, + "no_disturbance": 0, + "no_reprint": 1, + "subtitle": { + "open": 0, + "lan": "", + }, + "dolby": 0, + "lossless_music": 0, + "web_os": 1, + } + ``` + + meta 保留字段:videos, cover + """ + super().__init__() + self.meta = meta + self.pages = pages + self.credential = credential + self.cover = ( + self.meta.cover + if isinstance(self.meta, VideoMeta) + else cover + if isinstance(cover, Picture) + else Picture().from_file(cover) + ) + self.line = line + self.__task: Union[Task, None] = None + + async def _preupload(self, page: VideoUploaderPage) -> dict: + """ + 分 P 上传初始化 + + Returns: + dict: 初始化信息 + """ + self.dispatch(VideoUploaderEvents.PREUPLOAD.value, {"page": page}) + api = _API["preupload"] + + # 首先获取视频文件预检信息 + session = get_session() + + resp = await session.get( + api["url"], + params={ + "profile": "ugcfx/bup", + "name": os.path.basename(page.path), + "size": page.get_size(), + "r": self.line["os"], + "ssl": "0", + "version": "2.14.0", + "build": "2100400", + "upcdn": self.line["upcdn"], + "probe_version": self.line["probe_version"], + }, + cookies=self.credential.get_cookies(), + headers={ + "User-Agent": "Mozilla/5.0", + "Referer": "https://www.bilibili.com", + }, + ) + if resp.status_code >= 400: + self.dispatch(VideoUploaderEvents.PREUPLOAD_FAILED.value, {"page": page}) + raise NetworkException(resp.status_code, resp.reason_phrase) + + preupload = resp.json() + + if preupload["OK"] != 1: + self.dispatch(VideoUploaderEvents.PREUPLOAD_FAILED.value, {"page": page}) + raise ApiException(json.dumps(preupload)) + + preupload = self._switch_upload_endpoint(preupload, self.line) + url = self._get_upload_url(preupload) + + # 获取 upload_id + resp = await session.post( + url, + headers={ + "x-upos-auth": preupload["auth"], + "user-agent": "Mozilla/5.0", + "referer": "https://www.bilibili.com", + }, + params={ + "uploads": "", + "output": "json", + "profile": "ugcfx/bup", + "filesize": page.get_size(), + "partsize": preupload["chunk_size"], + "biz_id": preupload["biz_id"], + }, + ) + if resp.status_code >= 400: + self.dispatch(VideoUploaderEvents.PREUPLOAD_FAILED.value, {"page": page}) + raise ApiException("获取 upload_id 错误") + + data = json.loads(resp.text) + + if data["OK"] != 1: + self.dispatch(VideoUploaderEvents.PREUPLOAD_FAILED.value, {"page": page}) + raise ApiException("获取 upload_id 错误:" + json.dumps(data)) + + preupload["upload_id"] = data["upload_id"] + + # # 读取并上传视频元数据,这段代码暂时用不上 + # meta = ffmpeg.probe(page.path) + # meta_format = meta["format"] + # meta_video = list(map(lambda x: x if x["codec_type"] == "video" else None, meta["streams"])) + # meta_video.remove(None) + # meta_video = meta_video[0] + + # meta_audio = list(map(lambda x: x if x["codec_type"] == "audio" else None, meta["streams"])) + # meta_audio.remove(None) + # meta_audio = meta_audio[0] + + # meta_to_upload = json.dumps({ + # "code": 0, + # "filename": os.path.splitext(os.path.basename(preupload["upos_uri"]))[0], + # "filesize": int(meta_format["size"]), + # "key_frames": [], + # "meta": { + # "audio_meta": meta_audio, + # "video_meta": meta_video, + # "container_meta": { + # "duration": round(float(meta_format["duration"]), 2), + # "format_name": meta_format["format_name"] + # } + # }, + # "version": "2.3.7", + # "webVersion": "1.0.0" + # }) + + # # 预检元数据上传 + # async with session.get(api["url"], params={ + # "name": "BUploader_meta.txt", + # "size": len(meta_to_upload), + # "r": "upos", + # "profile": "fxmeta/bup", + # "ssl": "0", + # "version": "2.10.3", + # "build": "2100300", + # }, cookies=self.credential.get_cookies(), + # headers={ + # "User-Agent": "Mozilla/5.0", + # "Referer": "https://www.bilibili.com" + # }, proxy=settings.proxy + # ) as resp: + # if resp.status >= 400: + # self.dispatch(VideoUploaderEvents.PREUPLOAD_FAILED.value, {page: page}) + # raise NetworkException(resp.status, resp.reason) + + # preupload_m = await resp.json() + + # if preupload_m['OK'] != 1: + # self.dispatch(VideoUploaderEvents.PREUPLOAD_FAILED.value, {page: page}) + # raise ApiException(json.dumps(preupload_m)) + + # url = self._get_upload_url(preupload_m) + + # # 获取 upload_id + # async with session.post(url, params={ + # "uploads": "", + # "output": "json" + # }, headers={ + # "x-upos-auth": preupload_m["auth"] + # }, proxy=settings.proxy) as resp: + # if resp.status >= 400: + # self.dispatch(VideoUploaderEvents.PREUPLOAD_FAILED.value, {page: page}) + # raise NetworkException(resp.status, resp.reason) + + # data = json.loads(await resp.text()) + # if preupload_m['OK'] != 1: + # self.dispatch(VideoUploaderEvents.PREUPLOAD_FAILED.value, {page: page}) + # raise ApiException(json.dumps(preupload_m)) + + # upload_id = data["upload_id"] + + # size = len(meta_to_upload) + # async with session.put(url, params={ + # "partNumber": 1, + # "uploadId": upload_id, + # "chunk": 0, + # "chunks": 1, + # "size": size, + # "start": 0, + # "end": size, + # "total": size + # }, headers={ + # "x-upos-auth": preupload_m["auth"] + # }, data=meta_to_upload, proxy=settings.proxy) as resp: + # if resp.status >= 400: + # self.dispatch(VideoUploaderEvents.PREUPLOAD_FAILED.value, {page: page}) + # raise NetworkException(resp.status, resp.reason) + + # data = await resp.text() + + # if data != 'MULTIPART_PUT_SUCCESS': + # self.dispatch(VideoUploaderEvents.PREUPLOAD_FAILED.value, {page: page}) + # raise ApiException(json.dumps(preupload_m)) + + # async with session.post(url, + # data=json.dumps({"parts": [{"partNumber": 1, "eTag": "etag"}]}), + # params={ + # "output": "json", + # "name": "BUploader_meta.txt", + # "profile": "", + # "uploadId": upload_id, + # "biz_id": "" + # }, + # headers={ + # "x-upos-auth": preupload_m["auth"] + # }, proxy=settings.proxy + # ) as resp: + # if resp.status >= 400: + # self.dispatch(VideoUploaderEvents.PREUPLOAD_FAILED.value, {page: page}) + # raise NetworkException(resp.status, resp.reason) + + # data = json.loads(await resp.text()) + + # if data['OK'] != 1: + # self.dispatch(VideoUploaderEvents.PREUPLOAD_FAILED.value, {page: page}) + # raise ApiException(json.dumps(data)) + + return preupload + + async def _main(self) -> dict: + videos = [] + for page in self.pages: + data = await self._upload_page(page) + videos.append( + { + "title": page.title, + "desc": page.description, + "filename": data["filename"], # type: ignore + "cid": data["cid"], # type: ignore + } + ) + + cover_url = await self._upload_cover() + + result = await self._submit(videos, cover_url) + + self.dispatch(VideoUploaderEvents.COMPLETED.value, result) + return result + + async def start(self) -> dict: # type: ignore + """ + 开始上传 + + Returns: + dict: 返回带有 bvid 和 aid 的字典。 + """ + + self.line = await _choose_line(self.line) + task = create_task(self._main()) + self.__task = task + + try: + result = await task + self.__task = None + return result + except CancelledError: + # 忽略 task 取消异常 + pass + except Exception as e: + self.dispatch(VideoUploaderEvents.FAILED.value, {"err": e}) + raise e + + async def _upload_cover(self) -> str: + """ + 上传封面 + + Returns: + str: 封面 URL + """ + self.dispatch(VideoUploaderEvents.PRE_COVER.value, None) + try: + cover_url = await upload_cover(cover=self.cover, credential=self.credential) + self.dispatch(VideoUploaderEvents.AFTER_COVER.value, {"url": cover_url}) + return cover_url + except Exception as e: + self.dispatch(VideoUploaderEvents.COVER_FAILED.value, {"err": e}) + raise e + + async def _upload_page(self, page: VideoUploaderPage) -> dict: + """ + 上传分 P + + Args: + page (VideoUploaderPage): 分 P 对象 + + Returns: + str: 分 P 文件 ID,用于 submit 时的 $.videos[n].filename 字段使用。 + """ + preupload = await self._preupload(page) + self.dispatch(VideoUploaderEvents.PRE_PAGE.value, {"page": page}) + + page_size = page.get_size() + # 所有分块起始位置 + chunk_offset_list = list(range(0, page_size, preupload["chunk_size"])) + # 分块总数 + total_chunk_count = len(chunk_offset_list) + # 并发上传分块 + chunk_number = 0 + # 上传队列 + chunks_pending = [] + # 缓存 upload_id,这玩意只能从上传的分块预检结果获得 + upload_id = preupload["upload_id"] + for offset in chunk_offset_list: + chunks_pending.insert( + 0, + self._upload_chunk( + page, offset, chunk_number, total_chunk_count, preupload + ), + ) + chunk_number += 1 + + while chunks_pending: + tasks = [] + + while len(tasks) < preupload["threads"] and len(chunks_pending) > 0: + tasks.append(create_task(chunks_pending.pop())) + + result = await asyncio.gather(*tasks) + + for r in result: + if not r["ok"]: + chunks_pending.insert( + 0, + self._upload_chunk( + page, + r["offset"], + r["chunk_number"], + total_chunk_count, + preupload, + ), + ) + + data = await self._complete_page(page, total_chunk_count, preupload, upload_id) + + self.dispatch(VideoUploaderEvents.AFTER_PAGE.value, {"page": page}) + + return data + + @staticmethod + def _switch_upload_endpoint(preupload: dict, line: dict = None) -> dict: + # 替换线路 endpoint + if line is not None and re.match( + r"//upos-(sz|cs)-upcdn(bda2|ws|qn)\.bilivideo\.com", preupload["endpoint"] + ): + preupload["endpoint"] = re.sub( + r"upcdn(bda2|qn|ws)", f'upcdn{line["upcdn"]}', preupload["endpoint"] + ) + return preupload # tbh not needed since it is ref type + + @staticmethod + def _get_upload_url(preupload: dict) -> str: + # 上传目标 URL + # return f'https:{preupload["endpoint"]}/{preupload["upos_uri"].removeprefix("upos://")}' + return f'https:{preupload["endpoint"]}/{preupload["upos_uri"].replace("upos://","")}' + + async def _upload_chunk( + self, + page: VideoUploaderPage, + offset: int, + chunk_number: int, + total_chunk_count: int, + preupload: dict, + ) -> dict: + """ + 上传视频分块 + + Args: + page (VideoUploaderPage): 分 P 对象 + offset (int): 分块起始位置 + chunk_number (int): 分块编号 + total_chunk_count (int): 总分块数 + preupload (dict): preupload 数据 + + Returns: + dict: 上传结果和分块信息。 + """ + chunk_event_callback_data = { + "page": page, + "offset": offset, + "chunk_number": chunk_number, + "total_chunk_count": total_chunk_count, + } + self.dispatch(VideoUploaderEvents.PRE_CHUNK.value, chunk_event_callback_data) + session = get_session() + + stream = open(page.path, "rb") + stream.seek(offset) + chunk = stream.read(preupload["chunk_size"]) + stream.close() + + # 上传目标 URL + preupload = self._switch_upload_endpoint(preupload, self.line) + url = self._get_upload_url(preupload) + + err_return = { + "ok": False, + "chunk_number": chunk_number, + "offset": offset, + "page": page, + } + + real_chunk_size = len(chunk) + + params = { + "partNumber": str(chunk_number + 1), + "uploadId": str(preupload["upload_id"]), + "chunk": str(chunk_number), + "chunks": str(total_chunk_count), + "size": str(real_chunk_size), + "start": str(offset), + "end": str(offset + real_chunk_size), + "total": page.get_size(), + } + + ok_return = { + "ok": True, + "chunk_number": chunk_number, + "offset": offset, + "page": page, + } + + try: + resp = await session.put( + url, + data=chunk, # type: ignore + params=params, + headers={"x-upos-auth": preupload["auth"]}, + ) + if resp.status_code >= 400: + chunk_event_callback_data["info"] = f"Status {resp.status_code}" + self.dispatch( + VideoUploaderEvents.CHUNK_FAILED.value, + chunk_event_callback_data, + ) + return err_return + + data = resp.text + + if data != "MULTIPART_PUT_SUCCESS" and data != "": + chunk_event_callback_data["info"] = "分块上传失败" + self.dispatch( + VideoUploaderEvents.CHUNK_FAILED.value, + chunk_event_callback_data, + ) + return err_return + + except Exception as e: + chunk_event_callback_data["info"] = str(e) + self.dispatch( + VideoUploaderEvents.CHUNK_FAILED.value, chunk_event_callback_data + ) + return err_return + + self.dispatch(VideoUploaderEvents.AFTER_CHUNK.value, chunk_event_callback_data) + return ok_return + + async def _complete_page( + self, page: VideoUploaderPage, chunks: int, preupload: dict, upload_id: str + ) -> dict: + """ + 提交分 P 上传 + + Args: + page (VideoUploaderPage): 分 P 对象 + + chunks (int): 分块数量 + + preupload (dict): preupload 数据 + + upload_id (str): upload_id + + Returns: + dict: filename: 该分 P 的标识符,用于最后提交视频。cid: 分 P 的 cid + """ + self.dispatch(VideoUploaderEvents.PRE_PAGE_SUBMIT.value, {"page": page}) + + data = { + "parts": list( + map(lambda x: {"partNumber": x, "eTag": "etag"}, range(1, chunks + 1)) + ) + } + + params = { + "output": "json", + "name": os.path.basename(page.path), + "profile": "ugcfx/bup", + "uploadId": upload_id, + "biz_id": preupload["biz_id"], + } + + preupload = self._switch_upload_endpoint(preupload, self.line) + url = self._get_upload_url(preupload) + + session = get_session() + + resp = await session.post( + url=url, + data=json.dumps(data), # type: ignore + headers={ + "x-upos-auth": preupload["auth"], + "content-type": "application/json; charset=UTF-8", + }, + params=params, + ) + if resp.status_code >= 400: + err = NetworkException(resp.status_code, "状态码错误,提交分 P 失败") + self.dispatch( + VideoUploaderEvents.PAGE_SUBMIT_FAILED.value, + {"page": page, "err": err}, + ) + raise err + + data = json.loads(resp.read()) + + if data["OK"] != 1: + err = ResponseCodeException(-1, f'提交分 P 失败,原因: {data["message"]}') + self.dispatch( + VideoUploaderEvents.PAGE_SUBMIT_FAILED.value, + {"page": page, "err": err}, + ) + raise err + + self.dispatch(VideoUploaderEvents.AFTER_PAGE_SUBMIT.value, {"page": page}) + + # return { + # "filename": os.path.splitext(data["key"].removeprefix("/"))[0], + # "cid": preupload["biz_id"], + # } + return { + "filename": os.path.splitext(data["key"].replace("/",""))[0], + "cid": preupload["biz_id"], + } + + async def _submit(self, videos: list, cover_url: str = "") -> dict: + """ + 提交视频 + + Args: + videos (list): 视频列表 + + cover_url (str, optional): 封面 URL. + + Returns: + dict: 含 bvid 和 aid 的字典 + """ + meta = copy( + self.meta.__dict__() if isinstance(self.meta, VideoMeta) else self.meta + ) + meta["cover"] = cover_url + meta["videos"] = videos + + self.dispatch(VideoUploaderEvents.PRE_SUBMIT.value, deepcopy(meta)) + + meta["csrf"] = self.credential.bili_jct # csrf 不需要 print + api = _API["submit"] + + try: + params = {"csrf": self.credential.bili_jct, "t": time.time() * 1000} + # headers = {"content-type": "application/json"} + # 已有 json_body,似乎不需要单独设置 content-type + resp = ( + await Api( + **api, credential=self.credential, no_csrf=True, json_body=True + ) + .update_params(**params) + .update_data(**meta) + # .update_headers(**headers) + .result + ) + self.dispatch(VideoUploaderEvents.AFTER_SUBMIT.value, resp) + return resp + + except Exception as err: + self.dispatch(VideoUploaderEvents.SUBMIT_FAILED.value, {"err": err}) + raise err + + async def abort(self): + """ + 中断上传 + """ + if self.__task: + self.__task.cancel("用户手动取消") + + self.dispatch(VideoUploaderEvents.ABORTED.value, None) + + +async def get_missions( + tid: int = 0, credential: Union[Credential, None] = None +) -> dict: + """ + 获取活动信息 + + Args: + tid (int, optional) : 分区 ID. Defaults to 0. + + credential (Credential, optional): 凭据. Defaults to None. + + Returns: + dict API 调用返回结果 + """ + api = _API["missions"] + + params = {"tid": tid} + + return await Api(**api, credential=credential).update_params(**params).result + + +class VideoEditorEvents(Enum): + """ + 视频稿件编辑事件枚举 + + + PRELOAD : 加载数据前 + + AFTER_PRELOAD : 加载成功 + + PRELOAD_FAILED: 加载失败 + + PRE_COVER : 上传封面前 + + AFTER_COVER : 上传封面后 + + COVER_FAILED : 上传封面失败 + + PRE_SUBMIT : 提交前 + + AFTER_SUBMIT : 提交后 + + SUBMIT_FAILED : 提交失败 + + COMPLETED : 完成 + + ABOTRED : 停止 + + FAILED : 失败 + """ + + PRELOAD = "PRELOAD" + AFTER_PRELOAD = "AFTER_PRELOAD" + PRELOAD_FAILED = "PRELOAD_FAILED" + + PRE_COVER = "PRE_COVER" + AFTER_COVER = "AFTER_COVER" + COVER_FAILED = "COVER_FAILED" + + PRE_SUBMIT = "PRE_SUBMIT" + SUBMIT_FAILED = "SUBMIT_FAILED" + AFTER_SUBMIT = "AFTER_SUBMIT" + + COMPLETED = "COMPLETE" + ABORTED = "ABORTED" + FAILED = "FAILED" + + +class VideoEditor(AsyncEvent): + """ + 视频稿件编辑 + + Attributes: + bvid (str) : 稿件 BVID + + meta (dict) : 视频信息 + + cover_path (str) : 封面路径. Defaults to None(不更换封面). + + credential (Credential): 凭据类. Defaults to None. + """ + + def __init__( + self, + bvid: str, + meta: dict, + cover: Union[str, Picture] = "", + credential: Union[Credential, None] = None, + ): + """ + Args: + bvid (str) : 稿件 BVID + + meta (dict) : 视频信息 + + cover (str | Picture) : 封面地址. Defaults to None(不更改封面). + + credential (Credential | None): 凭据类. Defaults to None. + + meta 参数示例: (保留 video, cover, tid, aid 字段) + + ``` json + { + "title": "str: 标题", + "copyright": "int: 是否原创,0 否 1 是", + "tag": "标签. 用,隔开. ", + "desc_format_id": "const int: 0", + "desc": "str: 描述", + "dynamic": "str: 动态信息", + "interactive": "const int: 0", + "new_web_edit": "const int: 1", + "act_reserve_create": "const int: 0", + "handle_staff": "const bool: false", + "topic_grey": "const int: 1", + "no_reprint": "int: 是否显示“未经允许禁止转载”. 0 否 1 是", + "subtitles # 字幕设置": { + "lan": "str: 字幕投稿语言,不清楚作用请将该项设置为空", + "open": "int: 是否启用字幕投稿,1 or 0" + }, + "web_os": "const int: 2" + } + ``` + """ + super().__init__() + self.bvid = bvid + self.meta = meta + self.credential = credential if credential else Credential() + self.cover_path = cover + self.__old_configs = {} + self.meta["aid"] = bvid2aid(bvid) + self.__task: Union[Task, None] = None + + async def _fetch_configs(self): + """ + 在本地缓存原来的上传信息 + """ + self.dispatch(VideoEditorEvents.PRELOAD.value) + try: + api = _API["upload_args"] + params = {"bvid": self.bvid} + self.__old_configs = ( + await Api(**api, credential=self.credential) + .update_params(**params) + .result + ) + except Exception as e: + self.dispatch(VideoEditorEvents.PRELOAD_FAILED.value, {"err", e}) + raise e + self.dispatch( + VideoEditorEvents.AFTER_PRELOAD.value, {"data": self.__old_configs} + ) + + async def _change_cover(self) -> None: + """ + 更换封面 + + Returns: + None + """ + if self.cover_path == "": + return + self.dispatch(VideoEditorEvents.PRE_COVER.value, None) + try: + pic = ( + self.cover_path + if isinstance(self.cover_path, Picture) + else Picture().from_file(self.cover_path) + ) + resp = await upload_cover(pic, self.credential) + self.dispatch(VideoEditorEvents.AFTER_COVER.value, {"url": resp["url"]}) + # not sure if this key changed to "url" as well + self.meta["cover"] = resp["image_url"] + except Exception as e: + self.dispatch(VideoEditorEvents.COVER_FAILED.value, {"err": e}) + raise e + + async def _submit(self): + api = _API["edit"] + data = self.meta + data["csrf"] = self.credential.bili_jct + self.dispatch(VideoEditorEvents.PRE_SUBMIT.value) + try: + params = {"csrf": self.credential.bili_jct, "t": int(time.time())} + headers = { + "content-type": "application/json;charset=UTF-8", + "referer": "https://member.bilibili.com", + "user-agent": "Mozilla/5.0", + } + resp = ( + await Api(**api, credential=self.credential, no_csrf=True) + .update_params(**params) + .update_data(**data) + .update_headers(**headers) + .result + ) + self.dispatch(VideoEditorEvents.AFTER_SUBMIT.value, resp) + except Exception as e: + self.dispatch(VideoEditorEvents.SUBMIT_FAILED.value, {"err", e}) + raise e + + async def _main(self) -> dict: + await self._fetch_configs() + self.meta["videos"] = [] + cnt = 0 + for v in self.__old_configs["videos"]: + self.meta["videos"].append( + {"title": v["title"], "desc": v["desc"], "filename": v["filename"]} + ) + self.meta["videos"][-1]["cid"] = await Video(self.bvid).get_cid(cnt) + cnt += 1 + self.meta["cover"] = self.__old_configs["archive"]["cover"] + self.meta["tid"] = self.__old_configs["archive"]["tid"] + await self._change_cover() + await self._submit() + self.dispatch(VideoEditorEvents.COMPLETED.value) + return {"bvid": self.bvid} + + async def start(self) -> dict: # type: ignore + """ + 开始更改 + + Returns: + dict: 返回带有 bvid 和 aid 的字典。 + """ + + task = create_task(self._main()) + self.__task = task + + try: + result = await task + self.__task = None + return result + except CancelledError: + # 忽略 task 取消异常 + pass + except Exception as e: + self.dispatch(VideoEditorEvents.FAILED.value, {"err": e}) + raise e + + async def abort(self): + """ + 中断更改 + """ + if self.__task: + self.__task.cancel("用户手动取消") + + self.dispatch(VideoEditorEvents.ABORTED.value, None) diff --git a/bilibili_api/video_zone.py b/bilibili_api/video_zone.py new file mode 100644 index 0000000000000000000000000000000000000000..ecbedc0654c3f804de65240aa792f4cc14f61e8d --- /dev/null +++ b/bilibili_api/video_zone.py @@ -0,0 +1,481 @@ +""" +bilibili_api.video_zone + +分区相关操作,与频道不互通。 +""" + +import os +import copy +import enum +import json +from typing import Dict, List, Tuple, Union + +from .utils.utils import get_api +from .exceptions import ArgsException +from .utils.credential import Credential +from .utils.network import Api + +API = get_api("video_zone") + + +def get_zone_info_by_tid(tid: int) -> Tuple[Union[dict, None], Union[dict, None]]: + """ + 根据 tid 获取分区信息。 + + Args: + tid (int): 频道的 tid。 + + Returns: + `Tuple[dict | None, dict | None]`: 第一个是主分区,第二个是子分区,没有时返回 None。 + """ + with open( + os.path.join(os.path.dirname(__file__), "data/video_zone.json"), encoding="utf8" + ) as f: + channel = json.loads(f.read()) + + for main_ch in channel: + if "tid" not in main_ch: + continue + if tid == int(main_ch["tid"]): + return main_ch, None + + # 搜索子分区 + if "sub" in main_ch.keys(): + for sub_ch in main_ch["sub"]: + if "tid" not in sub_ch: + continue + if tid == sub_ch["tid"]: + return main_ch, sub_ch + else: + return None, None + + +def get_zone_info_by_name(name: str) -> Tuple[Union[dict, None], Union[dict, None]]: + """ + 根据分区名称获取分区信息。 + + Args: + name (str): 频道的名称。 + + Returns: + Tuple[dict | None, dict | None]: 第一个是主分区,第二个是子分区,没有时返回 None。 + """ + with open( + os.path.join(os.path.dirname(__file__), "data/video_zone.json"), encoding="utf8" + ) as f: + channel = json.loads(f.read()) + + for main_ch in channel: + if name in main_ch["name"]: + return main_ch, None + if "sub" in main_ch.keys(): + for sub_ch in main_ch["sub"]: + if name in sub_ch["name"]: + return main_ch, sub_ch + else: + return None, None + + +async def get_zone_top10( + tid: int, day: int = 7, credential: Union[Credential, None] = None +) -> dict: + """ + 获取分区前十排行榜。 + + Args: + tid (int) : 频道的 tid。 + + day (int, optional) : 3 天排行还是 7 天排行。 Defaults to 7. + + credential (Credential | None, optional): Credential 类。Defaults to None. + + Returns: + list: 前 10 的视频信息。 + """ + if credential is None: + credential = Credential() + if day not in (3, 7): + raise ArgsException("参数 day 只能是 3,7。") + + api = API["ranking"]["get_top10"] + params = {"rid": tid, "day": day} + return await Api(**api, credential=credential).update_params(**params).result + + +def get_zone_list() -> List[Dict]: + """ + 获取所有分区的数据 + + Returns: + List[dict]: 所有分区的数据 + """ + with open( + os.path.join(os.path.dirname(__file__), "data/video_zone.json"), encoding="utf8" + ) as f: + channel = json.loads(f.read()) + channel_list = [] + for channel_big in channel: + channel_big_copy = copy.copy(channel_big) + channel_list.append(channel_big_copy) + if "sub" in channel_big.keys(): + channel_big_copy.pop("sub") + for channel_sub in channel_big["sub"]: + channel_sub_copy = copy.copy(channel_sub) + channel_sub_copy["father"] = channel_big_copy + channel_list.append(channel_sub_copy) + return channel_list + + +def get_zone_list_sub() -> dict: + """ + 获取所有分区的数据 + 含父子关系(即一层次只有主分区) + + Returns: + dict: 所有分区的数据 + """ + with open( + os.path.join(os.path.dirname(__file__), "data/video_zone.json"), encoding="utf8" + ) as f: + channel = json.loads(f.read()) + return channel + + +async def get_zone_videos_count_today( + credential: Union[Credential, None] = None +) -> dict: + """ + 获取每个分区当日最新投稿数量 + + Args: + credential (Credential | None): 凭据类 + + Returns: + dict: 调用 API 返回的结果 + """ + credential = credential if credential else Credential() + api = API["count"] + return (await Api(**api, credential=credential).result)["region_count"] + + +async def get_zone_new_videos(tid: int, page_num: int = 1, page_size: int = 10) -> dict: + """ + 获取分区最新投稿 + + Args: + tid (int) : 分区 id + + page_num (int) : 第几页. Defaults to 1. + + page_size (int) : 每页的数据大小. Defaults to 10. + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["new"] + params = {"rid": tid, "pn": page_num, "ps": page_size} + return await Api(**api).update_params(**params).result + + +async def get_zone_hot_tags(tid: int) -> List[dict]: + """ + 获取分区热门标签 + + Args: + tid (int) : 分区 id + + Returns: + List[dict]: 热门标签 + """ + + api = API["get_hot_tags"] + params = {"rid": tid} + return (await Api(**api).update_params(**params).result)[0] + + +class VideoZoneTypes(enum.Enum): + """ + 所有分区枚举 + + - MAINPAGE: 主页 + - ANIME: 番剧 + - ANIME_SERIAL: 连载中番剧 + - ANIME_FINISH: 已完结番剧 + - ANIME_INFORMATION: 资讯 + - ANIME_OFFICAL: 官方延伸 + - MOVIE: 电影 + - GUOCHUANG: 国创 + - GUOCHUANG_CHINESE: 国产动画 + - GUOCHUANG_ORIGINAL: 国产原创相关 + - GUOCHUANG_PUPPETRY: 布袋戏 + - GUOCHUANG_MOTIONCOMIC: 动态漫·广播剧 + - GUOCHUANG_INFORMATION: 资讯 + - TELEPLAY: 电视剧 + - DOCUMENTARY: 纪录片 + - DOUGA: 动画 + - DOUGA_MAD: MAD·AMV + - DOUGA_MMD: MMD·3D + - DOUGA_HANDDRAWN: 短片·手书 + - DOUGA_VOICE: 配音 + - DOUGA_GARAGE_KIT: 手办·模玩 + - DOUGA_TOKUSATSU: 特摄 + - DOUGA_ACGNTALKS: 动漫杂谈 + - DOUGA_OTHER: 综合 + - GAME: 游戏 + - GAME_STAND_ALONE: 单机游戏 + - GAME_ESPORTS: 电子竞技 + - GAME_MOBILE: 手机游戏 + - GAME_ONLINE: 网络游戏 + - GAME_BOARD: 桌游棋牌 + - GAME_GMV: GMV + - GAME_MUSIC: 音游 + - GAME_MUGEN: Mugen + - KICHIKU: 鬼畜 + - KICHIKU_GUIDE: 鬼畜调教 + - KICHIKU_MAD: 音MAD + - KICHIKU_MANUAL_VOCALOID: 人力VOCALOID + - KICHIKU_THEATRE: 鬼畜剧场 + - KICHIKU_COURSE: 教程演示 + - MUSIC: 音乐 + - MUSIC_ORIGINAL: 原创音乐 + - MUSIC_COVER: 翻唱 + - MUSIC_PERFORM: 演奏 + - MUSIC_VOCALOID: VOCALOID·UTAU + - MUSIC_LIVE: 音乐现场 + - MUSIC_MV: MV + - MUSIC_COMMENTARY: 乐评盘点 + - MUSIC_TUTORIAL: 音乐教学 + - MUSIC_OTHER: 音乐综合 + - DANCE: 舞蹈 + - DANCE_OTAKU: 宅舞 + - DANCE_HIPHOP: 街舞 + - DANCE_STAR: 明星舞蹈 + - DANCE_CHINA: 中国舞 + - DANCE_THREE_D: 舞蹈综合 + - DANCE_DEMO: 舞蹈教程 + - DANGE_GESTURES: 手势·网红舞 + - CINEPHILE: 影视 + - CINEPHILE_CINECISM: 影视杂谈 + - CINEPHILE_MONTAGE: 影视剪辑 + - CINEPHILE_SHORTFILM: 短片 + - CINEPHILE_SHORTPLAY: 小剧场 + - CINEPHILE_TRAILER_INFO: 预告·资讯 + - ENT: 娱乐 + - ENT_VARIETY: 综艺 + - ENT_TALKER: 娱乐杂谈 + - ENT_FANS: 粉丝创作 + - ENT_CELEBRITY: 明星综合 + - KNOWLEDGE: 知识 + - KNOWLEDGE_SCIENCE: 科学科普 + - KNOWLEDGE_SOCIAL_SCIENCE: 社科·法律·心理 + - KNOWLEDGE_HUMANITY_HISTORY: 人文历史 + - KNOWLEDGE_BUSINESS: 财经商业 + - KNOWLEDGE_CAMPUS: 校园学习 + - KNOWLEDGE_CAREER: 职业职场 + - KNOWLEDGE_DESIGN: 设计·创意 + - KNOWLEDGE_SKILL: 野生技能协会 + - TECH: 科技 + - TECH_DIGITAL: 数码 + - TECH_APPLICATION: 软件应用 + - TECH_COMPUTER_TECH: 计算机技术 + - TECH_INDUSTRY: 科工机械 + - INFORMATION: 资讯 + - INFORMATION_HOTSPOT: 热点 + - INFORMATION_GLOBAL: 环球 + - INFORMATION_SOCIAL: 社会 + - INFORMATION_MULTIPLE: 综合 + - FOOD: 美食 + - FOOD_MAKE: 美食制作 + - FOOD_DETECTIVE: 美食侦探 + - FOOD_MEASUREMENT: 美食测评 + - FOOD_RURAL: 田园美食 + - FOOD_RECORD: 美食记录 + - LIFE: 生活 + - LIFE_FUNNY: 搞笑 + - LIFE_TRAVEL: 出行 + - LIFE_RURALLIFE: 三农 + - LIFE_HOME: 家居房产 + - LIFE_HANDMAKE: 手工 + - LIFE_PAINTING: 绘画 + - LIFE_DAILY: 日常 + - CAR: 汽车 + - CAR_RACING: 赛车 + - CAR_MODIFIEDVEHICLE: 改装玩车 + - CAR_NEWENERGYVEHICLE: 新能源车 + - CAR_TOURINGCAR: 房车 + - CAR_MOTORCYCLE: 摩托车 + - CAR_STRATEGY: 购车攻略 + - CAR_LIFE: 汽车生活 + - FASHION: 时尚 + - FASHION_MAKEUP: 美妆护肤 + - FASHION_COS: 仿妆cos + - FASHION_CLOTHING: 穿搭 + - FASHION_TREND: 时尚潮流 + - SPORTS: 运动 + - SPORTS_BASKETBALL: 篮球 + - SPORTS_FOOTBALL: 足球 + - SPORTS_AEROBICS: 健身 + - SPORTS_ATHLETIC: 竞技体育 + - SPORTS_CULTURE: 运动文化 + - SPORTS_COMPREHENSIVE: 运动综合 + - ANIMAL: 动物圈 + - ANIMAL_CAT: 喵星人 + - ANIMAL_DOG: 汪星人 + - ANIMAL_PANDA: 大熊猫 + - ANIMAL_WILD_ANIMAL: 野生动物 + - ANIMAL_REPTILES: 爬宠 + - ANIMAL_COMPOSITE: 动物综合 + - VLOG: VLOG + """ + + MAINPAGE = 0 + + ANIME = 13 + ANIME_SERIAL = 33 + ANIME_FINISH = 32 + ANIME_INFORMATION = 51 + ANIME_OFFICAL = 152 + + MOVIE = 23 + + GUOCHUANG = 167 + GUOCHUANG_CHINESE = 153 + GUOCHUANG_ORIGINAL = 168 + GUOCHUANG_PUPPETRY = 169 + GUOCHUANG_MOTIONCOMIC = 195 + GUOCHUANG_INFORMATION = 170 + + TELEPLAY = 11 + + DOCUMENTARY = 177 + + DOUGA = 1 + DOUGA_MAD = 24 + DOUGA_MMD = 25 + DOUGA_HANDDRAWN = 47 + DOUGA_VOICE = 257 + DOUGA_GARAGE_KIT = 210 + DOUGA_TOKUSATSU = 86 + DOUGA_ACGNTALKS = 253 + DOUGA_OTHER = 27 + + GAME = 4 + GAME_STAND_ALONE = 17 + GAME_ESPORTS = 171 + GAME_MOBILE = 172 + GAME_ONLINE = 65 + GAME_BOARD = 173 + GAME_GMV = 121 + GAME_MUSIC = 136 + GAME_MUGEN = 19 + + KICHIKU = 119 + KICHIKU_GUIDE = 22 + KICHIKU_MAD = 26 + KICHIKU_MANUAL_VOCALOID = 126 + KICHIKU_THEATRE = 216 + KICHIKU_COURSE = 127 + + MUSIC = 3 + MUSIC_ORIGINAL = 28 + MUSIC_COVER = 31 + MUSIC_PERFORM = 59 + MUSIC_VOCALOID = 30 + MUSIC_LIVE = 29 + MUSIC_MV = 193 + MUSIC_COMMENTARY = 243 + MUSIC_TUTORIAL = 244 + MUSIC_OTHER = 130 + + DANCE = 129 + DANCE_OTAKU = 20 + DANCE_HIPHOP = 198 + DANCE_STAR = 199 + DANCE_CHINA = 200 + DANCE_THREE_D = 154 + DANCE_DEMO = 156 + DANCE_GESTURES = 255 + + CINEPHILE = 181 + CINEPHILE_CINECISM = 182 + CINEPHILE_MONTAGE = 183 + CINEPHILE_SHORTPLAY = 85 + CINEPHILE_SHORTFILM = 256 + CINEPHILE_TRAILER_INFO = 184 + + ENT = 5 + ENT_VARIETY = 71 + ENT_TALKER = 241 + ENT_FANS = 242 + ENT_CELEBRITY = 137 + + KNOWLEDGE = 36 + KNOWLEDGE_SCIENCE = 201 + KNOWLEDGE_SOCIAL_SCIENCE = 124 + KNOWLEDGE_HUMANITY_HISTORY = 228 + KNOWLEDGE_BUSINESS = 207 + KNOWLEDGE_CAMPUS = 208 + KNOWLEDGE_CAREER = 209 + KNOWLEDGE_DESIGN = 229 + KNOWLEDGE_SKILL = 122 + + TECH = 188 + TECH_DIGITAL = 95 + TECH_APPLICATION = 230 + TECH_COMPUTER_TECH = 231 + TECH_INDUSTRY = 232 + + INFORMATION = 202 + INFORMATION_HOTSPOT = 203 + INFORMATION_GLOBAL = 204 + INFORMATION_SOCIAL = 205 + INFORMATION_MULTIPLE = 206 + + FOOD = 211 + FOOD_MAKE = 76 + FOOD_DETECTIVE = 212 + FOOD_MEASUREMENT = 213 + FOOD_RURAL = 214 + FOOD_RECORD = 215 + + LIFE = 160 + LIFE_FUNNY = 138 + LIFE_TRAVEL = 250 + LIFE_RURALLIFE = 251 + LIFE_HOME = 239 + LIFE_HANDMAKE = 161 + LIFE_PAINTING = 162 + LIFE_DAILY = 21 + + CAR = 223 + CAR_RACING = 245 + CAR_MODIFIEDVEHICLE = 246 + CAR_NEWENERGYVEHICLE = 246 + CAR_TOURINGCAR = 248 + CAR_MOTORCYCLE = 240 + CAR_STRATEGY = 227 + CAR_LIFE = 176 + + FASHION = 155 + FASHION_MAKEUP = 157 + FASHION_COS = 252 + FASHION_CLOTHING = 158 + FASHION_TREND = 159 + + SPORTS = 234 + SPORTS_BASKETBALL = 235 + SPORTS_FOOTBALL = 249 + SPORTS_AEROBICS = 164 + SPORTS_ATHLETIC = 236 + SPORTS_CULTURE = 237 + SPORTS_COMPREHENSIVE = 238 + + ANIMAL = 217 + ANIMAL_CAT = 218 + ANIMAL_DOG = 219 + ANIMAL_PANDA = 220 + ANIMAL_WILD_ANIMAL = 221 + ANIMAL_REPTILES = 222 + ANIMAL_COMPOSITE = 75 + + VLOG = 19 diff --git a/bilibili_api/vote.py b/bilibili_api/vote.py new file mode 100644 index 0000000000000000000000000000000000000000..e7c7a236fbf79568edd7264f7e50bd0e928f96da --- /dev/null +++ b/bilibili_api/vote.py @@ -0,0 +1,233 @@ +""" +bilibili_api.vote + +投票相关操作。 + +需要 vote_id,获取 vote_id: https://nemo2011.github.io/bilibili-api/#/vote_id +""" +from enum import Enum +from typing import Union, Optional + +from .utils.utils import get_api +from .utils.picture import Picture +from .utils.credential import Credential +from .utils.network import Api + +API = get_api("vote") + + +class VoteType(Enum): + """ + 投票类型枚举类 + + + TEXT: 文字投票 + + IMAGE: 图片投票 + """ + + TEXT = 0 + IMAGE = 1 + + +class VoteChoices: + """ + 投票选项类 + """ + + def __init__(self) -> None: + self.choices = [] + + def add_choice( + self, desc: str, image: Optional[Union[str, Picture]] = None + ) -> "VoteChoices": + """ + 往 VoteChoices 添加选项 + + Args: + desc (str): 选项描述 + + image (str, Picture, optional): 选项的图片链接,用于图片投票。支持 Picture 类. Defaults to None. + """ + if isinstance(image, Picture): + image = image.url + self.choices.append({"desc": desc, "img_url": image}) + return self + + def remove_choice(self, index: int) -> "VoteChoices": + """ + 从 VoteChoices 移除选项 + + Args: + index (int): 选项索引 + """ + self.choices.remove(index) + return self + + def get_choices(self) -> dict: + """ + 获取 VoteChoices 的 choices + + Returns: + dict: choices + """ + results = {} + for i in range(len(self.choices)): + choice_key_name = f"info[options][{i}]" + results[f"{choice_key_name}[desc]"] = self.choices[i]["desc"] + results[f"{choice_key_name}[img_url]"] = self.choices[i]["img_url"] + return results + + +class Vote: + """ + 投票类 + + Attributes: + vote_id (int): vote_id, 获取:https://nemo2011.github.io/bilibili-api/#/vote_id + + credential (Credential): 凭据类 + """ + + def __init__(self, vote_id: int, credential: Credential = Credential()) -> None: + """ + Args: + vote_id (int): vote_id, 获取:https://nemo2011.github.io/bilibili-api/#/vote_id + + credential (Credential): 凭据类,非必要. + """ + self.__vote_id = vote_id + self.credential = credential + self.title: Optional[str] = None + + def get_vote_id(self) -> int: + return self.__vote_id + + def get_info_sync(self) -> dict: + """ + 获取投票详情 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["vote_info"] + params = {"vote_id": self.get_vote_id()} + info = Api(**api, params=params).result_sync + self.title = info["info"]["title"] # 为 dynmaic.BuildDnamic.add_vote 缓存 title + return info + + async def get_info(self) -> dict: + """ + 获取投票详情 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["vote_info"] + params = {"vote_id": self.get_vote_id()} + info = await Api(**api).update_params(**params).result + self.title = info["info"]["title"] # 为 dynmaic.BuildDnamic.add_vote 缓存 title + return info + + async def get_title(self) -> str: + """ + 快速获取投票标题 + + Returns: + str: 投票标题 + """ + if self.title is None: + return (await self.get_info())["info"]["title"] + return self.title + + async def update_vote( + self, + title: str, + _type: VoteType, + choice_cnt: int, + duration: int, + choices: VoteChoices, + desc: Optional[str] = None, + ) -> dict: + """ + 更新投票内容 + + Args: + vote_id (int): vote_id + + title (str): 投票标题 + + _type (VoteType): 投票类型 + + choice_cnt (int): 最多几项 + + duration (int): 投票持续秒数 常用: 三天:259200 七天:604800 三十天:2592000 + + choices (VoteChoices): 投票选项 + + credential (Credential): Credential 枚举类 + + desc (Optional[str], optional): 投票描述. Defaults to None. + + Returns: + dict: 调用 API 返回的结果 + """ + self.credential.raise_for_no_sessdata() + api = API["operate"]["update"] + data = { + "info[title]": title, + "info[desc]": desc, + "info[type]": _type.value, + "info[choice_cnt]": choice_cnt, + "info[duration]": duration, + "info[vote_id]": self.get_vote_id(), + } + data.update(choices.get_choices()) + if choice_cnt > len(choices.choices): + raise ValueError("choice_cnt 大于 choices 选项数") + return await Api(**api, credential=self.credential).update_data(**data).result + + +async def create_vote( + title: str, + _type: VoteType, + choice_cnt: int, + duration: int, + choices: VoteChoices, + credential: Credential, + desc: Optional[str] = None, +) -> Vote: + """ + 创建投票 + + Args: + title (str): 投票标题 + + _type (VoteType): 投票类型 + + choice_cnt (int): 最多几项 + + duration (int): 投票持续秒数 常用: 三天:259200 七天:604800 三十天:2592000 + + choices (VoteChoices): 投票选项 + + credential (Credential): Credential + + desc (Optional[str], optional): 投票描述. Defaults to None. + + Returns: + Vote: Vote 类 + """ + api = API["operate"]["create"] + data = { + "info[title]": title, + "info[desc]": desc, + "info[type]": _type.value, + "info[choice_cnt]": choice_cnt, + "info[duration]": duration, + } + data.update(choices.get_choices()) + if choice_cnt > len(choices.choices): + raise ValueError("choice_cnt 大于 choices 选项数") + vote_id = (await Api(**api, credential=credential).update_data(**data).result)[ + "vote_id" + ] + return Vote(vote_id=vote_id, credential=credential) diff --git a/bilibili_api/watchroom.py b/bilibili_api/watchroom.py new file mode 100644 index 0000000000000000000000000000000000000000..6dc56429b05ff41aec48f58135866b5a8b538d6c --- /dev/null +++ b/bilibili_api/watchroom.py @@ -0,0 +1,398 @@ +""" +bilibili_api.watchroom + +放映室相关 API + +注意,此类操作务必传入 `Credential` 并且要求传入 `buvid3` 否则可能无法鉴权 +""" +import time +from enum import Enum +from typing import Dict, List, Union + +from .utils.credential import Credential +from .utils.network import Api +from .utils.utils import get_api + +API = get_api("watchroom") + + +watch_room_bangumi_cache: Dict[int, List[int]] = {} + + +class SeasonType(Enum): + """ + 季度类型 + + + ANIME: 番剧 + + MOVIE: 电影 + + DOCUMENTARY: 纪录片 + + GUOCHUANG: 国创 + + TV: 电视剧 + + VARIETY: 综艺 + """ + + ANIME = 1 + MOVIE = 2 + DOCUMENTARY = 3 + GUOCHUANG = 4 + TV = 5 + VARIETY = 7 + + +class MessageType(Enum): + """ + 消息类型 + + + PLAIN: 纯文本 + + EMOJI: 表情 + """ + + PLAIN = "plain" + EMOJI = "emoji" + + +class MessageSegment: + """ + 消息片段 + + Args: + msg (str) : 信息 + + is_emoji (bool): 是否为表情包 + """ + + def __init__(self, msg: str, is_emoji: bool = False): + self.msg = msg + self.msg_type = MessageType.EMOJI if is_emoji else MessageType.PLAIN + + def __repr__(self) -> str: + if self.msg_type == MessageType.EMOJI: + return f"[{self.msg}]" + return self.msg + + +class Message: + """ + 消息集合 + """ + + def __init__(self, *messages: Union[MessageSegment, str]): + self.msg_list: List[MessageSegment] = [] + for msg in messages: + if isinstance(msg, str): + self.msg_list.append(MessageSegment(msg)) + else: + self.msg_list.append(msg) + + def __add__(self, msg: Union[MessageSegment, "Message"]): + if isinstance(msg, MessageSegment): + return Message(*self.msg_list, msg) + elif isinstance(msg, Message): + return Message(*self.msg_list, *msg.msg_list) + raise TypeError + + def __str__(self) -> str: + return "".join(str(msg) for msg in self.msg_list) + + def __repr__(self) -> str: + return str(self.msg_list) + + +class WatchRoom: + """ + 放映室类 + """ + + __season_id: int + __episode_id: int + + def __init__(self, room_id: int, credential: Credential = None): + """ + Args: + + credential (Credential): 凭据类 (大部分用户操作都需要与之匹配的 buvid3 值,务必在 credential 传入) + + room_id (int) : 放映室 id + """ + credential = credential if credential else Credential() + self.__room_id = room_id + self.credential = credential + self.credential.raise_for_no_sessdata() + self.credential.raise_for_no_bili_jct() + self.credential.raise_for_no_buvid3() + if room_id in watch_room_bangumi_cache.keys(): + self.set_season_id(watch_room_bangumi_cache[room_id][0]) + self.set_episode_id(watch_room_bangumi_cache[room_id][1]) + else: + params = {"room_id": self.get_room_id(), "platform": "web"} + info: dict = ( + Api(credential=self.credential, **API["info"]["info"]) + .update_params(**params) + .result_sync + ) + self.set_season_id(info["status"]["season_id"]) + self.set_episode_id(info["status"]["episode_id"]) + + def set_season_id(self, season_id: int): + self.__season_id = season_id + + def set_episode_id(self, episode_id: int): + self.__episode_id = episode_id + + def get_season_id(self): + return self.__season_id + + def get_episode_id(self): + return self.__episode_id + + def get_room_id(self): + return self.__room_id + + async def get_info(self) -> dict: + """ + 获取放映室信息,播放进度等 + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["info"]["info"] + params = {"room_id": self.get_room_id(), "platform": "web"} + return ( + await Api(credential=self.credential, **api).update_params(**params).result + ) + + async def open(self) -> None: + """ + 开放放映室 + """ + api = API["operate"]["open"] + data = { + "room_id": self.get_room_id(), + "is_open": 1, + "csrf": self.credential.bili_jct, + "platform": "web", + } + return ( + await Api(credential=self.credential, no_csrf=True, **api) + .update_data(**data) + .result + ) + + async def close(self) -> None: + """ + 关闭放映室 + """ + api = API["operate"]["open"] + data = { + "room_id": self.get_room_id(), + "is_open": 0, + "csrf": self.credential.bili_jct, + "platform": "web", + } + return ( + await Api(credential=self.credential, no_csrf=True, **api) + .update_data(**data) + .result + ) + + async def progress(self, progress: int = None, status: int = 1) -> None: + """ + 设置播放状态,包括暂停与进度条 + + Args: + + progress (int, None) 进度,单位为秒 + + status (bool, None) 播放状态 1 播放中 0 暂停中 2 已结束 + """ + api = API["operate"]["progress"] + data = { + "room_id": self.get_room_id(), + "progress": progress, + "status": status if status in [1, 2, 0] else 1, + "csrf": self.credential.bili_jct, + "platform": "web", + } + return ( + await Api(credential=self.credential, no_csrf=True, **api) + .update_data(**data) + .result + ) + + async def join(self, token: str = "") -> dict: + """ + 加入放映室 + + Args: + + token (str, Optional) 邀请 Token + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["operate"]["join"] + data = { + "room_id": self.get_room_id(), + "token": token, + "csrf": self.credential.bili_jct, + "platform": "web", + } + res = ( + await Api(credential=self.credential, no_csrf=True, **api) + .update_data(**data) + .result + ) + self.set_season_id(res["season_id"]) + self.set_episode_id(res["episode_id"]) + return res + + async def send(self, msg: Message) -> dict: + """ + 发送消息 + + Args: + + msg (Message) 消息 + + Returns: + dict: 调用 API 返回的结果 + """ + data = { + "room_id": self.get_room_id(), + "content_type": 0, + "content": '{"text":"%s"}' % msg, + "req_id": int(time.time()) * 1000, + "platform": "web", + "csrf": self.credential.bili_jct, + } + api = API["operate"]["send"] + return ( + await Api(credential=self.credential, no_csrf=True, **api) + .update_data(**data) + .result + ) + + async def kickout(self, uid: int) -> dict: + """ + 踢出放映室 + + Args: + + uid (int) 用户 uid + + Returns: + dict: 调用 API 返回的结果 + """ + api = API["operate"]["kickout"] + data = { + "room_id": self.get_room_id(), + "mid": uid, + "csrf": self.credential.bili_jct, + "platform": "web", + } + return ( + await Api(credential=self.credential, no_csrf=True, **api) + .update_data(**data) + .result + ) + + async def share(self) -> str: + """ + 获取邀请 Token + + Returns: + str: 邀请 Token + """ + api = API["info"]["season"] + params = { + "room_id": self.get_room_id(), + "season_id": self.get_season_id(), + "ep_id": self.get_episode_id(), + "csrf": self.credential.bili_jct, + "platform": "web", + } + res = ( + await Api(credential=self.credential, no_csrf=True, **api) + .update_params(**params) + .result + ) + return res["room_info"]["share_url"].split("&token=")[-1] + + +async def create( + season_id: int, + episode_id: int, + is_open: bool = False, + credential: Credential = None, +) -> WatchRoom: + """ + 创建放映室 + + Args: + + season_id (int) 每季度的 ID + + ep_id (int) 剧集 ID + + is_open (bool) 是否公开 + + credential (Credential) 凭据 + + Returns: + Watchroom:放映室 + """ + global watch_room_bangumi_cache + + if credential is None: + credential = Credential() + + api = API["operate"]["create"] + data = { + "season_id": season_id, + "episode_id": episode_id, + "is_open": 1 if is_open else 0, + "csrf": credential.bili_jct, + "platform": "web", + } + room_id = ( + await Api(credential=credential, no_csrf=True, **api).update_data(**data).result + )["room_id"] + watch_room_bangumi_cache[room_id] = [season_id, episode_id] + return WatchRoom(room_id=room_id, credential=credential) + + +async def match( + season_id: int, + season_type: SeasonType = SeasonType.ANIME, + credential: Credential = None, +) -> WatchRoom: + """ + 匹配放映室 + + Args: + + season_id (int) 季度 ID + + season_type (str) 季度类型 + + Returns: + Watchroom:放映室 + """ + if credential is None: + credential = Credential() + + api = API["operate"]["match"] + data = { + "season_id": season_id, + "season_type": season_type.value, + "csrf": credential.bili_jct, + "platform": "web", + } + return WatchRoom( + ( + await Api(credential=credential, no_csrf=True, **api) + .update_data(**data) + .result + )["room_id"], + credential=credential, + )