From b8cb5e96e997845577a591e853af1dcea0eba6bd Mon Sep 17 00:00:00 2001
From: John Smith <xfgryujk@126.com>
Date: Mon, 1 Jul 2019 18:32:54 +0800
Subject: [PATCH] =?UTF-8?q?=E9=98=B2=E6=AD=A2=E8=8E=B7=E5=8F=96=E5=A4=B4?=
 =?UTF-8?q?=E5=83=8F=E9=A2=91=E7=8E=87=E5=A4=AA=E9=AB=98=E8=A2=ABban?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 views/chat.py | 58 ++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 46 insertions(+), 12 deletions(-)

diff --git a/views/chat.py b/views/chat.py
index db8483c..dba3aef 100644
--- a/views/chat.py
+++ b/views/chat.py
@@ -23,30 +23,48 @@ class Command(enum.IntEnum):
     ADD_MEMBER = 3
 
 
+DEFAULT_AVATAR_URL = 'https://static.hdslb.com/images/member/noface.gif'
+
 _http_session = aiohttp.ClientSession()
 _avatar_url_cache: Dict[int, str] = {}
+_last_fetch_avatar_time = datetime.datetime.now()
 _last_avatar_failed_time = None
+_uids_to_fetch_avatar = asyncio.Queue(15)
 
 
 async def get_avatar_url(user_id):
     if user_id in _avatar_url_cache:
         return _avatar_url_cache[user_id]
 
-    global _last_avatar_failed_time
+    global _last_avatar_failed_time, _last_fetch_avatar_time
+    cur_time = datetime.datetime.now()
+    # 防止获取头像频率太高被ban
+    if (cur_time - _last_fetch_avatar_time).total_seconds() < 0.2:
+        # 由_fetch_avatar_loop过一段时间再获取并缓存
+        try:
+            _uids_to_fetch_avatar.put_nowait(user_id)
+        except asyncio.QueueFull:
+            pass
+        return DEFAULT_AVATAR_URL
+
     if _last_avatar_failed_time is not None:
-        if (datetime.datetime.now() - _last_avatar_failed_time).seconds < 5 * 60:
-            # 5分钟以内被ban
-            return 'https://static.hdslb.com/images/member/noface.gif'
+        if (cur_time - _last_avatar_failed_time).total_seconds() < 5 * 60 + 3:
+            # 5分钟以内被ban,解封大约要15分钟
+            return DEFAULT_AVATAR_URL
         else:
             _last_avatar_failed_time = None
 
-    async with _http_session.get('https://api.bilibili.com/x/space/acc/info',
-                                 params={'mid': user_id}) as r:
-        if r.status != 200:  # 可能会被B站ban
-            logger.warning('获取头像失败:status=%d %s uid=%d', r.status, r.reason, user_id)
-            _last_avatar_failed_time = datetime.datetime.now()
-            return 'https://static.hdslb.com/images/member/noface.gif'
-        data = await r.json()
+    _last_fetch_avatar_time = cur_time
+    try:
+        async with _http_session.get('https://api.bilibili.com/x/space/acc/info',
+                                     params={'mid': user_id}) as r:
+            if r.status != 200:  # 可能会被B站ban
+                logger.warning('获取头像失败:status=%d %s uid=%d', r.status, r.reason, user_id)
+                _last_avatar_failed_time = cur_time
+                return DEFAULT_AVATAR_URL
+            data = await r.json()
+    except aiohttp.ServerDisconnectedError:
+        return DEFAULT_AVATAR_URL
     url = data['data']['face']
     if not url.endswith('noface.gif'):
         url += '@48w_48h'
@@ -59,6 +77,22 @@ async def get_avatar_url(user_id):
     return url
 
 
+async def _fetch_avatar_loop():
+    while True:
+        try:
+            user_id = await _uids_to_fetch_avatar.get()
+            if user_id in _avatar_url_cache:
+                continue
+            # 延时长一些使实时弹幕有机会获取头像
+            await asyncio.sleep(0.4 - (datetime.datetime.now() - _last_fetch_avatar_time).total_seconds())
+            asyncio.ensure_future(get_avatar_url(user_id))
+        except:
+            pass
+
+
+asyncio.ensure_future(_fetch_avatar_loop())
+
+
 class Room(blivedm.BLiveClient):
     def __init__(self, room_id):
         super().__init__(room_id, session=_http_session)
@@ -103,7 +137,7 @@ class Room(blivedm.BLiveClient):
         if gift.coin_type != 'gold':  # 丢人
             return
         self.send_message(Command.ADD_GIFT, {
-            'avatarUrl': await get_avatar_url(gift.uid),
+            'avatarUrl': gift.face,
             'timestamp': gift.timestamp,
             'authorName': gift.uname,
             'giftName': gift.gift_name,