From 6b3e896dc6d7e3fb210580313304418496244e11 Mon Sep 17 00:00:00 2001
From: Cam <yulinfeng000@gmail.com>
Date: Tue, 21 Jun 2022 10:33:40 +0800
Subject: [PATCH] add global error handle, and update README, add new example
 for error handle, update msg.py for add new property for SuperChatMsg

---
 README.md                | 23 ++++++++++++++
 blive/framework.py       | 29 ++++++++++++++++++
 blive/msg.py             | 13 ++++++++
 example/error_handler.py | 66 ++++++++++++++++++++++++++++++++++++++++
 example/multi_room.py    |  2 +-
 5 files changed, 132 insertions(+), 1 deletion(-)
 create mode 100644 example/error_handler.py

diff --git a/README.md b/README.md
index 7941b6f..8067e5b 100644
--- a/README.md
+++ b/README.md
@@ -156,6 +156,26 @@ async def show():
     return list(BLIVER_POOL.keys())
 ```
 
+
+## 全局异常处理
+
+全局异常处理分为两个共享级别,分别为类级别和实例级别,在类上注册的异常处理为所有类实例共享,实例级别的异常处理只有实例自身拥有
+
+```python
+
+app = BLiver(510)
+
+@app.catch(ZeroDivisionError)
+def err_handler(e, app: BLiver):
+    print(f"{app.uname} catch ZeroDivisionError", e)
+   
+@app.on(Events.DANMU_MSG)
+async def danmu_handler(ctx):
+   1 / 0 # will raise ZeroDivisionError
+
+azi.run()
+```
+
 ## 项目简介
 
 - blive 文件夹为框架代码
@@ -175,6 +195,9 @@ async def show():
 - example/with_fastapi.py
    与fastapi 配合使用的例子
 
+- example/error_handler.py
+   错误处理的例子
+
 ## TODO
 
 - 更多的消息操作类(欢迎各位提pr)
diff --git a/blive/framework.py b/blive/framework.py
index 08e40a1..05881e6 100644
--- a/blive/framework.py
+++ b/blive/framework.py
@@ -1,3 +1,4 @@
+from socket import gaierror
 import sys
 import json
 import asyncio
@@ -74,6 +75,25 @@ class Processor:
 
 
 class BLiver:
+    _global_catches = {}
+
+    def catch(self, err_type):
+        def _err_handler_wrapper(fn):
+            self.register_error_handler(err_type, fn)
+
+        return _err_handler_wrapper
+
+    @classmethod
+    def register_global_error_handler(cls, err_type, fn):
+        err_handlers = cls._global_catches.get(err_type, [])
+        err_handlers.append(fn)
+        cls._global_catches[err_type] = err_handlers
+
+    def register_error_handler(self, err_type, fn):
+        err_handlers = self._catches.get(err_type, [])
+        err_handlers.append(fn)
+        self._catches[err_type] = err_handlers
+
     def __init__(self, roomid, uid=0, logger=None, log_level="INFO"):
         self.roomid = roomid
         self.uid = uid
@@ -84,6 +104,7 @@ class BLiver:
             self.logger.add(sys.stderr, level=log_level)
         else:
             self.logger = logger
+        self._catches = {}  # to handle errors
         self._ws: ClientWebSocketResponse = None
         self.packman = BWS_MsgPackage()
         self.scheduler = AsyncIOScheduler(timezone="Asia/ShangHai")
@@ -162,6 +183,7 @@ class BLiver:
         except (
             aiohttp.ClientConnectionError,
             asyncio.TimeoutError,
+            ConnectionError,
             ConnectionResetError,
         ):
             self.logger.warning("send heartbeat error, will reconnect ws")
@@ -185,6 +207,7 @@ class BLiver:
                 aiohttp.ClientConnectionError,
                 asyncio.TimeoutError,
                 ConnectionError,
+                ConnectionResetError,
             ):
                 self.logger.warning(
                     "connect failed, will retry {}, current: {}", retries, i + 1
@@ -226,6 +249,12 @@ class BLiver:
                 self.logger.warning("ws conn will reconnect")
                 await self.connect()
 
+            # to handler errors
+            except tuple(self._catches.keys()) as e:
+                [eh(e,self) for eh in self._catches.get(type(e), [])]
+            except tuple(BLiver._global_catches.keys()) as e:
+                [eh(e,self) for eh in BLiver._global_catches.get(type(e), [])]
+
     async def graceful_close(self):
         await self._ws.close()
         await self.aio_session.close()
diff --git a/blive/msg.py b/blive/msg.py
index 0aaf04b..a891ac7 100644
--- a/blive/msg.py
+++ b/blive/msg.py
@@ -201,6 +201,19 @@ class SuperChatMsg(BaseMsg):
     @property
     def time(self):
         return dict_chain_get(self.body, "data.time")
+    
+    @property  # 头像
+    def avatar_url(self):
+        return dict_chain_get(self.body, "data.user_info.face")
+
+    @property
+    def anchor_uname(self):
+        return dict_chain_get(self.body, "data.medal_info.anchor_uname")
+
+    @property  # 背景色
+    def color(self):
+        return dict_chain_get(self.body, "data.background_bottom_color")
+
 
 
 class EntryEffectMsg(BaseMsg):
diff --git a/example/error_handler.py b/example/error_handler.py
new file mode 100644
index 0000000..196fc3e
--- /dev/null
+++ b/example/error_handler.py
@@ -0,0 +1,66 @@
+"""监听多个直播间的例子"""
+
+import asyncio
+from blive import BLiver, Events, BLiverCtx
+from blive.msg import DanMuMsg
+
+# 多个对象共用的全局异常处理
+# 首先定义全局异常处理handler
+
+
+def global_error_handler(e, app: BLiver):
+    print(f"{app.uname} 全局异常捕获", e)
+
+
+# 调用类方法注册异常以及其处理函数,需在实例化之前注册,注册后所有BLiver共同拥有该异常处理
+BLiver.register_global_error_handler(ZeroDivisionError, global_error_handler)
+
+
+# 定义弹幕事件handler,为了演示异常处理直接在方法中抛出异常
+async def azi_timeout_error(ctx: BLiverCtx):
+    raise TimeoutError
+
+
+async def ke_type_error(ctx):
+    raise TypeError
+
+
+async def zero_division_error(ctx):
+    1 / 0
+
+
+# 两个直播间
+ke = BLiver(21716679)
+azi = BLiver(7983476)
+
+# 注册handler
+ke.register_handler(Events.INTERACT_WORD, zero_division_error)
+azi.register_handler(Events.INTERACT_WORD, zero_division_error)
+ke.register_handler(Events.DANMU_MSG, ke_type_error)
+azi.register_handler(Events.DANMU_MSG, azi_timeout_error)
+
+
+# 类实例级别的异常处理,实例与实例之间不共享
+ke.register_error_handler(
+    TypeError, lambda e, app: print(f"{app.uname} catch TypeError", e)
+)
+
+# 实例级别的异常处理可以用注解方式进行注册
+@azi.catch(TimeoutError)
+def azi_handler(e, app):
+    print(f"{app.uname} catch TimeoutError", e)
+
+
+async def main():
+
+    # 以异步task的形式运行
+    task1 = ke.run_as_task()
+    task2 = azi.run_as_task()
+
+    # await 两个任务
+    await asyncio.gather(*[task1, task2])
+
+
+if __name__ == "__main__":
+    loop = asyncio.get_event_loop()
+    loop.run_until_complete(main())
diff --git a/example/multi_room.py b/example/multi_room.py
index d936e39..97136d8 100644
--- a/example/multi_room.py
+++ b/example/multi_room.py
@@ -9,7 +9,7 @@ from blive.msg import DanMuMsg
 async def listen(ctx: BLiverCtx):
     danmu = DanMuMsg(ctx.body)
     print(
-        f'\n{danmu.sender.name} ({danmu.sender.medal.medal_name}:{danmu.sender.medal.medal_level}): "{danmu.content}"\n'
+        f'\n【{ctx.bliver.uname}】{danmu.sender.name} ({danmu.sender.medal.medal_name}:{danmu.sender.medal.medal_level}): "{danmu.content}"\n'
     )