From 8277c562247bdb48506cc06655e9630115664c06 Mon Sep 17 00:00:00 2001 From: John Smith Date: Sat, 9 Mar 2024 22:37:11 +0800 Subject: [PATCH] =?UTF-8?q?GUI=E6=8F=92=E4=BB=B6=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/msg-logging/listener.py | 14 +- plugins/native-ui/LICENSE | 21 +++ plugins/native-ui/listener.py | 227 ++++++++++++++++++++++++++++++++ plugins/native-ui/main.pyw | 49 +++++-- plugins/native-ui/plugin.json | 8 ++ plugins/native-ui/ui/room.py | 10 ++ plugins/text-to-speech/tts.py | 5 + 7 files changed, 317 insertions(+), 17 deletions(-) create mode 100644 plugins/native-ui/LICENSE create mode 100644 plugins/native-ui/listener.py create mode 100644 plugins/native-ui/plugin.json create mode 100644 plugins/native-ui/ui/room.py diff --git a/plugins/msg-logging/listener.py b/plugins/msg-logging/listener.py index a9d1951..a42aaba 100644 --- a/plugins/msg-logging/listener.py +++ b/plugins/msg-logging/listener.py @@ -113,18 +113,18 @@ class MsgHandler(blcsdk.BaseHandler): def _get_or_add_room(room_id): - ctx = _id_room_dict.get(room_id, None) - if ctx is None: + room = _id_room_dict.get(room_id, None) + if room is None: if room_id is None: raise TypeError('room_id is None') - ctx = _id_room_dict[room_id] = Room(room_id) - return ctx + room = _id_room_dict[room_id] = Room(room_id) + return room def _del_room(room_id): - ctx = _id_room_dict.pop(room_id, None) - if ctx is not None: - ctx.close() + room = _id_room_dict.pop(room_id, None) + if room is not None: + room.close() class Room: diff --git a/plugins/native-ui/LICENSE b/plugins/native-ui/LICENSE new file mode 100644 index 0000000..5a3aae8 --- /dev/null +++ b/plugins/native-ui/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 xfgryujk + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/plugins/native-ui/listener.py b/plugins/native-ui/listener.py new file mode 100644 index 0000000..d7859f6 --- /dev/null +++ b/plugins/native-ui/listener.py @@ -0,0 +1,227 @@ +# -*- coding: utf-8 -*- +import __main__ +import dataclasses +import datetime +import logging +from typing import * + +import blcsdk +import blcsdk.models as sdk_models + +logger = logging.getLogger('native-ui.' + __name__) + +_msg_handler: Optional['MsgHandler'] = None +_key_room_dict: Dict[sdk_models.RoomKey, 'Room'] = {} + + +async def init(): + global _msg_handler + _msg_handler = MsgHandler() + blcsdk.set_msg_handler(_msg_handler) + + # 创建已有的房间。这一步失败了也没关系,只是有消息时才会创建房间 + 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_key, blc_room.room_id) + except blcsdk.SdkError: + pass + + +def shut_down(): + blcsdk.set_msg_handler(None) + + +class MsgHandler(blcsdk.BaseHandler): + def on_client_stopped(self, client: blcsdk.BlcPluginClient, exception: Optional[Exception]): + logger.info('blivechat disconnected') + __main__.start_shut_down() + + def _on_open_plugin_admin_ui( + self, client: blcsdk.BlcPluginClient, message: sdk_models.OpenPluginAdminUiMsg, extra: sdk_models.ExtraData + ): + pass + + 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_key, extra.room_id) + + 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_key) + + 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_key, extra.room_id) + room.add_danmaku(message.uid) + + 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_key, extra.room_id) + room.add_gift(GiftRecord( + uid=str(message.uid), # TODO SDK的uid改成Open ID + author_name=message.author_name, + gift_name=message.gift_name, + num=message.num, + price=message.total_coin / 1000, + )) + + def _on_add_member( + 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_key, extra.room_id) + + # 消息里没有价格,这里按最低算 + if message.privilege_type == sdk_models.GuardLevel.LV1: + guard_name = '舰长' + price = 138 + elif message.privilege_type == sdk_models.GuardLevel.LV2: + guard_name = '提督' + price = 1998 + elif message.privilege_type == sdk_models.GuardLevel.LV3: + guard_name = '总督' + price = 19998 + else: + guard_name = '未知舰队等级' + price = 0 + guard_name += f'({message.unit})' + + room.add_gift(GiftRecord( + uid=str(message.uid), # TODO SDK的uid改成Open ID + author_name=message.author_name, + gift_name=guard_name, + num=message.num, + price=price, + )) + + def _on_add_super_chat( + 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_key, extra.room_id) + room.add_super_chat(SuperChatRecord( + uid=str(message.uid), # TODO SDK的uid改成Open ID + author_name=message.author_name, + price=message.price, + content=message.content, + )) + + +def get_room(room_key: sdk_models.RoomKey): + return _key_room_dict.get(room_key, None) + + +def _get_or_add_room(room_key: sdk_models.RoomKey, room_id): + room = _key_room_dict.get(room_key, None) + if room is None: + if room_id is None: + raise TypeError('room_id is None') + room = _key_room_dict[room_id] = Room(room_key, room_id) + # TODO 打开房间窗口 + return room + + +def _del_room(room_key: sdk_models.RoomKey): + _key_room_dict.pop(room_key, None) + # TODO 关闭房间窗口 + + +@dataclasses.dataclass +class SuperChatRecord: + uid: str + author_name: str + price: float + content: str + time: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now) + + +@dataclasses.dataclass +class GiftRecord: + uid: str + author_name: str + gift_name: str + num: int + price: float + time: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now) + + +@dataclasses.dataclass +class PaidUserRecord: + uid: str + name: str + price: float + + +class Room: + def __init__(self, room_key: sdk_models.RoomKey, room_id: int): + self._room_key = room_key + self._room_id = room_id + + self._super_chats: List[SuperChatRecord] = [] + self._gifts: List[GiftRecord] = [] + self._uid_paid_user_dict: Dict[str, PaidUserRecord] = {} + + self._danmaku_num = 0 + self._interact_uids: Set[str] = set() + self._total_paid_price = 0 + + def add_danmaku(self, uid): + self._danmaku_num += 1 + self._interact_uids.add(uid) + + def add_super_chat(self, super_chat: SuperChatRecord): + self._super_chats.append(super_chat) + self._add_user_paid_price(PaidUserRecord( + uid=super_chat.uid, + name=super_chat.author_name, + price=super_chat.price, + )) + self._danmaku_num += 1 + self._interact_uids.add(super_chat.uid) + self._total_paid_price += super_chat.price + + def add_gift(self, gift: GiftRecord): + # 尝试合并 + is_merged = False + min_time_to_merge = gift.time - datetime.timedelta(seconds=10) + for old_gift in reversed(self._gifts): + if old_gift.time < min_time_to_merge: + break + if old_gift.uid == gift.uid and old_gift.gift_name == gift.gift_name: + old_gift.num += gift.num + old_gift.price += gift.price + is_merged = True + break + + if not is_merged: + self._gifts.append(gift) + if gift.price > 0.: + self._add_user_paid_price(PaidUserRecord( + uid=gift.uid, + name=gift.author_name, + price=gift.price, + )) + self._interact_uids.add(gift.uid) + self._total_paid_price += gift.price + + def _add_user_paid_price(self, paid_user: PaidUserRecord): + old_paid_user = self._uid_paid_user_dict.get(paid_user.uid, None) + if old_paid_user is None: + old_paid_user = self._uid_paid_user_dict[paid_user.uid] = PaidUserRecord( + uid=paid_user.uid, + name=paid_user.name, + price=0, + ) + old_paid_user.price += paid_user.price diff --git a/plugins/native-ui/main.pyw b/plugins/native-ui/main.pyw index 6530eb0..1d79add 100755 --- a/plugins/native-ui/main.pyw +++ b/plugins/native-ui/main.pyw @@ -3,13 +3,16 @@ import asyncio import logging.handlers import os +import signal import sys from typing import * +import wx import wxasync +import blcsdk import config -import designer.ui_base +import listener logger = logging.getLogger('native-ui') @@ -26,18 +29,38 @@ async def main(): async def init(): + init_signal_handlers() + init_logging() - global app - app = wxasync.WxAsyncApp() + await blcsdk.init() + if not blcsdk.is_sdk_version_compatible(): + raise RuntimeError('SDK version is not compatible') + + init_ui() + await listener.init() + + +def init_signal_handlers(): + signums = (signal.SIGINT, signal.SIGTERM) + try: + loop = asyncio.get_running_loop() + for signum in signums: + loop.add_signal_handler(signum, start_shut_down) + except NotImplementedError: + def signal_handler(*args): + asyncio.get_running_loop().call_soon(start_shut_down, *args) + + # 不太安全,但Windows只能用这个 + for signum in signums: + signal.signal(signum, signal_handler) - # TODO 测试 - frame = designer.ui_base.RoomFrameBase(None) - frame.chat_web_view.LoadURL('http://localhost:12450/room/test?minGiftPrice=0&showGiftName=true&relayMessagesByServer=true&lang=zh') - frame.paid_web_view.LoadURL('http://localhost:12450/room/test?showDanmaku=false&showGiftName=true&relayMessagesByServer=true&lang=zh') - frame.Show() - app.SetTopWindow(frame) +def start_shut_down(*_args): + if app is not None: + app.ExitMainLoop() + else: + wx.Exit() def init_logging(): @@ -55,6 +78,11 @@ def init_logging(): ) +def init_ui(): + global app + app = wxasync.WxAsyncApp(clearSigInt=False) + + async def run(): logger.info('Running event loop') await app.MainLoop() @@ -62,7 +90,8 @@ async def run(): async def shut_down(): - pass + listener.shut_down() + await blcsdk.shut_down() if __name__ == '__main__': diff --git a/plugins/native-ui/plugin.json b/plugins/native-ui/plugin.json new file mode 100644 index 0000000..8d22af8 --- /dev/null +++ b/plugins/native-ui/plugin.json @@ -0,0 +1,8 @@ +{ + "name": "原生UI", + "version": "1.0.0", + "author": "xfgryujk", + "description": "提供托盘图标和用来看弹幕的窗口。支持置顶窗口和设置不透明度。支持付费消息分开显示。统计弹幕数、付费用户等信息", + "run": "native-ui.exe", + "enabled": true +} diff --git a/plugins/native-ui/ui/room.py b/plugins/native-ui/ui/room.py new file mode 100644 index 0000000..9e0e600 --- /dev/null +++ b/plugins/native-ui/ui/room.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +import designer.ui_base + + +class RoomFrame(designer.ui_base.RoomFrameBase): + def __init__(self, parent): + super().__init__(parent) + + self.chat_web_view.LoadURL('http://localhost:12450/room/test?minGiftPrice=0&showGiftName=true&relayMessagesByServer=true&lang=zh') + self.paid_web_view.LoadURL('http://localhost:12450/room/test?showDanmaku=false&showGiftName=true&relayMessagesByServer=true&lang=zh') diff --git a/plugins/text-to-speech/tts.py b/plugins/text-to-speech/tts.py index be2278c..ae11f6c 100644 --- a/plugins/text-to-speech/tts.py +++ b/plugins/text-to-speech/tts.py @@ -156,10 +156,15 @@ class TaskQueue: q = self._queues[task.priority] # 尝试合并 + try_merge_count = 0 for old_task in reversed(q): if old_task.merge(task): return True + try_merge_count += 1 + if try_merge_count >= 5: + break + # 没满直接push if ( self._max_size is None