From d55f001c670ea900a1e55eb0146ca2ececa0a97f Mon Sep 17 00:00:00 2001 From: John Smith Date: Sat, 2 Sep 2023 12:40:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=87=AA=E5=8A=A8=E8=8E=B7?= =?UTF-8?q?=E5=8F=96UID=E3=80=81=E6=B7=BB=E5=8A=A0=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E5=B7=B2=E7=99=BB=E5=BD=95=E8=B4=A6=E5=8F=B7=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E7=9A=84=E6=BC=94=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blivedm/client.py | 100 ++++++++++++++++++++++++++++++++++++++-------- requirements.txt | 1 + sample.py | 22 +++++++++- 3 files changed, 105 insertions(+), 18 deletions(-) diff --git a/blivedm/client.py b/blivedm/client.py index 66b63d3..b066754 100644 --- a/blivedm/client.py +++ b/blivedm/client.py @@ -9,6 +9,7 @@ from typing import * import aiohttp import brotli +import yarl from . import handlers @@ -18,6 +19,12 @@ __all__ = ( logger = logging.getLogger('blivedm') +USER_AGENT = ( + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36' +) + +UID_INIT_URL = 'https://api.bilibili.com/x/web-interface/nav' +BUVID_INIT_URL = 'https://data.bilibili.com/v/' ROOM_INIT_URL = 'https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom' DANMAKU_SERVER_CONF_URL = 'https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo' DEFAULT_DANMAKU_SERVER_LIST = [ @@ -87,7 +94,7 @@ class BLiveClient: B站直播弹幕客户端,负责连接房间 :param room_id: URL中的房间ID,可以用短ID - :param uid: B站用户ID,0表示未登录 + :param uid: B站用户ID,0表示未登录,None表示自动获取 :param session: cookie、连接池 :param heartbeat_interval: 发送心跳包的间隔时间(秒) :param ssl: True表示用默认的SSLContext验证,False表示不验证,也可以传入SSLContext @@ -96,7 +103,7 @@ class BLiveClient: def __init__( self, room_id, - uid=0, + uid=None, session: Optional[aiohttp.ClientSession] = None, heartbeat_interval=30, ssl: Union[bool, ssl_.SSLContext] = True, @@ -170,6 +177,13 @@ class BLiveClient: """ return self._room_owner_uid + @property + def uid(self) -> Optional[int]: + """ + 当前登录的用户ID,未登录则为0,调用init_room后初始化 + """ + return self._uid + def add_handler(self, handler: 'handlers.HandlerInterface'): """ 添加消息处理器 @@ -248,6 +262,15 @@ class BLiveClient: :return: True代表没有降级,如果需要降级后还可用,重载这个函数返回True """ + if self._uid is None: + if not await self._init_uid(): + logger.warning('room=%d _init_uid() failed', self._tmp_room_id) + self._uid = 0 + + if self._get_buvid() == '': + if not await self._init_buvid(): + logger.warning('room=%d _init_buvid() failed', self._tmp_room_id) + res = True if not await self._init_room_id_and_owner(): res = False @@ -262,14 +285,64 @@ class BLiveClient: self._host_server_token = None return res + async def _init_uid(self): + try: + async with self._session.get( + UID_INIT_URL, + headers={'User-Agent': USER_AGENT}, + ssl=self._ssl + ) as res: + if res.status != 200: + logger.warning('room=%d _init_uid() failed, status=%d, reason=%s', self._tmp_room_id, + res.status, res.reason) + return False + data = await res.json() + if data['code'] != 0: + if data['code'] == -101: + # 未登录 + self._uid = 0 + return True + logger.warning('room=%d _init_uid() failed, message=%s', self._tmp_room_id, + data['message']) + return False + + data = data['data'] + if not data['isLogin']: + # 未登录 + self._uid = 0 + else: + self._uid = data['mid'] + return True + except (aiohttp.ClientConnectionError, asyncio.TimeoutError): + logger.exception('room=%d _init_uid() failed:', self._tmp_room_id) + return False + + def _get_buvid(self): + cookies = self._session.cookie_jar.filter_cookies(yarl.URL(BUVID_INIT_URL)) + buvid_cookie = cookies.get('buvid3', None) + if buvid_cookie is None: + return '' + return buvid_cookie.value + + async def _init_buvid(self): + try: + async with self._session.get( + BUVID_INIT_URL, + headers={'User-Agent': USER_AGENT}, + ssl=self._ssl + ) as res: + if res.status != 200: + logger.warning('room=%d _init_buvid() status error, status=%d, reason=%s', + self._tmp_room_id, res.status, res.reason) + except (aiohttp.ClientConnectionError, asyncio.TimeoutError): + logger.exception('room=%d _init_buvid() exception:', self._tmp_room_id) + return self._get_buvid() != '' + async def _init_room_id_and_owner(self): try: async with self._session.get( ROOM_INIT_URL, - headers={ - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)' - ' Chrome/102.0.0.0 Safari/537.36' - }, + headers={'User-Agent': USER_AGENT}, params={ 'room_id': self._tmp_room_id }, @@ -302,10 +375,7 @@ class BLiveClient: try: async with self._session.get( DANMAKU_SERVER_CONF_URL, - headers={ - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)' - ' Chrome/102.0.0.0 Safari/537.36' - }, + headers={'User-Agent': USER_AGENT}, params={ 'id': self._room_id, 'type': 0 @@ -385,10 +455,7 @@ class BLiveClient: host_server = self._host_server_list[retry_count % len(self._host_server_list)] async with self._session.ws_connect( f"wss://{host_server['host']}:{host_server['wss_port']}/sub", - headers={ - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)' - ' Chrome/102.0.0.0 Safari/537.36' - }, + headers={'User-Agent': USER_AGENT}, receive_timeout=self._heartbeat_interval + 5, ssl=self._ssl ) as websocket: @@ -444,11 +511,12 @@ class BLiveClient: 发送认证包 """ auth_params = { - 'uid': self._uid or self.room_owner_uid or 0, + 'uid': self._uid, 'roomid': self._room_id, 'protover': 3, 'platform': 'web', - 'type': 2 + 'type': 2, + 'buvid': self._get_buvid(), } if self._host_server_token is not None: auth_params['key'] = self._host_server_token diff --git a/requirements.txt b/requirements.txt index 78bc086..c6f8312 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ aiohttp==3.8.5 Brotli==1.0.9 pure-protobuf==3.0.0a5 +yarl==1.8.2 diff --git a/sample.py b/sample.py index 4e014d4..10e8e4d 100644 --- a/sample.py +++ b/sample.py @@ -1,7 +1,10 @@ # -*- coding: utf-8 -*- import asyncio +import http.cookies import random +import aiohttp + import blivedm # 直播间ID的取值看直播间URL @@ -13,19 +16,34 @@ TEST_ROOM_IDS = [ 23105590, ] +session = None + async def main(): + init_session() + await run_single_client() await run_multi_clients() +def init_session(): + # 这里填一个已登录账号的cookie。不填cookie也可以连接,但是收到弹幕的用户名会打码,UID会变成0 + cookies = http.cookies.SimpleCookie() + cookies['SESSDATA'] = '' + cookies['SESSDATA']['domain'] = 'bilibili.com' + + global session + session = aiohttp.ClientSession() + session.cookie_jar.update_cookies(cookies) + + async def run_single_client(): """ 演示监听一个直播间 """ room_id = random.choice(TEST_ROOM_IDS) # 如果SSL验证失败就把ssl设为False,B站真的有过忘续证书的情况 - client = blivedm.BLiveClient(room_id, ssl=True) + client = blivedm.BLiveClient(room_id, session=session, ssl=True) handler = MyHandler() client.add_handler(handler) @@ -44,7 +62,7 @@ async def run_multi_clients(): """ 演示同时监听多个直播间 """ - clients = [blivedm.BLiveClient(room_id) for room_id in TEST_ROOM_IDS] + clients = [blivedm.BLiveClient(room_id, session=session) for room_id in TEST_ROOM_IDS] handler = MyHandler() for client in clients: client.add_handler(handler)