diff --git a/app.py b/app.py index 5e4888a..1f2b5ca 100644 --- a/app.py +++ b/app.py @@ -1,14 +1,44 @@ -from blive import BLiver, Events, BLiverCtx, DanMuMsg +from blive import BLiver, Events, BLiverCtx +from blive.msg import DanMuMsg, HotRankChangeV2Msg, InteractWordMsg, SendGiftMsg -app = BLiver(510) +app = BLiver(605) -@app.handler(Events.DANMU_MSG) +@app.on(Events.DANMU_MSG) async def listen(ctx: BLiverCtx): danmu = DanMuMsg(ctx.body) - print(danmu.content()) - print(danmu.sender()) - print(danmu.timestamp()) + print( + f"\n{danmu.sender['name']}({danmu.sender['medal']['medal_name']}:{danmu.sender['medal']['medal_level']}): \"{danmu.content}\"\n " + ) + + +@app.on(Events.INTERACT_WORD) +async def listen_join(ctx: BLiverCtx): + join = InteractWordMsg(ctx.body) + print( + "欢迎", + f"{join.user['name']} ({join.user['medal']['medal_name']}:{join.user['medal']['medal_level']})", + "进入直播间", + ) + + +@app.on(Events.SUPER_CHAT_MESSAGE) +async def listen_sc(ctx: BLiverCtx): + print(ctx.body) + + +@app.on(Events.SEND_GIFT) +async def listen_gift(ctx: BLiverCtx): + msg = SendGiftMsg(ctx.body) + print(f"{msg.sender['name']} 送出 {msg.gift['gift_name']}") + + +@app.on(Events.HOT_RANK_CHANGED_V2) +async def hot(ctx: BLiverCtx): + msg = HotRankChangeV2Msg(ctx.body) + print( + f"恭喜 {ctx.bliver.uname} 在 {msg.area_name} 区 的 {msg.rank_desc} 榜单中获得第 {msg.rank} 名" + ) app.run() diff --git a/blive/core.py b/blive/core.py index bea73c3..b393258 100644 --- a/blive/core.py +++ b/blive/core.py @@ -21,7 +21,7 @@ def get_blive_ws_url(roomid, ssl=False): return url, data["data"]["token"] -def get_blive_room_id(roomid): +def get_blive_room_info(roomid): """ 得到b站直播间id,(短id不是真实的id) @@ -34,8 +34,7 @@ def get_blive_room_id(roomid): data = resp.json() return ( data["data"]["room_info"]["room_id"], - data["data"]["room_info"]["short_id"], - data["data"]["room_info"]["uid"], + data["data"]["anchor_info"]["base_info"]["uname"], ) @@ -81,7 +80,7 @@ class AuthReplyCode(enum.IntEnum): class ProtocolVersion(enum.IntEnum): NORMAL = 0 # 未压缩 HEARTBEAT = 1 # 心跳 - INFLATE = 2 # zlib压缩 + DEFLATE = 2 # zlib压缩 BROTLI = 3 # brotil 压缩 @@ -152,7 +151,6 @@ class B_MsgPackage: if header.operation == Operation.HEARTBEAT_REPLY: # 心跳不会粘包 packages.append((header, data[4:].decode("utf-8"))) - # 通知包处理 elif header.operation == Operation.NOTIFY: @@ -182,7 +180,7 @@ class B_MsgPackage: packages.append((header, data[16:].decode("utf-8"))) # NOTIFY 消息可能会粘包 - if header.version == ProtocolVersion.INFLATE: + if header.version == ProtocolVersion.DEFLATE: # 先zlib解码 data = zlib.decompress(data) notify_pk_process(data) @@ -213,7 +211,7 @@ class Events(str, enum.Enum): ROOM_CHANGE = "ROOM_CHANGE" # 房间信息改变 ROOM_RANK = "ROOM_RANK" # 排名改变 DANMU_MSG = "DANMU_MSG" # 接收到弹幕【自动回复】 - SEND_GIFT = "ROOM_RANK" # 有人送礼【答谢送礼】 + SEND_GIFT = "SEND_GIFT" # 有人送礼【答谢送礼】 WELCOME_GUARD = "WELCOME_GUARD" # 舰长进入(不会触发) ENTRY_EFFECT = "ENTRY_EFFECT" # 舰长、高能榜、老爷进入【欢迎舰长】 WELCOME = "WELCOME" # 老爷进入 @@ -222,7 +220,7 @@ class Events(str, enum.Enum): SHARE = "SHARE" # 用户分享直播间 SPECIAL_ATTENTION = "SPECIAL_ATTENTION" # 特别关注直播间,可用%special%判断 ROOM_REAL_TIME_MESSAGE_UPDATE = "ROOM_REAL_TIME_MESSAGE_UPDATE" # 粉丝数量改变 - SUPER_CHAT_MESSAGE = "ROOM_REAL_TIME_MESSAGE_UPDATE" # 醒目留言 + SUPER_CHAT_MESSAGE = "SUPER_CHAT_MESSAGE" # 醒目留言 SUPER_CHAT_MESSAGE_JPN = "SUPER_CHAT_MESSAGE_JPN" # 醒目留言日文翻译 SUPER_CHAT_MESSAGE_DELETE = "SUPER_CHAT_MESSAGE_DELETE" # 删除醒目留言 ROOM_BLOCK_MSG = "ROOM_BLOCK_MSG" # 用户被禁言,%uname%昵称 @@ -254,3 +252,10 @@ class Events(str, enum.Enum): # 勋章升级,仅送礼物后触发,需设置中开启“监听勋章升级”。%medal_level%获取新等级(但用户当前勋章不一定是本直播间) MEDAL_UPGRADE = "MEDAL_UPGRADE" STOP_LIVE_ROOM_LIST = "STOP_LIVE_ROOM_LIST" # 停止直播的房间 + WIDGET_BANNER = "WIDGET_BANNER" # 小部件横幅 + PK_BATTLE_PROCESS_NEW = "PK_BATTLE_PROCESS_NEW" # 开始pk + PK_BATTLE_PROCESS = "PK_BATTLE_PROCESS" # pk + COMMON_NOTICE_DANMAKU = "COMMON_NOTICE_DANMAKU" # 弹幕通知 + HOT_RANK_CHANGED_V2 = "HOT_RANK_CHANGED_V2" # 热门榜改变v2 + PK_BATTLE_SETTLE = "PK_BATTLE_SETTLE" # pk结果 + PK_BATTLE_PRE_NEW = "PK_BATTLE_PRE_NEW" # pk预创建 diff --git a/blive/framework.py b/blive/framework.py index e1c3d3c..42ea4a2 100644 --- a/blive/framework.py +++ b/blive/framework.py @@ -1,7 +1,7 @@ import sys import json import asyncio -from typing import Dict, Tuple +from typing import Dict, List, Tuple, Union import aiohttp from aiohttp.client_ws import ClientWebSocketResponse from aiohttp.http_websocket import WSMessage @@ -13,7 +13,7 @@ from .core import ( Operation, PackageHeader, packman, - get_blive_room_id, + get_blive_room_info, get_blive_ws_url, certification, heartbeat, @@ -55,20 +55,25 @@ class Processor: class BLiver: def __init__(self, roomid, logger=None, log_level="INFO"): self.roomid = roomid + self.real_roomid, self.uname = get_blive_room_info(roomid) if not logger: self.logger = loguru.logger self.logger.remove() + self.logger.add(sys.stderr, level=log_level) else: self.logger = logger - self.logger.add(sys.stderr, level=log_level) self._ws: ClientWebSocketResponse = None self.scheduler = AsyncIOScheduler(timezone="Asia/ShangHai") self.processor = Processor(logger=self.logger) - def handler(self, event: Events): + def on(self, event: Union[Events, List[Events]]): def f_wrapper(func): - self.logger.debug("handler added") - self.processor.register(event, func) + self.logger.debug("handler added,{}", func) + if isinstance(event, list): + for e in event: + self.processor.register(e, func) + else: + self.processor.register(event, func) return func return f_wrapper @@ -121,22 +126,20 @@ class BLiver: self.logger.debug("heartbeat sended") async def listen(self): - rommid, _, _ = get_blive_room_id(self.roomid) # 如果是短id,就得到直播间真实id - url, token = get_blive_ws_url(rommid) + + url, token = get_blive_ws_url(self.real_roomid) async with aiohttp.ClientSession().ws_connect(url) as ws: self._ws = ws await ws.send_bytes( - packman.pack(certification(rommid, token), Operation.AUTH) + packman.pack(certification(self.real_roomid, token), Operation.AUTH) ) self.scheduler.add_job(self.heartbeat, trigger="interval", seconds=30) self.scheduler.start() # 开始监听 while True: msg: WSMessage = await ws.receive() - # print(msg) if msg.type != aiohttp.WSMsgType.BINARY: continue - # print(msg.data) mq = packman.unpack(msg.data) self.logger.debug("received msg:\n{}", mq) tasks = [self.processor.process(BLiverCtx(self, m)) for m in mq] diff --git a/blive/msg.py b/blive/msg.py index 6aba14d..76393b2 100644 --- a/blive/msg.py +++ b/blive/msg.py @@ -11,12 +11,10 @@ class BaseMsg(ABC): super().__init__() self.body = body + @property def cmd(self): return self.body["cmd"] - def info(self): - return self.body["info"] - def __repr__(self) -> str: return json.dumps(self.body) @@ -25,11 +23,116 @@ class DanMuMsg(BaseMsg): def __init__(self, body) -> None: super(DanMuMsg, self).__init__(body) + @property def content(self): - return self.info()[1] + return self.body["info"][1] + @property def sender(self): - return {"id": self.body["info"][2][0], "name": self.body["info"][2][1]} + return { + "id": self.body["info"][2][0], + "name": self.body["info"][2][1], + "medal": { + "medal_name": self.body["info"][3][1] if self.body["info"][3] else "", + "medal_level": self.body["info"][3][0] if self.body["info"][3] else 0, + }, + } + @property def timestamp(self): return self.body["info"][9] + + +class InteractWordMsg(BaseMsg): + def __init__(self, body) -> None: + super().__init__(body) + + @property + def user(self): + return { + "id": self.body["data"]["uid"], + "name": self.body["data"]["uname"], + "medal": { + "medal_name": self.body["data"]["fans_medal"]["medal_name"], + "medal_level": self.body["data"]["fans_medal"]["medal_level"], + }, + } + + @property + def timestamp(self): + return self.body["data"]["timestamp"] + + +class StopLiveRoomListMsg(BaseMsg): + def __init__(self, body) -> None: + super().__init__(body) + + @property + def room_id_list(self): + return self.body["data"]["room_id_list"] + + +class HotRankChangeV2Msg(BaseMsg): + def __init__(self, body) -> None: + super().__init__(body) + + @property + def area_name(self): + return self.body["data"]["area_name"] + + @property + def rank_desc(self): + return self.body["data"]["rank_desc"] + + @property + def rank(self): + return self.body["data"]["rank"] + + @property + def trend(self): + return self.body["data"]["trend"] + + @property + def timestamp(self): + return self.body["data"]["timestamp"] + + +class SendGiftMsg(BaseMsg): + # TODO 礼物逻辑复杂, 考虑更复杂的封装类 + def __init__(self, body) -> None: + + super().__init__(body) + + @property + def sender(self): + return { + "id": self.body["data"]["uid"], + "name": self.body["data"]["uname"], + "medal": { + "medal_name": self.body["data"]["medal_info"]["medal_name"], + "medal_level": self.body["data"]["medal_info"]["medal_level"], + }, + } + + @property + def action(self): + return self.body["data"]["action"] + + @property + def gift(self): + return { + "gift_id": self.body["data"]["giftId"], + "gift_name": self.body["data"]["giftName"], + "gift_type": self.body["data"]["giftType"], + } + + @property + def combo(self): + return { + "batch_combo_id": self.body["data"]["batch_combo_id"], + "batch_combo_send": self.body["data"]["batch_combo_send"], + "combo_resources_id": self.body["data"]["combo_resources_id"], + "combo_send": self.body["data"]["combo_send"], + "combo_stay_time": self.body["data"]["combo_stay_time"], + "combo_total_coin": self.body["data"]["combo_total_coin"], + }