GUI插件添加消息处理

This commit is contained in:
John Smith 2024-03-09 22:37:11 +08:00
parent d6030aba9c
commit 8277c56224
7 changed files with 317 additions and 17 deletions

View File

@ -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:

21
plugins/native-ui/LICENSE Normal file
View File

@ -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.

View File

@ -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

View File

@ -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')
# 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()
init_ui()
await listener.init()
app.SetTopWindow(frame)
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)
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__':

View File

@ -0,0 +1,8 @@
{
"name": "原生UI",
"version": "1.0.0",
"author": "xfgryujk",
"description": "提供托盘图标和用来看弹幕的窗口。支持置顶窗口和设置不透明度。支持付费消息分开显示。统计弹幕数、付费用户等信息",
"run": "native-ui.exe",
"enabled": true
}

View File

@ -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')

View File

@ -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