From be5b49fae43108df932e7f216f139c1a64e1452c Mon Sep 17 00:00:00 2001 From: John Smith Date: Wed, 8 Nov 2023 00:21:17 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=9B=B4=E5=A4=9A=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/chat.py | 15 +-- api/plugin.py | 62 +++++++++++++ blcsdk/api.py | 157 ++++++++++++++++++++++++++++++-- blcsdk/exc.py | 17 ++++ blcsdk/handlers.py | 8 ++ blcsdk/models.py | 71 +++++++++++---- plugins/msg-logging/listener.py | 65 +++++++++---- services/chat.py | 110 +++++++++++++++++----- services/plugin.py | 1 + 9 files changed, 422 insertions(+), 84 deletions(-) diff --git a/api/chat.py b/api/chat.py index 406a135..78f1ec8 100644 --- a/api/chat.py +++ b/api/chat.py @@ -199,21 +199,10 @@ class ChatHandler(tornado.websocket.WebSocketHandler): room_key_dict = data.get('roomKey', None) if room_key_dict is not None: - room_key_type = services.chat.RoomKeyType(room_key_dict['type']) - room_key_value = room_key_dict['value'] - if room_key_type == services.chat.RoomKeyType.ROOM_ID: - if not isinstance(room_key_value, int): - raise TypeError(f'Room key value type error, value={room_key_value}') - elif room_key_type == services.chat.RoomKeyType.AUTH_CODE: - if not isinstance(room_key_value, str): - raise TypeError(f'Room key value type error, value={room_key_value}') - else: - raise ValueError(f'Unknown RoomKeyType={room_key_type}') + self.room_key = services.chat.RoomKey.from_dict(room_key_dict) else: # 兼容旧版客户端 TODO 过几个版本可以移除 - room_key_type = services.chat.RoomKeyType.ROOM_ID - room_key_value = int(data['roomId']) - self.room_key = services.chat.RoomKey(room_key_type, room_key_value) + self.room_key = services.chat.RoomKey(services.chat.RoomKeyType.ROOM_ID, int(data['roomId'])) logger.info('client=%s joining room %s', self.request.remote_ip, self.room_key) try: diff --git a/api/plugin.py b/api/plugin.py index bf54d24..87b46c0 100644 --- a/api/plugin.py +++ b/api/plugin.py @@ -8,7 +8,10 @@ import tornado.web import tornado.websocket import api.base +import api.chat import blcsdk.models as models +import services.avatar +import services.chat import services.plugin logger = logging.getLogger(__name__) @@ -93,15 +96,61 @@ class PluginWsHandler(_PluginHandlerBase, tornado.websocket.WebSocketHandler): try: body = json.loads(message) cmd = int(body['cmd']) + data = body['data'] if cmd == models.Command.HEARTBEAT: self._refresh_receive_timeout_timer() + elif cmd == models.Command.LOG_REQ: + logger.log(int(data['level']), '[%s] %s', self.plugin.id, data['msg']) + elif cmd == models.Command.ADD_TEXT_REQ: + self._on_add_text_req(data) else: logger.warning('plugin=%s unknown cmd=%d, body=%s', self.plugin.id, cmd, body) except Exception: # noqa logger.exception('plugin=%s on_message error, message=%s', self.plugin.id, message) + def _on_add_text_req(self, data: dict): + room_key_dict = data['roomKey'] + if room_key_dict is not None: + room_key = services.chat.RoomKey.from_dict(room_key_dict) + room = services.chat.client_room_manager.get_room(room_key) + if room is not None: + rooms = [room] + else: + rooms = [] + else: + rooms = list(services.chat.client_room_manager.iter_rooms()) + if not rooms: + return + + author_name = str(data['authorName']) + if author_name == '': + author_name = self.plugin.id + uid = int(data['uid']) + avatar_url = str(data['avatarUrl']) + if avatar_url == '': + avatar_url = services.avatar.get_default_avatar_url(uid, author_name) + + data_to_send = api.chat.make_text_message_data( + content=str(data['content']), + author_name=author_name, + uid=uid, + avatar_url=avatar_url, + author_type=int(data['authorType']), + privilege_type=int(data['guardLevel']), + medal_level=int(data['medalLevel']), + translation=str(data['translation']), + ) + + body_for_room = api.chat.make_message_body(api.chat.Command.ADD_TEXT, data_to_send) + for room in rooms: + room.send_body_no_raise(body_for_room) + + extra = services.chat.make_plugin_msg_extra_from_client_room(room) + extra['isFromPlugin'] = True + services.plugin.broadcast_cmd_data(models.Command.ADD_TEXT, data_to_send, extra) + def send_cmd_data(self, cmd, data, extra: Optional[dict] = None): self.send_body_no_raise(make_message_body(cmd, data, extra)) @@ -112,6 +161,19 @@ class PluginWsHandler(_PluginHandlerBase, tornado.websocket.WebSocketHandler): self.close() +class RoomsHandler(api.base.ApiHandler): + async def get(self): + rooms = [ + { + 'roomId': live_client.room_id, + 'roomKey': live_client.room_key.to_dict(), + } + for live_client in services.chat.iter_live_clients() + ] + self.write({'rooms': rooms}) + + ROUTES = [ (r'/api/plugin/websocket', PluginWsHandler), + (r'/api/plugin/rooms', RoomsHandler), ] diff --git a/blcsdk/api.py b/blcsdk/api.py index 71ce0b0..3f89826 100644 --- a/blcsdk/api.py +++ b/blcsdk/api.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import asyncio +import dataclasses import logging import os import re @@ -20,12 +21,21 @@ __all__ = ( 'shut_down', 'set_msg_handler', 'is_sdk_version_compatible', + 'get_blc_port', + 'get_blc_version', + 'get_blc_sdk_version', + 'get_plugin_id', + 'log', + 'send_text', + 'get_rooms', ) logger = logging.getLogger('blcsdk') # 环境变量 -_base_url = '' +_blc_port = 0 +"""服务器端口""" +_blc_base_url = '' """HTTP API的URL""" _token = '' """插件认证用的token""" @@ -54,15 +64,16 @@ async def init(): 在调用除了set_msg_handler以外的其他接口之前必须先调用这个。如果抛出任何异常,应该退出当前程序 """ try: - global _base_url, _token, _init_future, _init_msg, _http_session, _plugin_client, _msg_handler_wrapper + global _blc_port, _blc_base_url, _token, _init_future, _init_msg, _http_session, _plugin_client, \ + _msg_handler_wrapper if _init_future is not None: raise exc.InitError('Cannot call init() again') _init_future = asyncio.get_running_loop().create_future() # 初始化环境变量信息 - blc_port = int(os.environ['BLC_PORT']) - _base_url = f'http://localhost:{blc_port}' - blc_ws_url = f'ws://localhost:{blc_port}/api/plugin/websocket' + _blc_port = int(os.environ['BLC_PORT']) + _blc_base_url = f'http://localhost:{_blc_port}' + blc_ws_url = f'ws://localhost:{_blc_port}/api/plugin/websocket' _token = os.environ['BLC_TOKEN'] _http_session = aiohttp.ClientSession( @@ -134,11 +145,10 @@ def is_sdk_version_compatible(): 如果不兼容,建议退出当前程序。如果继续执行有可能不能正常工作 """ - if _init_msg is None: - raise exc.SdkError('Please call init() first') + assert _init_msg is not None, 'Please call init() first' major_ver_pattern = r'(\d+)\.\d+\.\d+' - remote_ver = _init_msg['sdkVersion'] + remote_ver = get_blc_sdk_version() m = re.match(major_ver_pattern, remote_ver) if m is None: @@ -154,3 +164,134 @@ def is_sdk_version_compatible(): if not res: logger.warning('SDK version is not compatible, remote=%s, local=%s', remote_ver, __version__) return res + + +def get_blc_port(): + """取blivechat服务器监听的端口""" + assert _blc_port != 0, 'Please call init() first' + return _blc_port + + +def get_blc_version() -> str: + """取blivechat版本""" + assert _init_msg is not None, 'Please call init() first' + return _init_msg['blcVersion'] + + +def get_blc_sdk_version() -> str: + """取blivechat用的SDK版本。是主进程用的版本,不是这个包的__version__""" + assert _init_msg is not None, 'Please call init() first' + return _init_msg['sdkVersion'] + + +def get_plugin_id() -> str: + """取当前插件的ID""" + assert _init_msg is not None, 'Please call init() first' + return _init_msg['pluginId'] + + +async def _blc_ws_send_cmd_data(cmd: models.Command, data: dict): + assert _plugin_client is not None, 'Please call init() first' + + try: + await _plugin_client.send_cmd_data(cmd, data) + except (ConnectionResetError, aiohttp.ClientError) as e: + raise exc.TransportError(str(e)) from e + + +def _blc_get(rel_url, *, params=None): + return _blc_http_request('GET', rel_url, params=params) + + +def _blc_post(rel_url, *, params=None, json=None): + return _blc_http_request('POST', rel_url, params=params, json=json) + + +async def _blc_http_request(method, rel_url, **kwargs): + assert _http_session is not None, 'Please call init() first' + + try: + async with _http_session.request(method, _blc_base_url + rel_url, **kwargs) as r: + if r.ok: + return await r.json() + + try: + data = await r.json() + except aiohttp.ContentTypeError: + data = None + raise exc.ResponseError(r.status, r.reason, data) + except aiohttp.ClientError as e: + raise exc.TransportError(str(e)) from e + + +async def log(msg, level=logging.INFO): + """ + 输出日志到blivechat + + blivechat不会分离插件的控制台,所以直接输出到标准输出流、标准错误流也能看到日志,但是通过这个接口输出的还会写到blivechat的日志文件 + """ + await _blc_ws_send_cmd_data(models.Command.LOG_REQ, { + 'msg': str(msg), + 'level': level, + }) + + +async def send_text( + content: str, + author_name: str = '', + *, + uid: int = 0, + avatar_url: str = '', + author_type: int = models.AuthorType.NORMAL.value, + guard_level: int = models.GuardLevel.NONE.value, + medal_level: int = 0, + translation: str = '', + room_key: Optional[models.RoomKey] = None, +): + """ + 发送文字消息 + + 当前插件也会收到这条消息,注意避免死循环 + + :param content: 内容 + :param author_name: 用户名,默认为当前插件ID + :param uid: 用户ID + :param avatar_url: 用户头像URL,默认自动生成 + :param author_type: 用户类型,见AuthorType + :param guard_level: 舰队等级,见GuardLevel + :param medal_level: 勋章等级 + :param translation: 内容翻译 + :param room_key: 发送到哪个房间,默认发送到所有房间 + """ + await _blc_ws_send_cmd_data(models.Command.ADD_TEXT_REQ, { + 'content': content, + 'authorName': author_name, + 'uid': uid, + 'avatarUrl': avatar_url, + 'authorType': author_type, + 'guardLevel': guard_level, + 'medalLevel': medal_level, + 'translation': translation, + 'roomKey': room_key.to_dict() if room_key is not None else None, + }) + + +@dataclasses.dataclass +class GetRoomsRes: + room_id: Optional[int] + """房间ID,初始化之前是None""" + room_key: models.RoomKey + """blivechat用来标识一个房间的key""" + + +async def get_rooms() -> List[GetRoomsRes]: + """取当前已创建的房间列表""" + rsp = await _blc_get('/api/plugin/rooms') + rooms = [ + GetRoomsRes( + room_id=room_dict['roomId'], + room_key=models.RoomKey.from_dict(room_dict['roomKey']), + ) + for room_dict in rsp['rooms'] + ] + return rooms diff --git a/blcsdk/exc.py b/blcsdk/exc.py index 7c175fb..fbe8e00 100644 --- a/blcsdk/exc.py +++ b/blcsdk/exc.py @@ -1,7 +1,11 @@ # -*- coding: utf-8 -*- +from typing import * + __all__ = ( 'SdkError', 'InitError', + 'TransportError', + 'ResponseError', ) @@ -11,3 +15,16 @@ class SdkError(Exception): class InitError(SdkError): """初始化失败""" + + +class TransportError(SdkError): + """通信错误""" + + +class ResponseError(SdkError): + """响应代码错误""" + def __init__(self, code: int, msg: str, data: Optional[dict] = None): + super().__init__(f'code={code}, msg={msg}, data={data}') + self.code = code + self.msg = msg + self.data = data diff --git a/blcsdk/handlers.py b/blcsdk/handlers.py index 07efe0d..058731d 100644 --- a/blcsdk/handlers.py +++ b/blcsdk/handlers.py @@ -46,6 +46,9 @@ class BaseHandler(HandlerInterface): models.Command.ADD_ROOM: _make_msg_callback('_on_add_room', models.AddRoomMsg), models.Command.ROOM_INIT: _make_msg_callback('_on_room_init', models.RoomInitMsg), models.Command.DEL_ROOM: _make_msg_callback('_on_del_room', models.DelRoomMsg), + models.Command.OPEN_PLUGIN_ADMIN_UI: _make_msg_callback( + '_on_open_plugin_admin_ui', models.OpenPluginAdminUiMsg + ), models.Command.ADD_TEXT: _make_msg_callback('_on_add_text', models.AddTextMsg), models.Command.ADD_GIFT: _make_msg_callback('_on_add_gift', models.AddGiftMsg), models.Command.ADD_MEMBER: _make_msg_callback('_on_add_member', models.AddMemberMsg), @@ -70,6 +73,11 @@ class BaseHandler(HandlerInterface): def _on_del_room(self, client: cli.BlcPluginClient, message: models.DelRoomMsg, extra: models.ExtraData): """删除房间""" + def _on_open_plugin_admin_ui( + self, client: cli.BlcPluginClient, message: models.OpenPluginAdminUiMsg, extra: models.ExtraData + ): + """用户请求打开当前插件的管理界面""" + def _on_add_text(self, client: cli.BlcPluginClient, message: models.AddTextMsg, extra: models.ExtraData): """收到弹幕""" diff --git a/blcsdk/models.py b/blcsdk/models.py index 9a4d998..3dbcbbf 100644 --- a/blcsdk/models.py +++ b/blcsdk/models.py @@ -7,6 +7,11 @@ __all__ = ( 'RoomKeyType', 'RoomKey', 'Command', + 'ExtraData', + 'AddRoomMsg', + 'RoomInitMsg', + 'DelRoomMsg', + 'OpenPluginAdminUiMsg', 'AuthorType', 'GuardLevel', 'ContentType', @@ -37,6 +42,21 @@ class RoomKey(NamedTuple): return res __repr__ = __str__ + @classmethod + def from_dict(cls, data: dict): + type_ = RoomKeyType(data['type']) + value = data['value'] + if type_ == RoomKeyType.ROOM_ID: + if not isinstance(value, int): + raise TypeError(f'Type of value is {type(value)}, value={value}') + elif type_ == RoomKeyType.AUTH_CODE: + if not isinstance(value, str): + raise TypeError(f'Type of value is {type(value)}, value={value}') + return cls(type=type_, value=value) + + def to_dict(self): + return {'type': self.type, 'value': self.value} + class Command(enum.IntEnum): HEARTBEAT = 0 @@ -44,13 +64,19 @@ class Command(enum.IntEnum): ADD_ROOM = 2 ROOM_INIT = 3 DEL_ROOM = 4 + OPEN_PLUGIN_ADMIN_UI = 5 - ADD_TEXT = 20 - ADD_GIFT = 21 - ADD_MEMBER = 22 - ADD_SUPER_CHAT = 23 - DEL_SUPER_CHAT = 24 - UPDATE_TRANSLATION = 25 + # 从插件发送到blivechat的请求 + LOG_REQ = 30 + ADD_TEXT_REQ = 31 + + # 房间内消息 + ADD_TEXT = 50 + ADD_GIFT = 51 + ADD_MEMBER = 52 + ADD_SUPER_CHAT = 53 + DEL_SUPER_CHAT = 54 + UPDATE_TRANSLATION = 55 @dataclasses.dataclass @@ -62,16 +88,17 @@ class ExtraData: room_key: Optional[RoomKey] = None """blivechat用来标识一个房间的key""" is_from_plugin: bool = False - """消息是插件生成的""" + """ + 消息是插件生成的 + + 如果你的插件既要监听消息,又要生成消息,注意判断这个以避免死循环 + """ @classmethod def from_dict(cls, data: dict): room_key_dict = data.get('roomKey', None) if room_key_dict is not None: - room_key = RoomKey( - type=RoomKeyType(room_key_dict['type']), - value=room_key_dict['value'], - ) + room_key = RoomKey.from_dict(room_key_dict) else: room_key = None @@ -83,17 +110,20 @@ class ExtraData: @dataclasses.dataclass -class AddRoomMsg: +class _EmptyMsg: + @classmethod + def from_command(cls, _data: dict): + return cls() + + +@dataclasses.dataclass +class AddRoomMsg(_EmptyMsg): """ 添加房间消息。房间信息在extra里 此时room_id是None,因为还没有初始化 """ - @classmethod - def from_command(cls, _data: dict): - return cls() - @dataclasses.dataclass class RoomInitMsg: @@ -113,16 +143,17 @@ class RoomInitMsg: @dataclasses.dataclass -class DelRoomMsg: +class DelRoomMsg(_EmptyMsg): """ 删除房间消息。房间信息在extra里 注意此时room_id可能是None """ - @classmethod - def from_command(cls, _data: dict): - return cls() + +@dataclasses.dataclass +class OpenPluginAdminUiMsg(_EmptyMsg): + """用户请求打开当前插件的管理界面消息""" class AuthorType(enum.IntEnum): diff --git a/plugins/msg-logging/listener.py b/plugins/msg-logging/listener.py index e909f5f..e2efd2c 100644 --- a/plugins/msg-logging/listener.py +++ b/plugins/msg-logging/listener.py @@ -3,14 +3,14 @@ import __main__ import datetime import logging import os +import sys from typing import * import blcsdk import blcsdk.models as sdk_models import config -from blcsdk import client as cli -logger = logging.getLogger(__name__) +logger = logging.getLogger('msg-logging.' + __name__) _msg_handler: Optional['MsgHandler'] = None _id_room_dict: Dict[int, 'Room'] = {} @@ -21,34 +21,59 @@ async def init(): _msg_handler = MsgHandler() blcsdk.set_msg_handler(_msg_handler) - # TODO 创建已有的房间 + # 创建已有的房间。这一步失败了也没关系,只是有消息时才会创建文件 + try: + blc_rooms = await blcsdk.get_rooms() + for blc_room in blc_rooms: + if blc_room.room_id is not None: + _get_or_add_room(blc_room.room_id) + except blcsdk.SdkError: + pass def shut_down(): blcsdk.set_msg_handler(None) - while _id_room_dict: + while len(_id_room_dict) != 0: room_id = next(iter(_id_room_dict)) _del_room(room_id) class MsgHandler(blcsdk.BaseHandler): - def on_client_stopped(self, client: cli.BlcPluginClient, exception: Optional[Exception]): + def on_client_stopped(self, client: blcsdk.BlcPluginClient, exception: Optional[Exception]): logger.info('blivechat disconnected') __main__.start_shut_down() - def _on_room_init(self, client: cli.BlcPluginClient, message: sdk_models.RoomInitMsg, extra: sdk_models.ExtraData): + def _on_open_plugin_admin_ui( + self, client: blcsdk.BlcPluginClient, message: sdk_models.OpenPluginAdminUiMsg, extra: sdk_models.ExtraData + ): + if sys.platform == 'win32': + os.startfile(config.LOG_PATH) + else: + logger.info('Log path is "%s"', config.LOG_PATH) + + def _on_room_init( + self, client: blcsdk.BlcPluginClient, message: sdk_models.RoomInitMsg, extra: sdk_models.ExtraData + ): + if extra.is_from_plugin: + return if message.is_success: _get_or_add_room(extra.room_id) - def _on_del_room(self, client: cli.BlcPluginClient, message: sdk_models.DelRoomMsg, extra: sdk_models.ExtraData): + def _on_del_room(self, client: blcsdk.BlcPluginClient, message: sdk_models.DelRoomMsg, extra: sdk_models.ExtraData): + if extra.is_from_plugin: + return if extra.room_id is not None: _del_room(extra.room_id) - def _on_add_text(self, client: cli.BlcPluginClient, message: sdk_models.AddTextMsg, extra: sdk_models.ExtraData): + def _on_add_text(self, client: blcsdk.BlcPluginClient, message: sdk_models.AddTextMsg, extra: sdk_models.ExtraData): + if extra.is_from_plugin: + return room = _get_or_add_room(extra.room_id) room.log(f'[dm] {message.author_name}:{message.content}') - def _on_add_gift(self, client: cli.BlcPluginClient, message: sdk_models.AddGiftMsg, extra: sdk_models.ExtraData): + def _on_add_gift(self, client: blcsdk.BlcPluginClient, message: sdk_models.AddGiftMsg, extra: sdk_models.ExtraData): + if extra.is_from_plugin: + return room = _get_or_add_room(extra.room_id) room.log( f'[gift] {message.author_name} 赠送了 {message.gift_name} x {message.num},' @@ -56,8 +81,10 @@ class MsgHandler(blcsdk.BaseHandler): ) def _on_add_member( - self, client: cli.BlcPluginClient, message: sdk_models.AddMemberMsg, extra: sdk_models.ExtraData + self, client: blcsdk.BlcPluginClient, message: sdk_models.AddMemberMsg, extra: sdk_models.ExtraData ): + if extra.is_from_plugin: + return room = _get_or_add_room(extra.room_id) if message.privilege_type == sdk_models.GuardLevel.LV1: guard_name = '舰长' @@ -71,8 +98,10 @@ class MsgHandler(blcsdk.BaseHandler): room.log(f'[guard] {message.author_name} 购买了 {guard_name}') def _on_add_super_chat( - self, client: cli.BlcPluginClient, message: sdk_models.AddSuperChatMsg, extra: sdk_models.ExtraData + self, client: blcsdk.BlcPluginClient, message: sdk_models.AddSuperChatMsg, extra: sdk_models.ExtraData ): + if extra.is_from_plugin: + return room = _get_or_add_room(extra.room_id) room.log(f'[superchat] {message.author_name} 发送了 {message.price} 元的醒目留言:{message.content}') @@ -80,6 +109,8 @@ class MsgHandler(blcsdk.BaseHandler): def _get_or_add_room(room_id): ctx = _id_room_dict.get(room_id, None) if ctx is None: + if room_id is None: + raise TypeError('room_id is None') ctx = _id_room_dict[room_id] = Room(room_id) return ctx @@ -92,19 +123,17 @@ def _del_room(room_id): class Room: def __init__(self, room_id): - self.room_id = room_id - cur_time = datetime.datetime.now() time_str = cur_time.strftime('%Y%m%d_%H%M%S') - filename = f'room_{room_id}-{time_str}.log' - self.file = open(os.path.join(config.LOG_PATH, filename), 'a', encoding='utf-8-sig') + filename = f'room_{room_id}-{time_str}.txt' + self._file = open(os.path.join(config.LOG_PATH, filename), 'a', encoding='utf-8-sig') def close(self): - self.file.close() + self._file.close() def log(self, content): cur_time = datetime.datetime.now() time_str = cur_time.strftime('%Y-%m-%d %H:%M:%S') text = f'{time_str} {content}\n' - self.file.write(text) - self.file.flush() + self._file.write(text) + self._file.flush() diff --git a/services/chat.py b/services/chat.py index 7ffd3b2..3e27d1c 100644 --- a/services/chat.py +++ b/services/chat.py @@ -39,6 +39,21 @@ class RoomKey(NamedTuple): return res __repr__ = __str__ + @classmethod + def from_dict(cls, data: dict): + type_ = RoomKeyType(data['type']) + value = data['value'] + if type_ == RoomKeyType.ROOM_ID: + if not isinstance(value, int): + raise TypeError(f'Type of value is {type(value)}, value={value}') + elif type_ == RoomKeyType.AUTH_CODE: + if not isinstance(value, str): + raise TypeError(f'Type of value is {type(value)}, value={value}') + return cls(type=type_, value=value) + + def to_dict(self): + return {'type': self.type, 'value': self.value} + # 用于类型标注的类型别名 LiveClientType = Union['WebLiveClient', 'OpenLiveClient'] @@ -65,14 +80,27 @@ async def shut_down(): await _live_client_manager.shut_down() -def make_plugin_msg_extra(live_client: LiveClientType): - room_key = live_client.room_key +def iter_live_clients() -> Iterable[LiveClientType]: + return _live_client_manager.iter_live_clients() + + +def make_plugin_msg_extra_from_live_client(live_client: LiveClientType): return { 'roomId': live_client.room_id, # init_room之前是None - 'roomKey': { - 'type': room_key.type, - 'value': room_key.value, - }, + 'roomKey': live_client.room_key.to_dict(), + } + + +def make_plugin_msg_extra_from_client_room(room: 'ClientRoom'): + room_key = room.room_key + live_client = _live_client_manager.get_live_client(room_key) + if live_client is not None: + room_id = live_client.room_id + else: + room_id = None + return { + 'roomId': room_id, # init_room之前是None + 'roomKey': room_key.to_dict(), } @@ -92,6 +120,9 @@ class LiveClientManager: def get_live_client(self, room_key: RoomKey): return self._live_clients.get(room_key, None) + def iter_live_clients(self): + return self._live_clients.values() + def add_live_client(self, room_key: RoomKey): if room_key in self._live_clients: return @@ -105,7 +136,9 @@ class LiveClientManager: logger.info('room=%s live client created, %d live clients', room_key, len(self._live_clients)) - services.plugin.broadcast_cmd_data(sdk_models.Command.ADD_ROOM, {}, make_plugin_msg_extra(live_client)) + services.plugin.broadcast_cmd_data( + sdk_models.Command.ADD_ROOM, {}, make_plugin_msg_extra_from_live_client(live_client) + ) @staticmethod def _create_live_client(room_key: RoomKey): @@ -131,7 +164,9 @@ class LiveClientManager: client_room_manager.del_room(room_key) - services.plugin.broadcast_cmd_data(sdk_models.Command.DEL_ROOM, {}, make_plugin_msg_extra(live_client)) + services.plugin.broadcast_cmd_data( + sdk_models.Command.DEL_ROOM, {}, make_plugin_msg_extra_from_live_client(live_client) + ) RECONNECT_POLICY = dm_utils.make_linear_retry_policy(1, 2, 10) @@ -164,7 +199,7 @@ class WebLiveClient(blivedm.BLiveClient): services.plugin.broadcast_cmd_data( sdk_models.Command.ROOM_INIT, {'isSuccess': True}, # 降级也算成功 - make_plugin_msg_extra(self), + make_plugin_msg_extra_from_live_client(self), ) # 允许降级 @@ -201,7 +236,7 @@ class OpenLiveClient(blivedm.OpenLiveClient): services.plugin.broadcast_cmd_data( sdk_models.Command.ROOM_INIT, {'isSuccess': res}, - make_plugin_msg_extra(self), + make_plugin_msg_extra_from_live_client(self), ) return res @@ -318,6 +353,9 @@ class ClientRoomManager: def get_room(self, room_key: RoomKey): return self._rooms.get(room_key, None) + def iter_rooms(self) -> Iterable['ClientRoom']: + return self._rooms.values() + def _get_or_add_room(self, room_key: RoomKey): room = self._rooms.get(room_key, None) if room is None: @@ -415,6 +453,10 @@ class ClientRoom: for client in filter(filterer, self._clients): client.send_body_no_raise(body) + def send_body_no_raise(self, body): + for client in self._clients: + client.send_body_no_raise(body) + class LiveMsgHandler(blivedm.BaseHandler): def on_client_stopped(self, client: LiveClientType, exception: Optional[Exception]): @@ -486,7 +528,9 @@ class LiveMsgHandler(blivedm.BaseHandler): uid=message.uid ) room.send_cmd_data(api.chat.Command.ADD_TEXT, data) - services.plugin.broadcast_cmd_data(sdk_models.Command.ADD_TEXT, data, make_plugin_msg_extra(client)) + services.plugin.broadcast_cmd_data( + sdk_models.Command.ADD_TEXT, data, make_plugin_msg_extra_from_live_client(client) + ) if need_translate: await self._translate_and_response(message.msg, room.room_key, msg_id) @@ -514,7 +558,9 @@ class LiveMsgHandler(blivedm.BaseHandler): 'uid': message.uid } room.send_cmd_data(api.chat.Command.ADD_GIFT, data) - services.plugin.broadcast_cmd_data(sdk_models.Command.ADD_GIFT, data, make_plugin_msg_extra(client)) + services.plugin.broadcast_cmd_data( + sdk_models.Command.ADD_GIFT, data, make_plugin_msg_extra_from_live_client(client) + ) def _on_buy_guard(self, client: WebLiveClient, message: dm_web_models.GuardBuyMessage): asyncio.create_task(self.__on_buy_guard(client, message)) @@ -537,7 +583,9 @@ class LiveMsgHandler(blivedm.BaseHandler): 'uid': message.uid } room.send_cmd_data(api.chat.Command.ADD_MEMBER, data) - services.plugin.broadcast_cmd_data(sdk_models.Command.ADD_MEMBER, data, make_plugin_msg_extra(client)) + services.plugin.broadcast_cmd_data( + sdk_models.Command.ADD_MEMBER, data, make_plugin_msg_extra_from_live_client(client) + ) def _on_super_chat(self, client: WebLiveClient, message: dm_web_models.SuperChatMessage): avatar_url = services.avatar.process_avatar_url(message.face) @@ -570,7 +618,9 @@ class LiveMsgHandler(blivedm.BaseHandler): 'uid': message.uid } room.send_cmd_data(api.chat.Command.ADD_SUPER_CHAT, data) - services.plugin.broadcast_cmd_data(sdk_models.Command.ADD_SUPER_CHAT, data, make_plugin_msg_extra(client)) + services.plugin.broadcast_cmd_data( + sdk_models.Command.ADD_SUPER_CHAT, data, make_plugin_msg_extra_from_live_client(client) + ) if need_translate: asyncio.create_task(self._translate_and_response( @@ -586,7 +636,9 @@ class LiveMsgHandler(blivedm.BaseHandler): 'ids': list(map(str, message.ids)) } room.send_cmd_data(api.chat.Command.DEL_SUPER_CHAT, data) - services.plugin.broadcast_cmd_data(sdk_models.Command.DEL_SUPER_CHAT, data, make_plugin_msg_extra(client)) + services.plugin.broadcast_cmd_data( + sdk_models.Command.DEL_SUPER_CHAT, data, make_plugin_msg_extra_from_live_client(client) + ) @staticmethod def _need_translate(text, room: ClientRoom, client: LiveClientType): @@ -615,11 +667,9 @@ class LiveMsgHandler(blivedm.BaseHandler): data ) - live_client = _live_client_manager.get_live_client(room_key) - if live_client is not None: - services.plugin.broadcast_cmd_data( - sdk_models.Command.UPDATE_TRANSLATION, data, make_plugin_msg_extra(live_client) - ) + services.plugin.broadcast_cmd_data( + sdk_models.Command.UPDATE_TRANSLATION, data, make_plugin_msg_extra_from_client_room(room) + ) # # 开放平台消息 @@ -675,7 +725,9 @@ class LiveMsgHandler(blivedm.BaseHandler): uid=message.uid ) room.send_cmd_data(api.chat.Command.ADD_TEXT, data) - services.plugin.broadcast_cmd_data(sdk_models.Command.ADD_TEXT, data, make_plugin_msg_extra(client)) + services.plugin.broadcast_cmd_data( + sdk_models.Command.ADD_TEXT, data, make_plugin_msg_extra_from_live_client(client) + ) if need_translate: asyncio.create_task(self._translate_and_response(message.msg, room.room_key, message.msg_id)) @@ -703,7 +755,9 @@ class LiveMsgHandler(blivedm.BaseHandler): 'uid': message.uid } room.send_cmd_data(api.chat.Command.ADD_GIFT, data) - services.plugin.broadcast_cmd_data(sdk_models.Command.ADD_GIFT, data, make_plugin_msg_extra(client)) + services.plugin.broadcast_cmd_data( + sdk_models.Command.ADD_GIFT, data, make_plugin_msg_extra_from_live_client(client) + ) def _on_open_live_buy_guard(self, client: OpenLiveClient, message: dm_open_models.GuardBuyMessage): avatar_url = message.user_info.uface @@ -722,7 +776,9 @@ class LiveMsgHandler(blivedm.BaseHandler): 'uid': message.user_info.uid } room.send_cmd_data(api.chat.Command.ADD_MEMBER, data) - services.plugin.broadcast_cmd_data(sdk_models.Command.ADD_MEMBER, data, make_plugin_msg_extra(client)) + services.plugin.broadcast_cmd_data( + sdk_models.Command.ADD_MEMBER, data, make_plugin_msg_extra_from_live_client(client) + ) def _on_open_live_super_chat(self, client: OpenLiveClient, message: dm_open_models.SuperChatMessage): avatar_url = services.avatar.process_avatar_url(message.uface) @@ -755,7 +811,9 @@ class LiveMsgHandler(blivedm.BaseHandler): 'uid': message.uid } room.send_cmd_data(api.chat.Command.ADD_SUPER_CHAT, data) - services.plugin.broadcast_cmd_data(sdk_models.Command.ADD_SUPER_CHAT, data, make_plugin_msg_extra(client)) + services.plugin.broadcast_cmd_data( + sdk_models.Command.ADD_SUPER_CHAT, data, make_plugin_msg_extra_from_live_client(client) + ) if need_translate: asyncio.create_task(self._translate_and_response( @@ -771,4 +829,6 @@ class LiveMsgHandler(blivedm.BaseHandler): 'ids': list(map(str, message.message_ids)) } room.send_cmd_data(api.chat.Command.DEL_SUPER_CHAT, data) - services.plugin.broadcast_cmd_data(sdk_models.Command.DEL_SUPER_CHAT, data, make_plugin_msg_extra(client)) + services.plugin.broadcast_cmd_data( + sdk_models.Command.DEL_SUPER_CHAT, data, make_plugin_msg_extra_from_live_client(client) + ) diff --git a/services/plugin.py b/services/plugin.py index 5f86706..696db1f 100644 --- a/services/plugin.py +++ b/services/plugin.py @@ -75,6 +75,7 @@ def iter_plugins() -> Iterable['Plugin']: def get_plugin_by_token(token): if token == '': return None + # 应该最多就十几个插件吧,偷懒用遍历了 for plugin in _plugins.values(): if plugin.token == token: return plugin