From cfda838e01cd804fea0fb738f27da05553cc33ad Mon Sep 17 00:00:00 2001
From: John Smith <xfgryujk@126.com>
Date: Sat, 18 Dec 2021 20:26:51 +0800
Subject: [PATCH] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E5=BC=B9=E5=B9=95=E5=8D=8F?=
 =?UTF-8?q?=E8=AE=AE=E7=89=88=E6=9C=AC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 blivedm/client.py   |  9 +++---
 blivedm/handlers.py | 35 ++++++++++++----------
 blivedm/models.py   | 72 ++++++++++++++++++++++++++++++++++++++-------
 requirements.txt    |  1 +
 4 files changed, 86 insertions(+), 31 deletions(-)

diff --git a/blivedm/client.py b/blivedm/client.py
index b9715f9..8db237e 100644
--- a/blivedm/client.py
+++ b/blivedm/client.py
@@ -6,10 +6,10 @@ import json
 import logging
 import ssl as ssl_
 import struct
-import zlib
 from typing import *
 
 import aiohttp
+import brotli
 
 from . import handlers
 
@@ -423,9 +423,8 @@ class BLiveClient:
         auth_params = {
             'uid': self._uid,
             'roomid': self._room_id,
-            'protover': 2,
+            'protover': 3,
             'platform': 'web',
-            'clientver': '1.14.3',
             'type': 2
         }
         if self._host_server_token is not None:
