fix bugs
This commit is contained in:
102
blivedm/clients/wbi.py
Normal file
102
blivedm/clients/wbi.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import urllib.parse
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
from ..utils import USER_AGENT
|
||||||
|
|
||||||
|
logger = logging.getLogger("blivedm")
|
||||||
|
|
||||||
|
UID_INIT_URL = "https://api.bilibili.com/x/web-interface/nav"
|
||||||
|
|
||||||
|
WTS = "wts"
|
||||||
|
W_RID = "w_rid"
|
||||||
|
|
||||||
|
KEY_LENGTH = 32
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
|
KEY_MAP = [
|
||||||
|
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,
|
||||||
|
]
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
INVALID_CHARS = ["!", "'", "(", ")", "*"]
|
||||||
|
|
||||||
|
|
||||||
|
def filtered_string(s: str) -> str:
|
||||||
|
return "".join(c for c in s if c not in INVALID_CHARS)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_key_part(url: str) -> str:
|
||||||
|
slash = url.rfind("/")
|
||||||
|
if slash == -1:
|
||||||
|
raise ValueError("missing url slash")
|
||||||
|
dot = url[slash:].find(".")
|
||||||
|
if dot == -1:
|
||||||
|
raise ValueError("missing url dot")
|
||||||
|
return url[slash + 1 : slash + dot]
|
||||||
|
|
||||||
|
|
||||||
|
def sign_content_with_key(content: str, key: str) -> str:
|
||||||
|
hasher = hashlib.md5()
|
||||||
|
hasher.update(f"{content}{key}".encode())
|
||||||
|
return hasher.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
async def get_wbi_key(session: aiohttp.ClientSession) -> str:
|
||||||
|
async with session.get(
|
||||||
|
UID_INIT_URL,
|
||||||
|
headers={"User-Agent": USER_AGENT},
|
||||||
|
) as response:
|
||||||
|
data = await response.json()
|
||||||
|
|
||||||
|
wbi_img = data["data"]["wbi_img"]
|
||||||
|
img = extract_key_part(wbi_img["img_url"])
|
||||||
|
sub = extract_key_part(wbi_img["sub_url"])
|
||||||
|
|
||||||
|
if not img or not sub:
|
||||||
|
raise ValueError("missing wbi key")
|
||||||
|
|
||||||
|
full = img + sub
|
||||||
|
key_chars = ["\0"] * KEY_LENGTH
|
||||||
|
for i, index in enumerate(KEY_MAP[:KEY_LENGTH]):
|
||||||
|
key_chars[i] = full[index] if index < len(full) else "\0"
|
||||||
|
|
||||||
|
return "".join(key_chars)
|
||||||
|
|
||||||
|
|
||||||
|
async def signed_query(
|
||||||
|
session: aiohttp.ClientSession, query: Dict[str, Any]
|
||||||
|
) -> Dict[str, str]:
|
||||||
|
ts = str(int(time.time()))
|
||||||
|
|
||||||
|
filtered_query = []
|
||||||
|
for k, v in query.items():
|
||||||
|
filtered_query.append((k, filtered_string(str(v))))
|
||||||
|
|
||||||
|
filtered_query.append((WTS, ts))
|
||||||
|
filtered_query.sort(key=lambda x: x[0])
|
||||||
|
|
||||||
|
content = urllib.parse.urlencode(filtered_query)
|
||||||
|
|
||||||
|
try:
|
||||||
|
key = await get_wbi_key(session)
|
||||||
|
except (aiohttp.ClientConnectionError, aiohttp.ClientResponseError, ValueError):
|
||||||
|
logger.exception("get_wbi_key() failed:")
|
||||||
|
return query
|
||||||
|
|
||||||
|
query_sign = sign_content_with_key(content, key)
|
||||||
|
|
||||||
|
signed_query = {
|
||||||
|
**query,
|
||||||
|
WTS: ts,
|
||||||
|
W_RID: query_sign,
|
||||||
|
}
|
||||||
|
|
||||||
|
return signed_query
|
||||||
@@ -6,8 +6,9 @@ from typing import *
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
import yarl
|
import yarl
|
||||||
|
|
||||||
from . import ws_base
|
|
||||||
from .. import utils
|
from .. import utils
|
||||||
|
from . import ws_base
|
||||||
|
from .wbi import UID_INIT_URL, signed_query
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'BLiveClient',
|
'BLiveClient',
|
||||||
@@ -15,7 +16,6 @@ __all__ = (
|
|||||||
|
|
||||||
logger = logging.getLogger('blivedm')
|
logger = logging.getLogger('blivedm')
|
||||||
|
|
||||||
UID_INIT_URL = 'https://api.bilibili.com/x/web-interface/nav'
|
|
||||||
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'
|
||||||
@@ -35,12 +35,12 @@ class BLiveClient(ws_base.WebSocketClientBase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
room_id: int,
|
room_id: int,
|
||||||
*,
|
*,
|
||||||
uid: Optional[int] = None,
|
uid: Optional[int] = None,
|
||||||
session: Optional[aiohttp.ClientSession] = None,
|
session: Optional[aiohttp.ClientSession] = None,
|
||||||
heartbeat_interval=30,
|
heartbeat_interval=30,
|
||||||
):
|
):
|
||||||
super().__init__(session, heartbeat_interval)
|
super().__init__(session, heartbeat_interval)
|
||||||
|
|
||||||
@@ -50,8 +50,6 @@ class BLiveClient(ws_base.WebSocketClientBase):
|
|||||||
|
|
||||||
# 在调用init_room后初始化的字段
|
# 在调用init_room后初始化的字段
|
||||||
self._room_owner_uid: Optional[int] = None
|
self._room_owner_uid: Optional[int] = None
|
||||||
self.live_status: Optional[int] = None
|
|
||||||
self.live_start_time: Optional[int] = None
|
|
||||||
"""主播用户ID"""
|
"""主播用户ID"""
|
||||||
self._host_server_list: Optional[List[dict]] = None
|
self._host_server_list: Optional[List[dict]] = None
|
||||||
"""
|
"""
|
||||||
@@ -122,8 +120,8 @@ class BLiveClient(ws_base.WebSocketClientBase):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
async with self._session.get(
|
async with self._session.get(
|
||||||
UID_INIT_URL,
|
UID_INIT_URL,
|
||||||
headers={'User-Agent': utils.USER_AGENT},
|
headers={'User-Agent': utils.USER_AGENT},
|
||||||
) as res:
|
) as res:
|
||||||
if res.status != 200:
|
if res.status != 200:
|
||||||
logger.warning('room=%d _init_uid() failed, status=%d, reason=%s', self._tmp_room_id,
|
logger.warning('room=%d _init_uid() failed, status=%d, reason=%s', self._tmp_room_id,
|
||||||
@@ -160,8 +158,8 @@ class BLiveClient(ws_base.WebSocketClientBase):
|
|||||||
async def _init_buvid(self):
|
async def _init_buvid(self):
|
||||||
try:
|
try:
|
||||||
async with self._session.get(
|
async with self._session.get(
|
||||||
BUVID_INIT_URL,
|
BUVID_INIT_URL,
|
||||||
headers={'User-Agent': utils.USER_AGENT},
|
headers={'User-Agent': utils.USER_AGENT},
|
||||||
) as res:
|
) as res:
|
||||||
if res.status != 200:
|
if res.status != 200:
|
||||||
logger.warning('room=%d _init_buvid() status error, status=%d, reason=%s',
|
logger.warning('room=%d _init_buvid() status error, status=%d, reason=%s',
|
||||||
@@ -173,11 +171,11 @@ class BLiveClient(ws_base.WebSocketClientBase):
|
|||||||
async def _init_room_id_and_owner(self):
|
async def _init_room_id_and_owner(self):
|
||||||
try:
|
try:
|
||||||
async with self._session.get(
|
async with self._session.get(
|
||||||
ROOM_INIT_URL,
|
ROOM_INIT_URL,
|
||||||
headers={'User-Agent': utils.USER_AGENT},
|
headers={'User-Agent': utils.USER_AGENT},
|
||||||
params={
|
params={
|
||||||
'room_id': self._tmp_room_id
|
'room_id': self._tmp_room_id
|
||||||
},
|
},
|
||||||
) as res:
|
) as res:
|
||||||
if res.status != 200:
|
if res.status != 200:
|
||||||
logger.warning('room=%d _init_room_id_and_owner() failed, status=%d, reason=%s', self._tmp_room_id,
|
logger.warning('room=%d _init_room_id_and_owner() failed, status=%d, reason=%s', self._tmp_room_id,
|
||||||
@@ -203,12 +201,12 @@ class BLiveClient(ws_base.WebSocketClientBase):
|
|||||||
async def _init_host_server(self):
|
async def _init_host_server(self):
|
||||||
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={
|
params=await signed_query(self._session, {
|
||||||
'id': self._room_id,
|
'id': self._room_id,
|
||||||
'type': 0
|
'type': 0
|
||||||
},
|
}),
|
||||||
) as res:
|
) as res:
|
||||||
if res.status != 200:
|
if res.status != 200:
|
||||||
logger.warning('room=%d _init_host_server() failed, status=%d, reason=%s', self._room_id,
|
logger.warning('room=%d _init_host_server() failed, status=%d, reason=%s', self._room_id,
|
||||||
@@ -264,4 +262,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))
|
||||||
4
main.py
4
main.py
@@ -111,8 +111,8 @@ async def connect_room(room_id):
|
|||||||
room_status_log.write(json.dumps(
|
room_status_log.write(json.dumps(
|
||||||
{
|
{
|
||||||
"room_id": room_id,
|
"room_id": room_id,
|
||||||
"live_status": client.live_status,
|
# "live_status": client.live_status,
|
||||||
"live_start_time": client.live_start_time,
|
# "live_start_time": client.live_start_time,
|
||||||
},
|
},
|
||||||
separators=(",", ":"),
|
separators=(",", ":"),
|
||||||
ensure_ascii=False,
|
ensure_ascii=False,
|
||||||
|
|||||||
Reference in New Issue
Block a user