Merge remote-tracking branch 'refs/remotes/github/dev' into tmp
# Conflicts: # blivedm/clients/web.py
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
__version__ = '1.1.2'
|
__version__ = '1.1.3'
|
||||||
|
|
||||||
from .handlers import *
|
from .handlers import *
|
||||||
from .clients import *
|
from .clients import *
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import datetime
|
||||||
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
|
import urllib
|
||||||
|
import weakref
|
||||||
from typing import *
|
from typing import *
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import yarl
|
import yarl
|
||||||
|
|
||||||
from .. import utils
|
|
||||||
from . import ws_base
|
from . import ws_base
|
||||||
from .wbi import UID_INIT_URL, signed_query
|
from .. import utils
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'BLiveClient',
|
'BLiveClient',
|
||||||
@@ -16,6 +19,8 @@ __all__ = (
|
|||||||
|
|
||||||
logger = logging.getLogger('blivedm')
|
logger = logging.getLogger('blivedm')
|
||||||
|
|
||||||
|
UID_INIT_URL = 'https://api.bilibili.com/x/web-interface/nav'
|
||||||
|
WBI_INIT_URL = UID_INIT_URL
|
||||||
BUVID_INIT_URL = 'https://www.bilibili.com/'
|
BUVID_INIT_URL = 'https://www.bilibili.com/'
|
||||||
ROOM_INIT_URL = 'https://api.live.bilibili.com/room/v1/Room/get_info'
|
ROOM_INIT_URL = 'https://api.live.bilibili.com/room/v1/Room/get_info'
|
||||||
DANMAKU_SERVER_CONF_URL = 'https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo'
|
DANMAKU_SERVER_CONF_URL = 'https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo'
|
||||||
@@ -23,6 +28,127 @@ DEFAULT_DANMAKU_SERVER_LIST = [
|
|||||||
{'host': 'broadcastlv.chat.bilibili.com', 'port': 2243, 'wss_port': 443, 'ws_port': 2244}
|
{'host': 'broadcastlv.chat.bilibili.com', 'port': 2243, 'wss_port': 443, 'ws_port': 2244}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
_session_to_wbi_signer = weakref.WeakKeyDictionary()
|
||||||
|
|
||||||
|
|
||||||
|
def _get_wbi_signer(session: aiohttp.ClientSession) -> '_WbiSigner':
|
||||||
|
wbi_signer = _session_to_wbi_signer.get(session, None)
|
||||||
|
if wbi_signer is None:
|
||||||
|
wbi_signer = _session_to_wbi_signer[session] = _WbiSigner(session)
|
||||||
|
return wbi_signer
|
||||||
|
|
||||||
|
|
||||||
|
class _WbiSigner:
|
||||||
|
WBI_KEY_INDEX_TABLE = [
|
||||||
|
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
|
||||||
|
]
|
||||||
|
"""wbi密码表"""
|
||||||
|
WBI_KEY_TTL = datetime.timedelta(hours=11, minutes=59, seconds=30)
|
||||||
|
|
||||||
|
def __init__(self, session: aiohttp.ClientSession):
|
||||||
|
self._session = session
|
||||||
|
|
||||||
|
self._wbi_key = ''
|
||||||
|
"""缓存的wbi鉴权口令"""
|
||||||
|
self._refresh_future: Optional[Awaitable] = None
|
||||||
|
"""用来避免同时刷新"""
|
||||||
|
self._last_refresh_time: Optional[datetime.datetime] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def wbi_key(self):
|
||||||
|
"""
|
||||||
|
缓存的wbi鉴权口令
|
||||||
|
"""
|
||||||
|
return self._wbi_key
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self._wbi_key = ''
|
||||||
|
self._last_refresh_time = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def need_refresh_wbi_key(self):
|
||||||
|
return self._wbi_key == '' or (
|
||||||
|
self._last_refresh_time is not None
|
||||||
|
and datetime.datetime.now() - self._last_refresh_time >= self.WBI_KEY_TTL
|
||||||
|
)
|
||||||
|
|
||||||
|
def refresh_wbi_key(self) -> Awaitable:
|
||||||
|
if self._refresh_future is None:
|
||||||
|
self._refresh_future = asyncio.create_task(self._do_refresh_wbi_key())
|
||||||
|
|
||||||
|
def on_done(_fu):
|
||||||
|
self._refresh_future = None
|
||||||
|
self._refresh_future.add_done_callback(on_done)
|
||||||
|
|
||||||
|
return self._refresh_future
|
||||||
|
|
||||||
|
async def _do_refresh_wbi_key(self):
|
||||||
|
wbi_key = await self._get_wbi_key()
|
||||||
|
if wbi_key == '':
|
||||||
|
return
|
||||||
|
|
||||||
|
self._wbi_key = wbi_key
|
||||||
|
self._last_refresh_time = datetime.datetime.now()
|
||||||
|
|
||||||
|
async def _get_wbi_key(self):
|
||||||
|
try:
|
||||||
|
async with self._session.get(
|
||||||
|
WBI_INIT_URL,
|
||||||
|
headers={'User-Agent': utils.USER_AGENT},
|
||||||
|
) as res:
|
||||||
|
if res.status != 200:
|
||||||
|
logger.warning('WbiSigner failed to get wbi key: status=%d %s', res.status, res.reason)
|
||||||
|
return ''
|
||||||
|
data = await res.json()
|
||||||
|
except (aiohttp.ClientConnectionError, asyncio.TimeoutError):
|
||||||
|
logger.exception('WbiSigner failed to get wbi key:')
|
||||||
|
return ''
|
||||||
|
|
||||||
|
try:
|
||||||
|
wbi_img = data['data']['wbi_img']
|
||||||
|
img_key = wbi_img['img_url'].rpartition('/')[2].partition('.')[0]
|
||||||
|
sub_key = wbi_img['sub_url'].rpartition('/')[2].partition('.')[0]
|
||||||
|
except KeyError:
|
||||||
|
logger.warning('WbiSigner failed to get wbi key: data=%s', data)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
shuffled_key = img_key + sub_key
|
||||||
|
wbi_key = []
|
||||||
|
for index in self.WBI_KEY_INDEX_TABLE:
|
||||||
|
if index < len(shuffled_key):
|
||||||
|
wbi_key.append(shuffled_key[index])
|
||||||
|
return ''.join(wbi_key)
|
||||||
|
|
||||||
|
def add_wbi_sign(self, params: dict):
|
||||||
|
if self._wbi_key == '':
|
||||||
|
return params
|
||||||
|
|
||||||
|
wts = str(int(datetime.datetime.now().timestamp()))
|
||||||
|
params_to_sign = {**params, 'wts': wts}
|
||||||
|
|
||||||
|
# 按key字典序排序
|
||||||
|
params_to_sign = {
|
||||||
|
key: params_to_sign[key]
|
||||||
|
for key in sorted(params_to_sign.keys())
|
||||||
|
}
|
||||||
|
# 过滤一些字符
|
||||||
|
for key, value in params_to_sign.items():
|
||||||
|
value = ''.join(
|
||||||
|
ch
|
||||||
|
for ch in str(value)
|
||||||
|
if ch not in "!'()*"
|
||||||
|
)
|
||||||
|
params_to_sign[key] = value
|
||||||
|
|
||||||
|
str_to_sign = urllib.parse.urlencode(params_to_sign) + self._wbi_key
|
||||||
|
w_rid = hashlib.md5(str_to_sign.encode('utf-8')).hexdigest()
|
||||||
|
return {
|
||||||
|
**params,
|
||||||
|
'wts': wts,
|
||||||
|
'w_rid': w_rid
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class BLiveClient(ws_base.WebSocketClientBase):
|
class BLiveClient(ws_base.WebSocketClientBase):
|
||||||
"""
|
"""
|
||||||
@@ -43,6 +169,7 @@ class BLiveClient(ws_base.WebSocketClientBase):
|
|||||||
heartbeat_interval=30,
|
heartbeat_interval=30,
|
||||||
):
|
):
|
||||||
super().__init__(session, heartbeat_interval)
|
super().__init__(session, heartbeat_interval)
|
||||||
|
self._wbi_signer = _get_wbi_signer(self._session)
|
||||||
|
|
||||||
self._tmp_room_id = room_id
|
self._tmp_room_id = room_id
|
||||||
"""用来init_room的临时房间ID,可以用短ID"""
|
"""用来init_room的临时房间ID,可以用短ID"""
|
||||||
@@ -199,11 +326,18 @@ class BLiveClient(ws_base.WebSocketClientBase):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
async def _init_host_server(self):
|
async def _init_host_server(self):
|
||||||
|
if self._wbi_signer.need_refresh_wbi_key:
|
||||||
|
await self._wbi_signer.refresh_wbi_key()
|
||||||
|
# 如果没刷新成功先用旧的key
|
||||||
|
if self._wbi_signer.wbi_key == '':
|
||||||
|
logger.exception('room=%d _init_host_server() failed: no wbi key', self._room_id)
|
||||||
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with self._session.get(
|
async with self._session.get(
|
||||||
DANMAKU_SERVER_CONF_URL,
|
DANMAKU_SERVER_CONF_URL,
|
||||||
headers={'User-Agent': utils.USER_AGENT},
|
headers={'User-Agent': utils.USER_AGENT},
|
||||||
params=await signed_query(self._session, {
|
params=self._wbi_signer.add_wbi_sign({
|
||||||
'id': self._room_id,
|
'id': self._room_id,
|
||||||
'type': 0
|
'type': 0
|
||||||
}),
|
}),
|
||||||
@@ -214,6 +348,9 @@ class BLiveClient(ws_base.WebSocketClientBase):
|
|||||||
return False
|
return False
|
||||||
data = await res.json()
|
data = await res.json()
|
||||||
if data['code'] != 0:
|
if data['code'] != 0:
|
||||||
|
if data['code'] == -352:
|
||||||
|
# wbi签名错误
|
||||||
|
self._wbi_signer.reset()
|
||||||
logger.warning('room=%d _init_host_server() failed, message=%s', self._room_id, data['message'])
|
logger.warning('room=%d _init_host_server() failed, message=%s', self._room_id, data['message'])
|
||||||
return False
|
return False
|
||||||
if not self._parse_danmaku_server_conf(data['data']):
|
if not self._parse_danmaku_server_conf(data['data']):
|
||||||
@@ -262,4 +399,4 @@ class BLiveClient(ws_base.WebSocketClientBase):
|
|||||||
}
|
}
|
||||||
if self._host_server_token is not None:
|
if self._host_server_token is not None:
|
||||||
auth_params['key'] = self._host_server_token
|
auth_params['key'] = self._host_server_token
|
||||||
await self._websocket.send_bytes(self._make_packet(auth_params, ws_base.Operation.AUTH))
|
await self._websocket.send_bytes(self._make_packet(auth_params, ws_base.Operation.AUTH))
|
||||||
|
|||||||
Reference in New Issue
Block a user