diff --git a/blcsdk/handlers.py b/blcsdk/handlers.py index d5950f1..07efe0d 100644 --- a/blcsdk/handlers.py +++ b/blcsdk/handlers.py @@ -28,22 +28,11 @@ def _make_msg_callback(method_name, message_cls): def callback(self: 'BaseHandler', client: cli.BlcPluginClient, command: dict): method = getattr(self, method_name) msg = message_cls.from_command(command['data']) - extra = _get_extra(command) + extra = models.ExtraData.from_dict(command.get('extra', {})) return method(client, msg, extra) return callback -def _get_extra(command: dict): - extra = command.get('extra', {}) - room_key_dict = extra.get('roomKey', None) - if room_key_dict is not None: - extra['roomKey'] = models.RoomKey( - type=models.RoomKeyType(room_key_dict['type']), - value=room_key_dict['value'], - ) - return extra - - class BaseHandler(HandlerInterface): """一个简单的消息处理器实现,带消息分发和消息类型转换。继承并重写_on_xxx方法即可实现自己的处理器""" @@ -54,17 +43,14 @@ class BaseHandler(HandlerInterface): Any ]] ] = { - # 收到弹幕 + 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.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), - # 醒目留言 models.Command.ADD_SUPER_CHAT: _make_msg_callback('_on_add_super_chat', models.AddSuperChatMsg), - # 删除醒目留言 models.Command.DEL_SUPER_CHAT: _make_msg_callback('_on_del_super_chat', models.DelSuperChatMsg), - # 更新翻译 models.Command.UPDATE_TRANSLATION: _make_msg_callback('_on_update_translation', models.UpdateTranslationMsg), } """cmd -> 处理回调""" @@ -75,20 +61,31 @@ class BaseHandler(HandlerInterface): if callback is not None: callback(self, client, command) - def _on_add_text(self, client: cli.BlcPluginClient, message: models.AddTextMsg): + def _on_add_room(self, client: cli.BlcPluginClient, message: models.AddRoomMsg, extra: models.ExtraData): + """添加房间""" + + def _on_room_init(self, client: cli.BlcPluginClient, message: models.RoomInitMsg, extra: models.ExtraData): + """房间初始化""" + + def _on_del_room(self, client: cli.BlcPluginClient, message: models.DelRoomMsg, extra: models.ExtraData): + """删除房间""" + + def _on_add_text(self, client: cli.BlcPluginClient, message: models.AddTextMsg, extra: models.ExtraData): """收到弹幕""" - def _on_add_gift(self, client: cli.BlcPluginClient, message: models.AddGiftMsg): + def _on_add_gift(self, client: cli.BlcPluginClient, message: models.AddGiftMsg, extra: models.ExtraData): """有人送礼""" - def _on_add_member(self, client: cli.BlcPluginClient, message: models.AddMemberMsg): + def _on_add_member(self, client: cli.BlcPluginClient, message: models.AddMemberMsg, extra: models.ExtraData): """有人上舰""" - def _on_add_super_chat(self, client: cli.BlcPluginClient, message: models.AddSuperChatMsg): + def _on_add_super_chat(self, client: cli.BlcPluginClient, message: models.AddSuperChatMsg, extra: models.ExtraData): """醒目留言""" - def _on_del_super_chat(self, client: cli.BlcPluginClient, message: models.DelSuperChatMsg): + def _on_del_super_chat(self, client: cli.BlcPluginClient, message: models.DelSuperChatMsg, extra: models.ExtraData): """删除醒目留言""" - def _on_update_translation(self, client: cli.BlcPluginClient, message: models.UpdateTranslationMsg): + def _on_update_translation( + self, client: cli.BlcPluginClient, message: models.UpdateTranslationMsg, extra: models.ExtraData + ): """更新翻译""" diff --git a/blcsdk/models.py b/blcsdk/models.py index ffba3d5..9a4d998 100644 --- a/blcsdk/models.py +++ b/blcsdk/models.py @@ -41,6 +41,9 @@ class RoomKey(NamedTuple): class Command(enum.IntEnum): HEARTBEAT = 0 BLC_INIT = 1 + ADD_ROOM = 2 + ROOM_INIT = 3 + DEL_ROOM = 4 ADD_TEXT = 20 ADD_GIFT = 21 @@ -50,6 +53,78 @@ class Command(enum.IntEnum): UPDATE_TRANSLATION = 25 +@dataclasses.dataclass +class ExtraData: + """一些消息共用的附加信息""" + + room_id: Optional[int] = None + """房间ID""" + 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'], + ) + else: + room_key = None + + return cls( + room_id=data.get('roomId', None), + room_key=room_key, + is_from_plugin=data.get('isFromPlugin', False), + ) + + +@dataclasses.dataclass +class AddRoomMsg: + """ + 添加房间消息。房间信息在extra里 + + 此时room_id是None,因为还没有初始化 + """ + + @classmethod + def from_command(cls, _data: dict): + return cls() + + +@dataclasses.dataclass +class RoomInitMsg: + """ + 房间初始化消息。房间信息在extra里 + + 一个房间创建后可能被多次初始化,处理时注意去重 + """ + + is_success: bool = False + + @classmethod + def from_command(cls, data: dict): + return cls( + is_success=data['isSuccess'], + ) + + +@dataclasses.dataclass +class DelRoomMsg: + """ + 删除房间消息。房间信息在extra里 + + 注意此时room_id可能是None + """ + + @classmethod + def from_command(cls, _data: dict): + return cls() + + class AuthorType(enum.IntEnum): NORMAL = 0 GUARD = 1 diff --git a/plugins/msg-logging/listener.py b/plugins/msg-logging/listener.py index f30a9ab..e909f5f 100644 --- a/plugins/msg-logging/listener.py +++ b/plugins/msg-logging/listener.py @@ -1,15 +1,19 @@ # -*- coding: utf-8 -*- import __main__ +import datetime import logging +import os from typing import * import blcsdk import blcsdk.models as sdk_models +import config from blcsdk import client as cli logger = logging.getLogger(__name__) _msg_handler: Optional['MsgHandler'] = None +_id_room_dict: Dict[int, 'Room'] = {} async def init(): @@ -17,26 +21,90 @@ async def init(): _msg_handler = MsgHandler() blcsdk.set_msg_handler(_msg_handler) + # TODO 创建已有的房间 + + +def shut_down(): + blcsdk.set_msg_handler(None) + while _id_room_dict: + 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]): logger.info('blivechat disconnected') __main__.start_shut_down() - def _on_add_text(self, client: cli.BlcPluginClient, message: sdk_models.AddTextMsg): - """收到弹幕""" + def _on_room_init(self, client: cli.BlcPluginClient, message: sdk_models.RoomInitMsg, extra: sdk_models.ExtraData): + 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): + 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): + 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): + room = _get_or_add_room(extra.room_id) + room.log( + f'[gift] {message.author_name} 赠送了 {message.gift_name} x {message.num},' + f'总价 {message.total_coin / 1000} 元' + ) + + def _on_add_member( + self, client: cli.BlcPluginClient, message: sdk_models.AddMemberMsg, extra: sdk_models.ExtraData + ): + room = _get_or_add_room(extra.room_id) + if message.privilege_type == sdk_models.GuardLevel.LV1: + guard_name = '舰长' + elif message.privilege_type == sdk_models.GuardLevel.LV2: + guard_name = '提督' + elif message.privilege_type == sdk_models.GuardLevel.LV3: + guard_name = '总督' + else: + guard_name = '未知舰队等级' + # TODO 可以加上时长 + 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 + ): + room = _get_or_add_room(extra.room_id) + room.log(f'[superchat] {message.author_name} 发送了 {message.price} 元的醒目留言:{message.content}') + + +def _get_or_add_room(room_id): + ctx = _id_room_dict.get(room_id, None) + if ctx is None: + ctx = _id_room_dict[room_id] = Room(room_id) + return ctx + + +def _del_room(room_id): + ctx = _id_room_dict.pop(room_id, None) + if ctx is not None: + ctx.close() - def _on_add_gift(self, client: cli.BlcPluginClient, message: sdk_models.AddGiftMsg): - """有人送礼""" - def _on_add_member(self, client: cli.BlcPluginClient, message: sdk_models.AddMemberMsg): - """有人上舰""" +class Room: + def __init__(self, room_id): + self.room_id = room_id - def _on_add_super_chat(self, client: cli.BlcPluginClient, message: sdk_models.AddSuperChatMsg): - """醒目留言""" + 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') - def _on_del_super_chat(self, client: cli.BlcPluginClient, message: sdk_models.DelSuperChatMsg): - """删除醒目留言""" + def close(self): + self.file.close() - def _on_update_translation(self, client: cli.BlcPluginClient, message: sdk_models.UpdateTranslationMsg): - """更新翻译""" + 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() diff --git a/plugins/msg-logging/main.py b/plugins/msg-logging/main.py index 3722063..357d9e6 100755 --- a/plugins/msg-logging/main.py +++ b/plugins/msg-logging/main.py @@ -78,6 +78,7 @@ async def run(): async def shut_down(): + listener.shut_down() await blcsdk.shut_down() diff --git a/services/chat.py b/services/chat.py index d07826f..7ffd3b2 100644 --- a/services/chat.py +++ b/services/chat.py @@ -14,6 +14,8 @@ import blivedm.blivedm.utils as dm_utils import config import services.avatar import services.translate +import services.plugin +import blcsdk.models as sdk_models import utils.request logger = logging.getLogger(__name__) @@ -63,6 +65,17 @@ async def shut_down(): await _live_client_manager.shut_down() +def make_plugin_msg_extra(live_client: LiveClientType): + room_key = live_client.room_key + return { + 'roomId': live_client.room_id, # init_room之前是None + 'roomKey': { + 'type': room_key.type, + 'value': room_key.value, + }, + } + + class LiveClientManager: """管理到B站的连接""" def __init__(self): @@ -76,6 +89,9 @@ class LiveClientManager: await asyncio.gather(*self._close_client_futures, return_exceptions=True) + def get_live_client(self, room_key: RoomKey): + return self._live_clients.get(room_key, None) + def add_live_client(self, room_key: RoomKey): if room_key in self._live_clients: return @@ -89,6 +105,8 @@ 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)) + @staticmethod def _create_live_client(room_key: RoomKey): if room_key.type == RoomKeyType.ROOM_ID: @@ -113,6 +131,8 @@ 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)) + RECONNECT_POLICY = dm_utils.make_linear_retry_policy(1, 2, 10) @@ -140,6 +160,13 @@ class WebLiveClient(blivedm.BLiveClient): 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) + + services.plugin.broadcast_cmd_data( + sdk_models.Command.ROOM_INIT, + {'isSuccess': True}, # 降级也算成功 + make_plugin_msg_extra(self), + ) + # 允许降级 return True @@ -170,6 +197,13 @@ class OpenLiveClient(blivedm.OpenLiveClient): 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) + + services.plugin.broadcast_cmd_data( + sdk_models.Command.ROOM_INIT, + {'isSuccess': res}, + make_plugin_msg_extra(self), + ) + return res async def _start_game(self): @@ -433,7 +467,7 @@ class LiveMsgHandler(blivedm.BaseHandler): translation = '' msg_id = uuid.uuid4().hex - room.send_cmd_data(api.chat.Command.ADD_TEXT, api.chat.make_text_message_data( + data = api.chat.make_text_message_data( avatar_url=avatar_url, timestamp=int(message.timestamp / 1000), author_name=message.uname, @@ -450,7 +484,9 @@ class LiveMsgHandler(blivedm.BaseHandler): content_type=content_type, content_type_params=content_type_params, 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)) if need_translate: await self._translate_and_response(message.msg, room.room_key, msg_id) @@ -467,7 +503,7 @@ class LiveMsgHandler(blivedm.BaseHandler): if room is None: return - room.send_cmd_data(api.chat.Command.ADD_GIFT, { + data = { 'id': uuid.uuid4().hex, 'avatarUrl': avatar_url, 'timestamp': message.timestamp, @@ -476,7 +512,9 @@ class LiveMsgHandler(blivedm.BaseHandler): 'giftName': message.gift_name, 'num': message.num, '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)) def _on_buy_guard(self, client: WebLiveClient, message: dm_web_models.GuardBuyMessage): asyncio.create_task(self.__on_buy_guard(client, message)) @@ -490,14 +528,16 @@ class LiveMsgHandler(blivedm.BaseHandler): if room is None: return - room.send_cmd_data(api.chat.Command.ADD_MEMBER, { + data = { 'id': uuid.uuid4().hex, 'avatarUrl': avatar_url, 'timestamp': message.start_time, 'authorName': message.username, 'privilegeType': message.guard_level, '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)) def _on_super_chat(self, client: WebLiveClient, message: dm_web_models.SuperChatMessage): avatar_url = services.avatar.process_avatar_url(message.face) @@ -519,7 +559,7 @@ class LiveMsgHandler(blivedm.BaseHandler): translation = '' msg_id = str(message.id) - room.send_cmd_data(api.chat.Command.ADD_SUPER_CHAT, { + data = { 'id': msg_id, 'avatarUrl': avatar_url, 'timestamp': message.start_time, @@ -528,7 +568,9 @@ class LiveMsgHandler(blivedm.BaseHandler): 'content': message.message, 'translation': translation, '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)) if need_translate: asyncio.create_task(self._translate_and_response( @@ -540,9 +582,11 @@ class LiveMsgHandler(blivedm.BaseHandler): if room is None: return - room.send_cmd_data(api.chat.Command.DEL_SUPER_CHAT, { + data = { '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)) @staticmethod def _need_translate(text, room: ClientRoom, client: LiveClientType): @@ -564,15 +608,19 @@ class LiveMsgHandler(blivedm.BaseHandler): if room is None: return + data = api.chat.make_translation_message_data(msg_id, translation) room.send_cmd_data_if( lambda client: client.auto_translate, api.chat.Command.UPDATE_TRANSLATION, - api.chat.make_translation_message_data( - msg_id, - translation - ) + 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) + ) + # # 开放平台消息 # @@ -612,7 +660,7 @@ class LiveMsgHandler(blivedm.BaseHandler): else: translation = '' - room.send_cmd_data(api.chat.Command.ADD_TEXT, api.chat.make_text_message_data( + data = api.chat.make_text_message_data( avatar_url=avatar_url, timestamp=message.timestamp, author_name=message.uname, @@ -625,7 +673,9 @@ class LiveMsgHandler(blivedm.BaseHandler): content_type=content_type, content_type_params=content_type_params, 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)) if need_translate: asyncio.create_task(self._translate_and_response(message.msg, room.room_key, message.msg_id)) @@ -642,7 +692,7 @@ class LiveMsgHandler(blivedm.BaseHandler): if room is None: return - room.send_cmd_data(api.chat.Command.ADD_GIFT, { + data = { 'id': message.msg_id, 'avatarUrl': avatar_url, 'timestamp': message.timestamp, @@ -651,7 +701,9 @@ class LiveMsgHandler(blivedm.BaseHandler): 'giftName': message.gift_name, 'num': message.gift_num, '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)) def _on_open_live_buy_guard(self, client: OpenLiveClient, message: dm_open_models.GuardBuyMessage): avatar_url = message.user_info.uface @@ -661,14 +713,16 @@ class LiveMsgHandler(blivedm.BaseHandler): if room is None: return - room.send_cmd_data(api.chat.Command.ADD_MEMBER, { + data = { 'id': message.msg_id, 'avatarUrl': avatar_url, 'timestamp': message.timestamp, 'authorName': message.user_info.uname, 'privilegeType': message.guard_level, '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)) def _on_open_live_super_chat(self, client: OpenLiveClient, message: dm_open_models.SuperChatMessage): avatar_url = services.avatar.process_avatar_url(message.uface) @@ -690,7 +744,7 @@ class LiveMsgHandler(blivedm.BaseHandler): translation = '' msg_id = str(message.message_id) - room.send_cmd_data(api.chat.Command.ADD_SUPER_CHAT, { + data = { 'id': msg_id, 'avatarUrl': avatar_url, 'timestamp': message.start_time, @@ -699,7 +753,9 @@ class LiveMsgHandler(blivedm.BaseHandler): 'content': message.message, 'translation': translation, '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)) if need_translate: asyncio.create_task(self._translate_and_response( @@ -711,6 +767,8 @@ class LiveMsgHandler(blivedm.BaseHandler): if room is None: return - room.send_cmd_data(api.chat.Command.DEL_SUPER_CHAT, { + data = { '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))