@@ -531,9 +530,9 @@ class BLiveClient:
         """
         if header.operation == Operation.SEND_MSG_REPLY:
             # 业务消息
-            if header.ver == ProtoVer.DEFLATE:
+            if header.ver == ProtoVer.BROTLI:
                 # 压缩过的先解压,为了避免阻塞网络线程,放在其他线程执行
-                body = await self._loop.run_in_executor(None, zlib.decompress, body)
+                body = await self._loop.run_in_executor(None, brotli.decompress, body)
                 await self._parse_ws_message(body)
             elif header.ver == ProtoVer.NORMAL:
                 # 没压缩过的直接反序列化,因为有万恶的GIL,这里不能并行避免阻塞
diff --git a/blivedm/handlers.py b/blivedm/handlers.py
index 4aca61c..7db1102 100644
--- a/blivedm/handlers.py
+++ b/blivedm/handlers.py
@@ -14,26 +14,29 @@ logger = logging.getLogger('blivedm')
 
 # 常见可忽略的cmd
 IGNORED_CMDS = (
-    'INTERACT_WORD',
-    'ROOM_BANNER',
-    'ROOM_REAL_TIME_MESSAGE_UPDATE',
-    'NOTICE_MSG',
     'COMBO_SEND',
-    'COMBO_END',
     'ENTRY_EFFECT',
-    'WELCOME_GUARD',
-    'WELCOME',
-    'ROOM_RANK',
-    'ACTIVITY_BANNER_UPDATE_V2',
-    'PANEL',
-    'SUPER_CHAT_MESSAGE_JPN',
-    'USER_TOAST_MSG',
-    'ROOM_BLOCK_MSG',
+    'HOT_RANK_CHANGED',
+    'HOT_RANK_CHANGED_V2',
+    'INTERACT_WORD',
     'LIVE',
+    'LIVE_INTERACTIVE_GAME',
+    'NOTICE_MSG',
+    'ONLINE_RANK_COUNT',
+    'ONLINE_RANK_TOP3',
+    'ONLINE_RANK_V2',
+    'PK_BATTLE_END',
+    'PK_BATTLE_FINAL_PROCESS',
+    'PK_BATTLE_PROCESS',
+    'PK_BATTLE_PROCESS_NEW',
+    'PK_BATTLE_SETTLE',
+    'PK_BATTLE_SETTLE_USER',
+    'PK_BATTLE_SETTLE_V2',
     'PREPARING',
-    'room_admin_entrance',
-    'ROOM_ADMINS',
-    'ROOM_CHANGE',
+    'ROOM_REAL_TIME_MESSAGE_UPDATE',
+    'STOP_LIVE_ROOM_LIST',
+    'SUPER_CHAT_MESSAGE_JPN',
+    'WIDGET_BANNER',
 )
 
 # 已打日志的未知cmd
diff --git a/blivedm/models.py b/blivedm/models.py
index aff237c..f1931d4 100644
--- a/blivedm/models.py
+++ b/blivedm/models.py
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+import json
 from typing import *
 
 __all__ = (
@@ -39,10 +40,14 @@ class DanmakuMessage:
     :param font_size: 字体尺寸
     :param color: 颜色
     :param timestamp: 时间戳(毫秒)
-    :param rnd: 随机数,可能是去重用的
+    :param rnd: 随机数,前端叫作弹幕ID,可能是去重用的
     :param uid_crc32: 用户ID文本的CRC32
     :param msg_type: 是否礼物弹幕(节奏风暴)
     :param bubble: 右侧评论栏气泡
+    :param dm_type: 弹幕类型,0文本,1表情,2语音
+    :param emoticon_options: 表情参数
+    :param voice_config: 语音参数
+    :param mode_info: 一些附加参数
 
     :param msg: 弹幕内容
 
@@ -82,6 +87,10 @@ class DanmakuMessage:
         uid_crc32: str = None,
         msg_type: int = None,
         bubble: int = None,
+        dm_type: int = None,
+        emoticon_options: Union[dict, str] = None,
+        voice_config: Union[dict, str] = None,
+        mode_info: dict = None,
 
         msg: str = None,
 
@@ -118,6 +127,10 @@ class DanmakuMessage:
         self.uid_crc32: str = uid_crc32
         self.msg_type: int = msg_type
         self.bubble: int = bubble
+        self.dm_type: int = dm_type
+        self.emoticon_options: Union[dict, str] = emoticon_options
+        self.voice_config: Union[dict, str] = voice_config
+        self.mode_info: dict = mode_info
 
         self.msg: str = msg
 
@@ -172,6 +185,10 @@ class DanmakuMessage:
             uid_crc32=info[0][7],
             msg_type=info[0][9],
             bubble=info[0][10],
+            dm_type=info[0][12],
+            emoticon_options=info[0][13],
+            voice_config=info[0][14],
+            mode_info=info[0][15],
 
             msg=info[1],
 
@@ -201,6 +218,37 @@ class DanmakuMessage:
             privilege_type=info[7],
         )
 
+    @property
+    def emoticon_options_dict(self) -> dict:
+        """
+        示例:
+        {'bulge_display': 0, 'emoticon_unique': 'official_13', 'height': 60, 'in_player_area': 1, 'is_dynamic': 1,
+         'url': 'https://i0.hdslb.com/bfs/live/a98e35996545509188fe4d24bd1a56518ea5af48.png', 'width': 183}
+        """
+        if isinstance(self.emoticon_options, dict):
+            return self.emoticon_options
+        try:
+            return json.loads(self.emoticon_options)
+        except (json.JSONDecodeError, TypeError):
+            return {}
+
+    @property
+    def voice_config_dict(self) -> dict:
+        """
+        示例:
+        {'voice_url': 'https%3A%2F%2Fboss.hdslb.com%2Flive-dm-voice%2Fb5b26e48b556915cbf3312a59d3bb2561627725945.wav
+         %3FX-Amz-Algorithm%3DAWS4-HMAC-SHA256%26X-Amz-Credential%3D2663ba902868f12f%252F20210731%252Fshjd%252Fs3%25
+         2Faws4_request%26X-Amz-Date%3D20210731T100545Z%26X-Amz-Expires%3D600000%26X-Amz-SignedHeaders%3Dhost%26
+         X-Amz-Signature%3D114e7cb5ac91c72e231c26d8ca211e53914722f36309b861a6409ffb20f07ab8',
+         'file_format': 'wav', 'text': '汤,下午好。', 'file_duration': 1}
+        """
+        if isinstance(self.voice_config, dict):
+            return self.voice_config
+        try:
+            return json.loads(self.voice_config)
+        except (json.JSONDecodeError, TypeError):
+            return {}
+
 
 class GiftMessage:
     """
