2022-02-15 00:18:46 +08:00
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
import asyncio
|
2023-09-07 00:56:32 +08:00
|
|
|
|
import enum
|
2022-02-15 00:18:46 +08:00
|
|
|
|
import logging
|
|
|
|
|
import uuid
|
|
|
|
|
from typing import *
|
|
|
|
|
|
|
|
|
|
import api.chat
|
2023-09-08 20:53:04 +08:00
|
|
|
|
import api.open_live as api_open_live
|
2022-02-15 00:18:46 +08:00
|
|
|
|
import blivedm.blivedm as blivedm
|
2023-09-09 17:13:53 +08:00
|
|
|
|
import blivedm.blivedm.models.open_live as dm_open_models
|
2023-09-03 22:53:15 +08:00
|
|
|
|
import blivedm.blivedm.models.web as dm_web_models
|
2023-09-15 22:47:17 +08:00
|
|
|
|
import blivedm.blivedm.utils as dm_utils
|
2022-02-15 00:18:46 +08:00
|
|
|
|
import config
|
|
|
|
|
import services.avatar
|
|
|
|
|
import services.translate
|
|
|
|
|
import utils.request
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
|
|
|
|
|
class RoomKeyType(enum.IntEnum):
|
|
|
|
|
ROOM_ID = 1
|
|
|
|
|
AUTH_CODE = 2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RoomKey(NamedTuple):
|
|
|
|
|
"""内部用来标识一个房间,由客户端加入房间时传入"""
|
|
|
|
|
type: RoomKeyType
|
|
|
|
|
value: Union[int, str]
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
res = str(self.value)
|
|
|
|
|
if self.type == RoomKeyType.AUTH_CODE:
|
|
|
|
|
# 身份码要脱敏
|
|
|
|
|
res = '***' + res[-3:]
|
|
|
|
|
return res
|
|
|
|
|
__repr__ = __str__
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 用于类型标注的类型别名
|
|
|
|
|
LiveClientType = Union['WebLiveClient', 'OpenLiveClient']
|
|
|
|
|
|
2022-02-19 14:58:29 +08:00
|
|
|
|
# 到B站的连接管理
|
|
|
|
|
_live_client_manager: Optional['LiveClientManager'] = None
|
|
|
|
|
# 到客户端的连接管理
|
|
|
|
|
client_room_manager: Optional['ClientRoomManager'] = None
|
|
|
|
|
# 直播消息处理器
|
|
|
|
|
_live_msg_handler: Optional['LiveMsgHandler'] = None
|
2022-02-15 00:18:46 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def init():
|
2022-02-19 14:58:29 +08:00
|
|
|
|
global _live_client_manager, client_room_manager, _live_msg_handler
|
|
|
|
|
_live_client_manager = LiveClientManager()
|
|
|
|
|
client_room_manager = ClientRoomManager()
|
|
|
|
|
_live_msg_handler = LiveMsgHandler()
|
|
|
|
|
|
|
|
|
|
|
2023-09-06 21:34:04 +08:00
|
|
|
|
async def shut_down():
|
|
|
|
|
if client_room_manager is not None:
|
|
|
|
|
client_room_manager.shut_down()
|
|
|
|
|
if _live_client_manager is not None:
|
|
|
|
|
await _live_client_manager.shut_down()
|
|
|
|
|
|
|
|
|
|
|
2022-02-19 14:58:29 +08:00
|
|
|
|
class LiveClientManager:
|
|
|
|
|
"""管理到B站的连接"""
|
|
|
|
|
def __init__(self):
|
2023-09-07 00:56:32 +08:00
|
|
|
|
self._live_clients: Dict[RoomKey, LiveClientType] = {}
|
2023-09-06 21:34:04 +08:00
|
|
|
|
self._close_client_futures: Set[asyncio.Future] = set()
|
|
|
|
|
|
|
|
|
|
async def shut_down(self):
|
|
|
|
|
while len(self._live_clients) != 0:
|
2023-09-07 00:56:32 +08:00
|
|
|
|
room_key = next(iter(self._live_clients))
|
|
|
|
|
self.del_live_client(room_key)
|
2023-09-06 21:34:04 +08:00
|
|
|
|
|
|
|
|
|
await asyncio.gather(*self._close_client_futures, return_exceptions=True)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
def add_live_client(self, room_key: RoomKey):
|
|
|
|
|
if room_key in self._live_clients:
|
2022-02-19 14:58:29 +08:00
|
|
|
|
return
|
2023-09-07 00:56:32 +08:00
|
|
|
|
|
|
|
|
|
logger.info('room=%s creating live client', room_key)
|
|
|
|
|
|
|
|
|
|
self._live_clients[room_key] = live_client = self._create_live_client(room_key)
|
2023-09-03 22:53:15 +08:00
|
|
|
|
live_client.set_handler(_live_msg_handler)
|
2023-09-06 21:34:04 +08:00
|
|
|
|
# 直接启动吧,这里不用管init_room失败的情况,万一失败了会在on_client_stopped里删除掉这个客户端
|
2022-02-19 14:58:29 +08:00
|
|
|
|
live_client.start()
|
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
logger.info('room=%s live client created, %d live clients', room_key, len(self._live_clients))
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _create_live_client(room_key: RoomKey):
|
|
|
|
|
if room_key.type == RoomKeyType.ROOM_ID:
|
|
|
|
|
return WebLiveClient(room_key)
|
|
|
|
|
elif room_key.type == RoomKeyType.AUTH_CODE:
|
|
|
|
|
return OpenLiveClient(room_key)
|
|
|
|
|
raise ValueError(f'Unknown RoomKeyType={room_key.type}')
|
|
|
|
|
|
|
|
|
|
def del_live_client(self, room_key: RoomKey):
|
|
|
|
|
live_client = self._live_clients.pop(room_key, None)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
if live_client is None:
|
|
|
|
|
return
|
2023-09-06 21:34:04 +08:00
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
logger.info('room=%s removing live client', room_key)
|
|
|
|
|
|
|
|
|
|
live_client.set_handler(None)
|
2023-09-06 21:34:04 +08:00
|
|
|
|
future = asyncio.create_task(live_client.stop_and_close())
|
|
|
|
|
self._close_client_futures.add(future)
|
|
|
|
|
future.add_done_callback(lambda _future: self._close_client_futures.discard(future))
|
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
logger.info('room=%s live client removed, %d live clients', room_key, len(self._live_clients))
|
|
|
|
|
|
|
|
|
|
client_room_manager.del_room(room_key)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
|
|
|
|
|
|
2023-09-16 23:01:45 +08:00
|
|
|
|
RECONNECT_POLICY = dm_utils.make_linear_retry_policy(1, 2, 10)
|
|
|
|
|
|
|
|
|
|
|
2023-09-06 21:34:04 +08:00
|
|
|
|
class WebLiveClient(blivedm.BLiveClient):
|
2022-02-20 00:42:31 +08:00
|
|
|
|
HEARTBEAT_INTERVAL = 10
|
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
def __init__(self, room_key: RoomKey):
|
|
|
|
|
assert room_key.type == RoomKeyType.ROOM_ID
|
|
|
|
|
super().__init__(
|
|
|
|
|
room_key.value,
|
|
|
|
|
uid=0,
|
|
|
|
|
session=utils.request.http_session,
|
|
|
|
|
heartbeat_interval=self.HEARTBEAT_INTERVAL,
|
|
|
|
|
)
|
2023-09-16 23:01:45 +08:00
|
|
|
|
self.set_reconnect_policy(RECONNECT_POLICY)
|
2023-09-07 00:56:32 +08:00
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def room_key(self):
|
|
|
|
|
return RoomKey(RoomKeyType.ROOM_ID, self.tmp_room_id)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
|
|
|
|
|
async def init_room(self):
|
2023-09-07 00:56:32 +08:00
|
|
|
|
res = await super().init_room()
|
|
|
|
|
if res:
|
|
|
|
|
logger.info('room=%s live client init succeeded, room_id=%d', self.room_key, self.room_id)
|
|
|
|
|
else:
|
|
|
|
|
logger.info('room=%s live client init with a downgrade, room_id=%d', self.room_key, self.room_id)
|
|
|
|
|
# 允许降级
|
2022-02-19 14:58:29 +08:00
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
class OpenLiveClient(blivedm.OpenLiveClient):
|
|
|
|
|
HEARTBEAT_INTERVAL = 10
|
|
|
|
|
|
|
|
|
|
def __init__(self, room_key: RoomKey):
|
|
|
|
|
assert room_key.type == RoomKeyType.AUTH_CODE
|
|
|
|
|
cfg = config.get_config()
|
|
|
|
|
super().__init__(
|
|
|
|
|
access_key_id=cfg.open_live_access_key_id,
|
|
|
|
|
access_key_secret=cfg.open_live_access_key_secret,
|
|
|
|
|
app_id=cfg.open_live_app_id,
|
|
|
|
|
room_owner_auth_code=room_key.value,
|
|
|
|
|
session=utils.request.http_session,
|
|
|
|
|
heartbeat_interval=self.HEARTBEAT_INTERVAL,
|
|
|
|
|
)
|
2023-09-16 23:01:45 +08:00
|
|
|
|
self.set_reconnect_policy(RECONNECT_POLICY)
|
2023-09-07 00:56:32 +08:00
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def room_key(self):
|
|
|
|
|
return RoomKey(RoomKeyType.AUTH_CODE, self.room_owner_auth_code)
|
|
|
|
|
|
|
|
|
|
async def init_room(self):
|
|
|
|
|
res = await super().init_room()
|
|
|
|
|
if res:
|
|
|
|
|
logger.info('room=%s live client init succeeded, room_id=%d', self.room_key, self.room_id)
|
|
|
|
|
else:
|
|
|
|
|
logger.info('room=%s live client init failed', self.room_key)
|
|
|
|
|
return res
|
|
|
|
|
|
2023-09-08 20:53:04 +08:00
|
|
|
|
async def _start_game(self):
|
|
|
|
|
try:
|
|
|
|
|
data = await api_open_live.request_open_live_or_common_server(
|
|
|
|
|
api_open_live.START_GAME_OPEN_LIVE_URL,
|
|
|
|
|
api_open_live.START_GAME_COMMON_SERVER_URL,
|
|
|
|
|
{'code': self._room_owner_auth_code, 'app_id': self._app_id}
|
|
|
|
|
)
|
|
|
|
|
except api_open_live.TransportError:
|
|
|
|
|
logger.error('_start_game() failed')
|
|
|
|
|
return False
|
2023-09-13 00:13:11 +08:00
|
|
|
|
except api_open_live.BusinessError as e:
|
2023-09-08 20:53:04 +08:00
|
|
|
|
logger.warning('_start_game() failed')
|
2023-09-13 00:13:11 +08:00
|
|
|
|
|
|
|
|
|
if e.code == 7007:
|
|
|
|
|
# 身份码错误
|
|
|
|
|
# 让我看看是哪个混蛋把房间ID、UID当做身份码
|
2023-09-16 12:23:01 +08:00
|
|
|
|
logger.info('Auth code error! auth_code=%s', self._room_owner_auth_code)
|
2023-09-13 00:13:11 +08:00
|
|
|
|
room = client_room_manager.get_room(self.room_key)
|
|
|
|
|
if room is not None:
|
|
|
|
|
room.send_cmd_data(api.chat.Command.FATAL_ERROR, {
|
|
|
|
|
'type': api.chat.FatalErrorType.AUTH_CODE_ERROR,
|
|
|
|
|
'msg': str(e)
|
|
|
|
|
})
|
|
|
|
|
|
2023-09-08 20:53:04 +08:00
|
|
|
|
return False
|
|
|
|
|
return self._parse_start_game(data['data'])
|
|
|
|
|
|
|
|
|
|
async def _end_game(self):
|
|
|
|
|
if self._game_id in (None, ''):
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
await api_open_live.request_open_live_or_common_server(
|
|
|
|
|
api_open_live.END_GAME_OPEN_LIVE_URL,
|
|
|
|
|
api_open_live.END_GAME_COMMON_SERVER_URL,
|
|
|
|
|
{'app_id': self._app_id, 'game_id': self._game_id}
|
|
|
|
|
)
|
|
|
|
|
except api_open_live.TransportError:
|
|
|
|
|
logger.error('room=%d _end_game() failed', self.room_id)
|
|
|
|
|
return False
|
|
|
|
|
except api_open_live.BusinessError as e:
|
|
|
|
|
if e.code in (7000, 7003):
|
|
|
|
|
# 项目已经关闭了也算成功
|
|
|
|
|
return True
|
|
|
|
|
logger.warning('room=%d _end_game() failed', self.room_id)
|
|
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
async def _send_game_heartbeat(self):
|
|
|
|
|
if self._game_id in (None, ''):
|
|
|
|
|
logger.warning('game=%d _send_game_heartbeat() failed, game_id not found', self._game_id)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# 保存一下,防止await之后game_id改变
|
|
|
|
|
game_id = self._game_id
|
|
|
|
|
try:
|
|
|
|
|
await api_open_live.request_open_live_or_common_server(
|
|
|
|
|
api_open_live.GAME_HEARTBEAT_OPEN_LIVE_URL,
|
|
|
|
|
api_open_live.GAME_HEARTBEAT_COMMON_SERVER_URL,
|
|
|
|
|
{'game_id': game_id}
|
|
|
|
|
)
|
|
|
|
|
except api_open_live.TransportError:
|
|
|
|
|
logger.error('room=%d _send_game_heartbeat() failed', self.room_id)
|
|
|
|
|
return False
|
|
|
|
|
except api_open_live.BusinessError as e:
|
|
|
|
|
logger.warning('room=%d _send_game_heartbeat() failed', self.room_id)
|
|
|
|
|
if e.code == 7003 and self._game_id == game_id:
|
|
|
|
|
# 项目异常关闭,可能是心跳超时,需要重新开启项目
|
|
|
|
|
self._need_init_room = True
|
|
|
|
|
if self._websocket is not None and not self._websocket.closed:
|
|
|
|
|
await self._websocket.close()
|
|
|
|
|
return False
|
|
|
|
|
return True
|
2023-09-07 00:56:32 +08:00
|
|
|
|
|
|
|
|
|
|
2022-02-19 14:58:29 +08:00
|
|
|
|
class ClientRoomManager:
|
|
|
|
|
"""管理到客户端的连接"""
|
2022-02-20 00:42:31 +08:00
|
|
|
|
# 房间没有客户端后延迟多久删除房间,不立即删除防止短时间后重连
|
|
|
|
|
DELAY_DEL_ROOM_TIMEOUT = 10
|
|
|
|
|
|
2022-02-19 14:58:29 +08:00
|
|
|
|
def __init__(self):
|
2023-09-07 00:56:32 +08:00
|
|
|
|
self._rooms: Dict[RoomKey, ClientRoom] = {}
|
|
|
|
|
self._delay_del_timer_handles: Dict[RoomKey, asyncio.TimerHandle] = {}
|
2022-02-19 14:58:29 +08:00
|
|
|
|
|
2023-09-06 21:34:04 +08:00
|
|
|
|
def shut_down(self):
|
|
|
|
|
while len(self._rooms) != 0:
|
2023-09-07 00:56:32 +08:00
|
|
|
|
room_key = next(iter(self._rooms))
|
|
|
|
|
self.del_room(room_key)
|
2023-09-06 21:34:04 +08:00
|
|
|
|
|
|
|
|
|
for timer_handle in self._delay_del_timer_handles.values():
|
|
|
|
|
timer_handle.cancel()
|
|
|
|
|
self._delay_del_timer_handles.clear()
|
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
def add_client(self, room_key: RoomKey, client: 'api.chat.ChatHandler'):
|
|
|
|
|
room = self._get_or_add_room(room_key)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
room.add_client(client)
|
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
self._clear_delay_del_timer(room_key)
|
2022-02-20 00:42:31 +08:00
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
def del_client(self, room_key: RoomKey, client: 'api.chat.ChatHandler'):
|
|
|
|
|
room = self.get_room(room_key)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
if room is None:
|
|
|
|
|
return
|
2023-09-07 00:56:32 +08:00
|
|
|
|
|
2022-02-19 14:58:29 +08:00
|
|
|
|
room.del_client(client)
|
|
|
|
|
|
|
|
|
|
if room.client_count == 0:
|
2023-09-07 00:56:32 +08:00
|
|
|
|
self.delay_del_room(room_key, self.DELAY_DEL_ROOM_TIMEOUT)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
def get_room(self, room_key: RoomKey):
|
|
|
|
|
return self._rooms.get(room_key, None)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
def _get_or_add_room(self, room_key: RoomKey):
|
|
|
|
|
room = self._rooms.get(room_key, None)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
if room is None:
|
2023-09-07 00:56:32 +08:00
|
|
|
|
logger.info('room=%s creating client room', room_key)
|
|
|
|
|
self._rooms[room_key] = room = ClientRoom(room_key)
|
|
|
|
|
logger.info('room=%s client room created, %d client rooms', room_key, len(self._rooms))
|
2022-02-19 14:58:29 +08:00
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
_live_client_manager.add_live_client(room_key)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
return room
|
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
def del_room(self, room_key: RoomKey):
|
|
|
|
|
self._clear_delay_del_timer(room_key)
|
2022-02-20 00:42:31 +08:00
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
room = self._rooms.pop(room_key, None)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
if room is None:
|
|
|
|
|
return
|
2023-09-07 00:56:32 +08:00
|
|
|
|
|
|
|
|
|
logger.info('room=%s removing client room', room_key)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
room.clear_clients()
|
2023-09-07 00:56:32 +08:00
|
|
|
|
logger.info('room=%s client room removed, %d client rooms', room_key, len(self._rooms))
|
2022-02-19 14:58:29 +08:00
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
_live_client_manager.del_live_client(room_key)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
def delay_del_room(self, room_key: RoomKey, timeout):
|
|
|
|
|
self._clear_delay_del_timer(room_key)
|
|
|
|
|
self._delay_del_timer_handles[room_key] = asyncio.get_running_loop().call_later(
|
|
|
|
|
timeout, self._on_delay_del_room, room_key
|
2022-02-20 00:42:31 +08:00
|
|
|
|
)
|
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
def _clear_delay_del_timer(self, room_key: RoomKey):
|
|
|
|
|
timer_handle = self._delay_del_timer_handles.pop(room_key, None)
|
2022-02-20 00:42:31 +08:00
|
|
|
|
if timer_handle is not None:
|
|
|
|
|
timer_handle.cancel()
|
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
def _on_delay_del_room(self, room_key: RoomKey):
|
|
|
|
|
self._delay_del_timer_handles.pop(room_key, None)
|
|
|
|
|
self.del_room(room_key)
|
2022-02-20 00:42:31 +08:00
|
|
|
|
|
2022-02-19 14:58:29 +08:00
|
|
|
|
|
|
|
|
|
class ClientRoom:
|
2023-09-07 00:56:32 +08:00
|
|
|
|
def __init__(self, room_key: RoomKey):
|
|
|
|
|
self._room_key = room_key
|
2022-02-19 14:58:29 +08:00
|
|
|
|
self._clients: List[api.chat.ChatHandler] = []
|
|
|
|
|
self._auto_translate_count = 0
|
|
|
|
|
|
|
|
|
|
@property
|
2023-09-07 00:56:32 +08:00
|
|
|
|
def room_key(self) -> RoomKey:
|
|
|
|
|
return self._room_key
|
2022-02-19 14:58:29 +08:00
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def client_count(self):
|
|
|
|
|
return len(self._clients)
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def need_translate(self):
|
|
|
|
|
return self._auto_translate_count > 0
|
|
|
|
|
|
|
|
|
|
def add_client(self, client: 'api.chat.ChatHandler'):
|
2023-09-07 00:56:32 +08:00
|
|
|
|
logger.info('room=%s addding client %s', self._room_key, client.request.remote_ip)
|
|
|
|
|
|
2022-02-19 14:58:29 +08:00
|
|
|
|
self._clients.append(client)
|
|
|
|
|
if client.auto_translate:
|
|
|
|
|
self._auto_translate_count += 1
|
2023-09-07 00:56:32 +08:00
|
|
|
|
|
|
|
|
|
logger.info('room=%s added client %s, %d clients', self._room_key, client.request.remote_ip,
|
2022-02-19 14:58:29 +08:00
|
|
|
|
self.client_count)
|
|
|
|
|
|
|
|
|
|
def del_client(self, client: 'api.chat.ChatHandler'):
|
|
|
|
|
client.close()
|
|
|
|
|
try:
|
|
|
|
|
self._clients.remove(client)
|
|
|
|
|
except ValueError:
|
|
|
|
|
return
|
|
|
|
|
if client.auto_translate:
|
|
|
|
|
self._auto_translate_count -= 1
|
2023-09-07 00:56:32 +08:00
|
|
|
|
|
|
|
|
|
logger.info('room=%s removed client %s, %d clients', self._room_key, client.request.remote_ip,
|
2022-02-19 14:58:29 +08:00
|
|
|
|
self.client_count)
|
|
|
|
|
|
|
|
|
|
def clear_clients(self):
|
2023-09-07 00:56:32 +08:00
|
|
|
|
logger.info('room=%s clearing %d clients', self._room_key, self.client_count)
|
|
|
|
|
|
2022-02-19 14:58:29 +08:00
|
|
|
|
for client in self._clients:
|
|
|
|
|
client.close()
|
|
|
|
|
self._clients.clear()
|
|
|
|
|
self._auto_translate_count = 0
|
|
|
|
|
|
|
|
|
|
def send_cmd_data(self, cmd, data):
|
|
|
|
|
body = api.chat.make_message_body(cmd, data)
|
|
|
|
|
for client in self._clients:
|
|
|
|
|
client.send_body_no_raise(body)
|
2022-02-15 00:18:46 +08:00
|
|
|
|
|
2022-02-19 14:58:29 +08:00
|
|
|
|
def send_cmd_data_if(self, filterer: Callable[['api.chat.ChatHandler'], bool], cmd, data):
|
|
|
|
|
body = api.chat.make_message_body(cmd, data)
|
|
|
|
|
for client in filter(filterer, self._clients):
|
|
|
|
|
client.send_body_no_raise(body)
|
2022-02-15 00:18:46 +08:00
|
|
|
|
|
|
|
|
|
|
2022-02-19 14:58:29 +08:00
|
|
|
|
class LiveMsgHandler(blivedm.BaseHandler):
|
2023-09-07 00:56:32 +08:00
|
|
|
|
def on_client_stopped(self, client: LiveClientType, exception: Optional[Exception]):
|
|
|
|
|
_live_client_manager.del_live_client(client.room_key)
|
2023-09-03 22:53:15 +08:00
|
|
|
|
|
2023-09-09 17:13:53 +08:00
|
|
|
|
def _on_danmaku(self, client: WebLiveClient, message: dm_web_models.DanmakuMessage):
|
2023-07-29 12:48:57 +08:00
|
|
|
|
asyncio.create_task(self.__on_danmaku(client, message))
|
2022-02-15 00:18:46 +08:00
|
|
|
|
|
2023-09-09 17:13:53 +08:00
|
|
|
|
async def __on_danmaku(self, client: WebLiveClient, message: dm_web_models.DanmakuMessage):
|
2023-11-25 22:54:53 +08:00
|
|
|
|
# 先异步调用再获取房间,因为返回时房间可能已经不存在了
|
|
|
|
|
avatar_url = await services.avatar.get_avatar_url(message.uid, message.uname)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
room = client_room_manager.get_room(client.room_key)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
if room is None:
|
|
|
|
|
return
|
2022-02-15 00:18:46 +08:00
|
|
|
|
|
2022-02-19 14:58:29 +08:00
|
|
|
|
if message.uid == client.room_owner_uid:
|
2022-02-15 00:18:46 +08:00
|
|
|
|
author_type = 3 # 主播
|
|
|
|
|
elif message.admin:
|
|
|
|
|
author_type = 2 # 房管
|
|
|
|
|
elif message.privilege_type != 0: # 1总督,2提督,3舰长
|
|
|
|
|
author_type = 1 # 舰队
|
|
|
|
|
else:
|
|
|
|
|
author_type = 0
|
|
|
|
|
|
|
|
|
|
if message.dm_type == 1:
|
|
|
|
|
content_type = api.chat.ContentType.EMOTICON
|
|
|
|
|
content_type_params = api.chat.make_emoticon_params(
|
|
|
|
|
message.emoticon_options_dict['url'],
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
content_type = api.chat.ContentType.TEXT
|
|
|
|
|
content_type_params = None
|
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
need_translate = (
|
|
|
|
|
content_type != api.chat.ContentType.EMOTICON and self._need_translate(message.msg, room, client)
|
|
|
|
|
)
|
2022-02-15 00:18:46 +08:00
|
|
|
|
if need_translate:
|
|
|
|
|
translation = services.translate.get_translation_from_cache(message.msg)
|
|
|
|
|
if translation is None:
|
|
|
|
|
# 没有缓存,需要后面异步翻译后通知
|
|
|
|
|
translation = ''
|
|
|
|
|
else:
|
|
|
|
|
need_translate = False
|
|
|
|
|
else:
|
|
|
|
|
translation = ''
|
|
|
|
|
|
2022-02-19 14:58:29 +08:00
|
|
|
|
msg_id = uuid.uuid4().hex
|
|
|
|
|
room.send_cmd_data(api.chat.Command.ADD_TEXT, api.chat.make_text_message_data(
|
|
|
|
|
avatar_url=avatar_url,
|
2022-02-15 00:18:46 +08:00
|
|
|
|
timestamp=int(message.timestamp / 1000),
|
|
|
|
|
author_name=message.uname,
|
|
|
|
|
author_type=author_type,
|
|
|
|
|
content=message.msg,
|
|
|
|
|
privilege_type=message.privilege_type,
|
2022-02-23 23:15:21 +08:00
|
|
|
|
is_gift_danmaku=bool(message.msg_type),
|
2022-02-15 00:18:46 +08:00
|
|
|
|
author_level=message.user_level,
|
|
|
|
|
is_newbie=message.urank < 10000,
|
2022-02-23 23:15:21 +08:00
|
|
|
|
is_mobile_verified=bool(message.mobile_verify),
|
2022-02-19 14:58:29 +08:00
|
|
|
|
medal_level=0 if message.medal_room_id != client.room_id else message.medal_level,
|
|
|
|
|
id_=msg_id,
|
2022-02-15 00:18:46 +08:00
|
|
|
|
translation=translation,
|
|
|
|
|
content_type=content_type,
|
|
|
|
|
content_type_params=content_type_params,
|
2023-09-24 18:36:38 +08:00
|
|
|
|
uid=message.uid
|
2022-02-15 00:18:46 +08:00
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
if need_translate:
|
2023-09-07 00:56:32 +08:00
|
|
|
|
await self._translate_and_response(message.msg, room.room_key, msg_id)
|
2022-02-15 00:18:46 +08:00
|
|
|
|
|
2023-09-09 17:13:53 +08:00
|
|
|
|
def _on_gift(self, client: WebLiveClient, message: dm_web_models.GiftMessage):
|
2022-02-15 00:18:46 +08:00
|
|
|
|
avatar_url = services.avatar.process_avatar_url(message.face)
|
2023-07-30 22:22:47 +08:00
|
|
|
|
services.avatar.update_avatar_cache_if_expired(message.uid, avatar_url)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
|
|
|
|
|
# 丢人
|
|
|
|
|
if message.coin_type != 'gold':
|
|
|
|
|
return
|
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
room = client_room_manager.get_room(client.room_key)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
if room is None:
|
2022-02-15 00:18:46 +08:00
|
|
|
|
return
|
2022-02-19 14:58:29 +08:00
|
|
|
|
|
|
|
|
|
room.send_cmd_data(api.chat.Command.ADD_GIFT, {
|
|
|
|
|
'id': uuid.uuid4().hex,
|
2022-02-15 00:18:46 +08:00
|
|
|
|
'avatarUrl': avatar_url,
|
|
|
|
|
'timestamp': message.timestamp,
|
|
|
|
|
'authorName': message.uname,
|
|
|
|
|
'totalCoin': message.total_coin,
|
|
|
|
|
'giftName': message.gift_name,
|
2023-09-24 18:36:38 +08:00
|
|
|
|
'num': message.num,
|
|
|
|
|
'uid': message.uid
|
2022-02-15 00:18:46 +08:00
|
|
|
|
})
|
|
|
|
|
|
2023-09-09 17:13:53 +08:00
|
|
|
|
def _on_buy_guard(self, client: WebLiveClient, message: dm_web_models.GuardBuyMessage):
|
2023-07-29 12:48:57 +08:00
|
|
|
|
asyncio.create_task(self.__on_buy_guard(client, message))
|
2022-02-19 14:58:29 +08:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
2023-09-09 17:13:53 +08:00
|
|
|
|
async def __on_buy_guard(client: WebLiveClient, message: dm_web_models.GuardBuyMessage):
|
2022-02-19 14:58:29 +08:00
|
|
|
|
# 先异步调用再获取房间,因为返回时房间可能已经不存在了
|
2023-09-16 11:31:46 +08:00
|
|
|
|
avatar_url = await services.avatar.get_avatar_url(message.uid, message.username)
|
2022-02-15 00:18:46 +08:00
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
room = client_room_manager.get_room(client.room_key)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
if room is None:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
room.send_cmd_data(api.chat.Command.ADD_MEMBER, {
|
|
|
|
|
'id': uuid.uuid4().hex,
|
|
|
|
|
'avatarUrl': avatar_url,
|
2022-02-15 00:18:46 +08:00
|
|
|
|
'timestamp': message.start_time,
|
|
|
|
|
'authorName': message.username,
|
2023-09-24 18:36:38 +08:00
|
|
|
|
'privilegeType': message.guard_level,
|
|
|
|
|
'uid': message.uid
|
2022-02-15 00:18:46 +08:00
|
|
|
|
})
|
|
|
|
|
|
2023-09-09 17:13:53 +08:00
|
|
|
|
def _on_super_chat(self, client: WebLiveClient, message: dm_web_models.SuperChatMessage):
|
2022-02-15 00:18:46 +08:00
|
|
|
|
avatar_url = services.avatar.process_avatar_url(message.face)
|
2023-07-30 22:22:47 +08:00
|
|
|
|
services.avatar.update_avatar_cache_if_expired(message.uid, avatar_url)
|
2022-02-15 00:18:46 +08:00
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
room = client_room_manager.get_room(client.room_key)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
if room is None:
|
|
|
|
|
return
|
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
need_translate = self._need_translate(message.message, room, client)
|
2022-02-15 00:18:46 +08:00
|
|
|
|
if need_translate:
|
|
|
|
|
translation = services.translate.get_translation_from_cache(message.message)
|
|
|
|
|
if translation is None:
|
|
|
|
|
# 没有缓存,需要后面异步翻译后通知
|
|
|
|
|
translation = ''
|
|
|
|
|
else:
|
|
|
|
|
need_translate = False
|
|
|
|
|
else:
|
|
|
|
|
translation = ''
|
|
|
|
|
|
2022-02-19 14:58:29 +08:00
|
|
|
|
msg_id = str(message.id)
|
|
|
|
|
room.send_cmd_data(api.chat.Command.ADD_SUPER_CHAT, {
|
|
|
|
|
'id': msg_id,
|
2022-02-15 00:18:46 +08:00
|
|
|
|
'avatarUrl': avatar_url,
|
|
|
|
|
'timestamp': message.start_time,
|
|
|
|
|
'authorName': message.uname,
|
|
|
|
|
'price': message.price,
|
|
|
|
|
'content': message.message,
|
2023-09-24 18:36:38 +08:00
|
|
|
|
'translation': translation,
|
|
|
|
|
'uid': message.uid
|
2022-02-15 00:18:46 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if need_translate:
|
2023-08-05 21:25:59 +08:00
|
|
|
|
asyncio.create_task(self._translate_and_response(
|
2023-09-07 00:56:32 +08:00
|
|
|
|
message.message, room.room_key, msg_id, services.translate.Priority.HIGH
|
2023-08-05 21:25:59 +08:00
|
|
|
|
))
|
2022-02-15 00:18:46 +08:00
|
|
|
|
|
2023-09-09 17:13:53 +08:00
|
|
|
|
def _on_super_chat_delete(self, client: WebLiveClient, message: dm_web_models.SuperChatDeleteMessage):
|
2023-09-07 00:56:32 +08:00
|
|
|
|
room = client_room_manager.get_room(client.room_key)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
if room is None:
|
|
|
|
|
return
|
|
|
|
|
|
2022-03-16 22:20:41 +08:00
|
|
|
|
room.send_cmd_data(api.chat.Command.DEL_SUPER_CHAT, {
|
2022-02-15 00:18:46 +08:00
|
|
|
|
'ids': list(map(str, message.ids))
|
|
|
|
|
})
|
|
|
|
|
|
2022-02-19 14:58:29 +08:00
|
|
|
|
@staticmethod
|
2023-09-07 00:56:32 +08:00
|
|
|
|
def _need_translate(text, room: ClientRoom, client: LiveClientType):
|
2022-02-15 00:18:46 +08:00
|
|
|
|
cfg = config.get_config()
|
|
|
|
|
return (
|
2022-02-19 14:58:29 +08:00
|
|
|
|
cfg.enable_translate
|
|
|
|
|
and room.need_translate
|
2023-09-07 00:56:32 +08:00
|
|
|
|
and (not cfg.allow_translate_rooms or client.room_id in cfg.allow_translate_rooms)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
and services.translate.need_translate(text)
|
2022-02-15 00:18:46 +08:00
|
|
|
|
)
|
|
|
|
|
|
2022-02-19 14:58:29 +08:00
|
|
|
|
@staticmethod
|
2023-09-07 00:56:32 +08:00
|
|
|
|
async def _translate_and_response(text, room_key: RoomKey, msg_id, priority=services.translate.Priority.NORMAL):
|
2023-08-05 21:25:59 +08:00
|
|
|
|
translation = await services.translate.translate(text, priority)
|
2022-02-15 00:18:46 +08:00
|
|
|
|
if translation is None:
|
|
|
|
|
return
|
2022-02-19 14:58:29 +08:00
|
|
|
|
|
2023-09-07 00:56:32 +08:00
|
|
|
|
room = client_room_manager.get_room(room_key)
|
2022-02-19 14:58:29 +08:00
|
|
|
|
if room is None:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
room.send_cmd_data_if(
|
2022-02-15 00:18:46 +08:00
|
|
|
|
lambda client: client.auto_translate,
|
|
|
|
|
api.chat.Command.UPDATE_TRANSLATION,
|
2022-02-19 14:58:29 +08:00
|
|
|
|
api.chat.make_translation_message_data(
|
2022-02-15 00:18:46 +08:00
|
|
|
|
msg_id,
|
|
|
|
|
translation
|
|
|
|
|
)
|
|
|
|
|
)
|
2023-09-08 23:54:47 +08:00
|
|
|
|
|
2023-09-09 17:13:53 +08:00
|
|
|
|
#
|
|
|
|
|
# 开放平台消息
|
|
|
|
|
#
|
|
|
|
|
|
|
|
|
|
def _on_open_live_danmaku(self, client: OpenLiveClient, message: dm_open_models.DanmakuMessage):
|
|
|
|
|
avatar_url = message.uface
|
|
|
|
|
services.avatar.update_avatar_cache_if_expired(message.uid, avatar_url)
|
|
|
|
|
|
|
|
|
|
room = client_room_manager.get_room(client.room_key)
|
|
|
|
|
if room is None:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if message.uid == client.room_owner_uid:
|
|
|
|
|
author_type = 3 # 主播
|
|
|
|
|
elif message.guard_level != 0: # 1总督,2提督,3舰长
|
|
|
|
|
author_type = 1 # 舰队
|
|
|
|
|
else:
|
|
|
|
|
author_type = 0
|
|
|
|
|
|
|
|
|
|
if message.dm_type == 1:
|
|
|
|
|
content_type = api.chat.ContentType.EMOTICON
|
|
|
|
|
content_type_params = api.chat.make_emoticon_params(message.emoji_img_url)
|
|
|
|
|
else:
|
|
|
|
|
content_type = api.chat.ContentType.TEXT
|
|
|
|
|
content_type_params = None
|
|
|
|
|
|
|
|
|
|
need_translate = (
|
|
|
|
|
content_type != api.chat.ContentType.EMOTICON and self._need_translate(message.msg, room, client)
|
|
|
|
|
)
|
|
|
|
|
if need_translate:
|
|
|
|
|
translation = services.translate.get_translation_from_cache(message.msg)
|
|
|
|
|
if translation is None:
|
|
|
|
|
# 没有缓存,需要后面异步翻译后通知
|
|
|
|
|
translation = ''
|
|
|
|
|
else:
|
|
|
|
|
need_translate = False
|
|
|
|
|
else:
|
|
|
|
|
translation = ''
|
|
|
|
|
|
|
|
|
|
room.send_cmd_data(api.chat.Command.ADD_TEXT, api.chat.make_text_message_data(
|
|
|
|
|
avatar_url=avatar_url,
|
|
|
|
|
timestamp=message.timestamp,
|
|
|
|
|
author_name=message.uname,
|
|
|
|
|
author_type=author_type,
|
|
|
|
|
content=message.msg,
|
|
|
|
|
privilege_type=message.guard_level,
|
|
|
|
|
medal_level=0 if not message.fans_medal_wearing_status else message.fans_medal_level,
|
|
|
|
|
id_=message.msg_id,
|
|
|
|
|
translation=translation,
|
|
|
|
|
content_type=content_type,
|
|
|
|
|
content_type_params=content_type_params,
|
2023-09-24 18:36:38 +08:00
|
|
|
|
uid=message.uid
|
2023-09-09 17:13:53 +08:00
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
if need_translate:
|
|
|
|
|
asyncio.create_task(self._translate_and_response(message.msg, room.room_key, message.msg_id))
|
|
|
|
|
|
|
|
|
|
def _on_open_live_gift(self, client: OpenLiveClient, message: dm_open_models.GiftMessage):
|
|
|
|
|
avatar_url = services.avatar.process_avatar_url(message.uface)
|
|
|
|
|
services.avatar.update_avatar_cache_if_expired(message.uid, avatar_url)
|
|
|
|
|
|
|
|
|
|
# 丢人
|
|
|
|
|
if not message.paid:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
room = client_room_manager.get_room(client.room_key)
|
|
|
|
|
if room is None:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
room.send_cmd_data(api.chat.Command.ADD_GIFT, {
|
|
|
|
|
'id': message.msg_id,
|
|
|
|
|
'avatarUrl': avatar_url,
|
|
|
|
|
'timestamp': message.timestamp,
|
|
|
|
|
'authorName': message.uname,
|
2023-09-18 20:08:08 +08:00
|
|
|
|
'totalCoin': message.price * message.gift_num,
|
2023-09-09 17:13:53 +08:00
|
|
|
|
'giftName': message.gift_name,
|
2023-09-24 18:36:38 +08:00
|
|
|
|
'num': message.gift_num,
|
|
|
|
|
'uid': message.uid
|
2023-09-09 17:13:53 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
def _on_open_live_buy_guard(self, client: OpenLiveClient, message: dm_open_models.GuardBuyMessage):
|
|
|
|
|
avatar_url = message.user_info.uface
|
|
|
|
|
services.avatar.update_avatar_cache_if_expired(message.user_info.uid, avatar_url)
|
|
|
|
|
|
|
|
|
|
room = client_room_manager.get_room(client.room_key)
|
|
|
|
|
if room is None:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
room.send_cmd_data(api.chat.Command.ADD_MEMBER, {
|
|
|
|
|
'id': message.msg_id,
|
|
|
|
|
'avatarUrl': avatar_url,
|
|
|
|
|
'timestamp': message.timestamp,
|
|
|
|
|
'authorName': message.user_info.uname,
|
2023-09-24 18:36:38 +08:00
|
|
|
|
'privilegeType': message.guard_level,
|
|
|
|
|
'uid': message.user_info.uid
|
2023-09-09 17:13:53 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
def _on_open_live_super_chat(self, client: OpenLiveClient, message: dm_open_models.SuperChatMessage):
|
|
|
|
|
avatar_url = services.avatar.process_avatar_url(message.uface)
|
|
|
|
|
services.avatar.update_avatar_cache_if_expired(message.uid, avatar_url)
|
|
|
|
|
|
|
|
|
|
room = client_room_manager.get_room(client.room_key)
|
|
|
|
|
if room is None:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
need_translate = self._need_translate(message.message, room, client)
|
|
|
|
|
if need_translate:
|
|
|
|
|
translation = services.translate.get_translation_from_cache(message.message)
|
|
|
|
|
if translation is None:
|
|
|
|
|
# 没有缓存,需要后面异步翻译后通知
|
|
|
|
|
translation = ''
|
|
|
|
|
else:
|
|
|
|
|
need_translate = False
|
|
|
|
|
else:
|
|
|
|
|
translation = ''
|
|
|
|
|
|
|
|
|
|
msg_id = str(message.message_id)
|
|
|
|
|
room.send_cmd_data(api.chat.Command.ADD_SUPER_CHAT, {
|
|
|
|
|
'id': msg_id,
|
|
|
|
|
'avatarUrl': avatar_url,
|
|
|
|
|
'timestamp': message.start_time,
|
|
|
|
|
'authorName': message.uname,
|
|
|
|
|
'price': message.rmb,
|
|
|
|
|
'content': message.message,
|
2023-09-24 18:36:38 +08:00
|
|
|
|
'translation': translation,
|
|
|
|
|
'uid': message.uid
|
2023-09-09 17:13:53 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if need_translate:
|
|
|
|
|
asyncio.create_task(self._translate_and_response(
|
|
|
|
|
message.message, room.room_key, msg_id, services.translate.Priority.HIGH
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
def _on_open_live_super_chat_delete(self, client: OpenLiveClient, message: dm_open_models.SuperChatDeleteMessage):
|
|
|
|
|
room = client_room_manager.get_room(client.room_key)
|
|
|
|
|
if room is None:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
room.send_cmd_data(api.chat.Command.DEL_SUPER_CHAT, {
|
|
|
|
|
'ids': list(map(str, message.message_ids))
|
|
|
|
|
})
|