@@ -217,9 +265,10 @@ class GiftMessage:
     :param gift_type: 礼物类型(未知)
     :param action: 目前遇到的有'喂食'、'赠送'
     :param price: 礼物单价瓜子数
-    :param rnd: 随机数,可能是去重用的
-    :param coin_type: 瓜子类型,'silver'或'gold'
+    :param rnd: 随机数,可能是去重用的。有时是时间戳+去重ID,有时是UUID
+    :param coin_type: 瓜子类型,'silver'或'gold',1000金瓜子 = 1元
     :param total_coin: 总瓜子数
+    :param tid: 可能是事务ID,有时和rnd相同
     """
 
     def __init__(
@@ -238,6 +287,7 @@ class GiftMessage:
         rnd: str = None,
         coin_type: str = None,
         total_coin: int = None,
+        tid: str = None,
     ):
         self.gift_name = gift_name
         self.num = num
@@ -253,6 +303,7 @@ class GiftMessage:
         self.rnd = rnd
         self.coin_type = coin_type
         self.total_coin = total_coin
+        self.tid = tid
 
     @classmethod
     def from_command(cls, data: dict):
@@ -271,6 +322,7 @@ class GiftMessage:
             rnd=data['rnd'],
             coin_type=data['coin_type'],
             total_coin=data['total_coin'],
+            tid=data['tid'],
         )
 
 
@@ -285,8 +337,8 @@ class GuardBuyMessage:
     :param price: 单价金瓜子数
     :param gift_id: 礼物ID
     :param gift_name: 礼物名
-    :param start_time: 开始时间戳,和结束时间戳一样
-    :param end_time: 结束时间戳,和开始时间戳一样
+    :param start_time: 开始时间戳,和结束时间戳相同
+    :param end_time: 结束时间戳,和开始时间戳相同
     """
 
     def __init__(
@@ -332,10 +384,10 @@ class SuperChatMessage:
 
     :param price: 价格(人民币)
     :param message: 消息
-    :param message_jpn: 消息日文翻译(目前只出现在SUPER_CHAT_MESSAGE_JPN)
+    :param message_trans: 消息日文翻译(目前只出现在SUPER_CHAT_MESSAGE_JPN)
     :param start_time: 开始时间戳
     :param end_time: 结束时间戳
-    :param time: 持续时间(结束时间戳 - 开始时间戳)
+    :param time: 剩余时间(约等于 结束时间戳 - 开始时间戳)
     :param id_: str,醒目留言ID,删除时用
     :param gift_id: 礼物ID
     :param gift_name: 礼物名
@@ -355,7 +407,7 @@ class SuperChatMessage:
         self,
         price: int = None,
         message: str = None,
-        message_jpn: str = None,
+        message_trans: str = None,
         start_time: int = None,
         end_time: int = None,
         time: int = None,
@@ -375,7 +427,7 @@ class SuperChatMessage:
     ):
         self.price: int = price
         self.message: str = message
-        self.message_jpn: str = message_jpn
+        self.message_trans: str = message_trans
         self.start_time: int = start_time
         self.end_time: int = end_time
         self.time: int = time
@@ -398,7 +450,7 @@ class SuperChatMessage:
         return cls(
             price=data['price'],
             message=data['message'],
-            message_jpn=data['message_trans'],
+            message_trans=data['message_trans'],
             start_time=data['start_time'],
             end_time=data['end_time'],
             time=data['time'],
diff --git a/requirements.txt b/requirements.txt
index 7a37a73..f6ec99b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1,2 @@
 aiohttp==3.7.4
+Brotli==1.0.9