mirror of
https://github.com/xfgryujk/blivechat.git
synced 2025-01-15 23:00:32 +08:00
websocket消息优化
This commit is contained in:
parent
49cdc56cff
commit
1c5df01fbc
156
api/chat.py
156
api/chat.py
@ -4,6 +4,7 @@ import asyncio
|
|||||||
import enum
|
import enum
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import random
|
||||||
import time
|
import time
|
||||||
from typing import *
|
from typing import *
|
||||||
|
|
||||||
@ -58,7 +59,7 @@ class Room(blivedm.BLiveClient):
|
|||||||
def __parse_gift(self, command):
|
def __parse_gift(self, command):
|
||||||
data = command['data']
|
data = command['data']
|
||||||
return self._on_receive_gift(blivedm.GiftMessage(
|
return self._on_receive_gift(blivedm.GiftMessage(
|
||||||
data['giftName'], data['num'], data['uname'], data['face'], None,
|
None, None, data['uname'], data['face'], None,
|
||||||
data['uid'], data['timestamp'], None, None,
|
data['uid'], data['timestamp'], None, None,
|
||||||
None, None, None, data['coin_type'], data['total_coin']
|
None, None, None, data['coin_type'], data['total_coin']
|
||||||
))
|
))
|
||||||
@ -121,19 +122,31 @@ class Room(blivedm.BLiveClient):
|
|||||||
author_type = 1 # 舰队
|
author_type = 1 # 舰队
|
||||||
else:
|
else:
|
||||||
author_type = 0
|
author_type = 0
|
||||||
self.send_message(Command.ADD_TEXT, {
|
# 为了节省带宽用list而不是dict
|
||||||
'avatarUrl': await models.avatar.get_avatar_url(danmaku.uid),
|
self.send_message(Command.ADD_TEXT, [
|
||||||
'timestamp': danmaku.timestamp,
|
# 0: avatarUrl
|
||||||
'authorName': danmaku.uname,
|
await models.avatar.get_avatar_url(danmaku.uid),
|
||||||
'authorType': author_type,
|
# 1: timestamp
|
||||||
'content': danmaku.msg,
|
danmaku.timestamp,
|
||||||
'privilegeType': danmaku.privilege_type,
|
# 2: authorName
|
||||||
'isGiftDanmaku': bool(danmaku.msg_type),
|
danmaku.uname,
|
||||||
'authorLevel': danmaku.user_level,
|
# 3: authorType
|
||||||
'isNewbie': danmaku.urank < 10000,
|
author_type,
|
||||||
'isMobileVerified': bool(danmaku.mobile_verify),
|
# 4: content
|
||||||
'medalLevel': 0 if danmaku.room_id != self.room_id else danmaku.medal_level
|
danmaku.msg,
|
||||||
})
|
# 5: privilegeType
|
||||||
|
danmaku.privilege_type,
|
||||||
|
# 6: isGiftDanmaku
|
||||||
|
1 if danmaku.msg_type else 0,
|
||||||
|
# 7: authorLevel
|
||||||
|
danmaku.user_level,
|
||||||
|
# 8: isNewbie
|
||||||
|
1 if danmaku.urank < 10000 else 0,
|
||||||
|
# 9: isMobileVerified
|
||||||
|
1 if danmaku.mobile_verify else 0,
|
||||||
|
# 10: medalLevel
|
||||||
|
0 if danmaku.room_id != self.room_id else danmaku.medal_level
|
||||||
|
])
|
||||||
|
|
||||||
async def _on_receive_gift(self, gift: blivedm.GiftMessage):
|
async def _on_receive_gift(self, gift: blivedm.GiftMessage):
|
||||||
avatar_url = models.avatar.process_avatar_url(gift.face)
|
avatar_url = models.avatar.process_avatar_url(gift.face)
|
||||||
@ -144,8 +157,6 @@ class Room(blivedm.BLiveClient):
|
|||||||
'avatarUrl': avatar_url,
|
'avatarUrl': avatar_url,
|
||||||
'timestamp': gift.timestamp,
|
'timestamp': gift.timestamp,
|
||||||
'authorName': gift.uname,
|
'authorName': gift.uname,
|
||||||
'giftName': gift.gift_name,
|
|
||||||
'giftNum': gift.num,
|
|
||||||
'totalCoin': gift.total_coin
|
'totalCoin': gift.total_coin
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -154,7 +165,7 @@ class Room(blivedm.BLiveClient):
|
|||||||
|
|
||||||
async def __on_buy_guard(self, message: blivedm.GuardBuyMessage):
|
async def __on_buy_guard(self, message: blivedm.GuardBuyMessage):
|
||||||
self.send_message(Command.ADD_MEMBER, {
|
self.send_message(Command.ADD_MEMBER, {
|
||||||
'avatarUrl': await models.avatar.get_avatar_url(message.uid),
|
'avatarUrl': await models.avatar.get_avatar_url(message.uid),
|
||||||
'timestamp': message.start_time,
|
'timestamp': message.start_time,
|
||||||
'authorName': message.username
|
'authorName': message.username
|
||||||
})
|
})
|
||||||
@ -173,7 +184,7 @@ class Room(blivedm.BLiveClient):
|
|||||||
|
|
||||||
async def _on_super_chat_delete(self, message: blivedm.SuperChatDeleteMessage):
|
async def _on_super_chat_delete(self, message: blivedm.SuperChatDeleteMessage):
|
||||||
self.send_message(Command.ADD_SUPER_CHAT, {
|
self.send_message(Command.ADD_SUPER_CHAT, {
|
||||||
'ids': message.ids
|
'ids': message.ids
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@ -191,7 +202,7 @@ class RoomManager:
|
|||||||
logger.info('%d clients in room %s', len(room.clients), room_id)
|
logger.info('%d clients in room %s', len(room.clients), room_id)
|
||||||
|
|
||||||
if client.application.settings['debug']:
|
if client.application.settings['debug']:
|
||||||
client.send_test_message()
|
await client.send_test_message()
|
||||||
|
|
||||||
def del_client(self, room_id, client: 'ChatHandler'):
|
def del_client(self, room_id, client: 'ChatHandler'):
|
||||||
if room_id not in self._rooms:
|
if room_id not in self._rooms:
|
||||||
@ -230,29 +241,49 @@ class RoomManager:
|
|||||||
class ChatHandler(tornado.websocket.WebSocketHandler):
|
class ChatHandler(tornado.websocket.WebSocketHandler):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
self._close_on_timeout_future = None
|
||||||
self.room_id = None
|
self.room_id = None
|
||||||
|
|
||||||
def open(self):
|
def open(self):
|
||||||
logger.info('Websocket connected %s', self.request.remote_ip)
|
logger.info('Websocket connected %s', self.request.remote_ip)
|
||||||
|
self._close_on_timeout_future = asyncio.ensure_future(self._close_on_timeout())
|
||||||
|
|
||||||
|
async def _close_on_timeout(self):
|
||||||
|
try:
|
||||||
|
# 超过一定时间还没加入房间则断开
|
||||||
|
await asyncio.sleep(10)
|
||||||
|
logger.warning('Client %s joining room timed out', self.request.remote_ip)
|
||||||
|
self.close()
|
||||||
|
except (asyncio.CancelledError, tornado.websocket.WebSocketClosedError):
|
||||||
|
pass
|
||||||
|
|
||||||
def on_message(self, message):
|
def on_message(self, message):
|
||||||
body = json.loads(message)
|
try:
|
||||||
cmd = body['cmd']
|
body = json.loads(message)
|
||||||
if cmd == Command.HEARTBEAT:
|
cmd = body['cmd']
|
||||||
pass
|
if cmd == Command.HEARTBEAT:
|
||||||
elif cmd == Command.JOIN_ROOM:
|
|
||||||
if self.room_id is not None:
|
|
||||||
return
|
return
|
||||||
self.room_id = int(body['data']['roomId'])
|
elif cmd == Command.JOIN_ROOM:
|
||||||
logger.info('Client %s is joining room %d', self.request.remote_ip, self.room_id)
|
if self.has_joined_room:
|
||||||
asyncio.ensure_future(room_manager.add_client(self.room_id, self))
|
return
|
||||||
else:
|
self.room_id = int(body['data']['roomId'])
|
||||||
logger.warning('Unknown cmd: %s body: %s', cmd, body)
|
logger.info('Client %s is joining room %d', self.request.remote_ip, self.room_id)
|
||||||
|
|
||||||
|
asyncio.ensure_future(room_manager.add_client(self.room_id, self))
|
||||||
|
self._close_on_timeout_future.cancel()
|
||||||
|
self._close_on_timeout_future = None
|
||||||
|
else:
|
||||||
|
logger.warning('Unknown cmd, client: %s, cmd: %d, body: %s', self.request.remote_ip, cmd, body)
|
||||||
|
except:
|
||||||
|
logger.exception('on_message error, client: %s, message: %s', self.request.remote_ip, message)
|
||||||
|
|
||||||
def on_close(self):
|
def on_close(self):
|
||||||
logger.info('Websocket disconnected %s room: %s', self.request.remote_ip, str(self.room_id))
|
logger.info('Websocket disconnected %s room: %s', self.request.remote_ip, str(self.room_id))
|
||||||
if self.room_id is not None:
|
if self.has_joined_room:
|
||||||
room_manager.del_client(self.room_id, self)
|
room_manager.del_client(self.room_id, self)
|
||||||
|
if self._close_on_timeout_future is not None:
|
||||||
|
self._close_on_timeout_future.cancel()
|
||||||
|
self._close_on_timeout_future = None
|
||||||
|
|
||||||
# 跨域测试用
|
# 跨域测试用
|
||||||
def check_origin(self, origin):
|
def check_origin(self, origin):
|
||||||
@ -261,52 +292,67 @@ class ChatHandler(tornado.websocket.WebSocketHandler):
|
|||||||
return super().check_origin(origin)
|
return super().check_origin(origin)
|
||||||
|
|
||||||
# 测试用
|
# 测试用
|
||||||
def send_test_message(self):
|
async def send_test_message(self):
|
||||||
base_data = {
|
base_data = {
|
||||||
'avatarUrl': '//i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@48w_48h',
|
'avatarUrl': await models.avatar.get_avatar_url(300474),
|
||||||
'timestamp': time.time(),
|
'timestamp': time.time(),
|
||||||
'authorName': 'xfgryujk',
|
'authorName': 'xfgryujk',
|
||||||
}
|
}
|
||||||
text_data = {
|
text_data = [
|
||||||
**base_data,
|
# 0: avatarUrl
|
||||||
'authorType': 0,
|
base_data['avatarUrl'],
|
||||||
'content': '我能吞下玻璃而不伤身体',
|
# 1: timestamp
|
||||||
'privilegeType': 0,
|
base_data['timestamp'],
|
||||||
'isGiftDanmaku': False,
|
# 2: authorName
|
||||||
'authorLevel': 20,
|
base_data['authorName'],
|
||||||
'isNewbie': False,
|
# 3: authorType
|
||||||
'isMobileVerified': True
|
0,
|
||||||
}
|
# 4: content
|
||||||
member_data = base_data
|
'我能吞下玻璃而不伤身体',
|
||||||
|
# 5: privilegeType
|
||||||
|
0,
|
||||||
|
# 6: isGiftDanmaku
|
||||||
|
0,
|
||||||
|
# 7: authorLevel
|
||||||
|
20,
|
||||||
|
# 8: isNewbie
|
||||||
|
0,
|
||||||
|
# 9: isMobileVerified
|
||||||
|
1,
|
||||||
|
# 10: medalLevel
|
||||||
|
0
|
||||||
|
]
|
||||||
|
member_data = base_data.copy()
|
||||||
gift_data = {
|
gift_data = {
|
||||||
**base_data,
|
**base_data,
|
||||||
'giftName': '摩天大楼',
|
|
||||||
'giftNum': 1,
|
|
||||||
'totalCoin': 450000
|
'totalCoin': 450000
|
||||||
}
|
}
|
||||||
sc_data = {
|
sc_data = {
|
||||||
**base_data,
|
**base_data,
|
||||||
'price': 30,
|
'price': 30,
|
||||||
'content': 'The quick brown fox jumps over the lazy dog',
|
'content': 'The quick brown fox jumps over the lazy dog',
|
||||||
'id': 1
|
'id': random.randint(1, 65535)
|
||||||
}
|
}
|
||||||
self.send_message(Command.ADD_TEXT, text_data)
|
self.send_message(Command.ADD_TEXT, text_data)
|
||||||
text_data['authorName'] = '主播'
|
text_data[2] = '主播'
|
||||||
text_data['authorType'] = 3
|
text_data[3] = 3
|
||||||
text_data['content'] = "I can eat glass, it doesn't hurt me."
|
text_data[4] = "I can eat glass, it doesn't hurt me."
|
||||||
self.send_message(Command.ADD_TEXT, text_data)
|
self.send_message(Command.ADD_TEXT, text_data)
|
||||||
self.send_message(Command.ADD_MEMBER, member_data)
|
self.send_message(Command.ADD_MEMBER, member_data)
|
||||||
self.send_message(Command.ADD_SUPER_CHAT, sc_data)
|
self.send_message(Command.ADD_SUPER_CHAT, sc_data)
|
||||||
sc_data['price'] = 100
|
sc_data['price'] = 100
|
||||||
sc_data['content'] = '敏捷的棕色狐狸跳过了懒狗'
|
sc_data['content'] = '敏捷的棕色狐狸跳过了懒狗'
|
||||||
sc_data['id'] = 2
|
sc_data['id'] = random.randint(1, 65535)
|
||||||
self.send_message(Command.ADD_SUPER_CHAT, sc_data)
|
self.send_message(Command.ADD_SUPER_CHAT, sc_data)
|
||||||
# self.send_message(Command.DEL_SUPER_CHAT, {'ids': [1, 2]})
|
# self.send_message(Command.DEL_SUPER_CHAT, {'ids': [sc_data['id']]})
|
||||||
self.send_message(Command.ADD_GIFT, gift_data)
|
self.send_message(Command.ADD_GIFT, gift_data)
|
||||||
gift_data['giftName'] = '小电视飞船'
|
|
||||||
gift_data['totalCoin'] = 1245000
|
gift_data['totalCoin'] = 1245000
|
||||||
self.send_message(Command.ADD_GIFT, gift_data)
|
self.send_message(Command.ADD_GIFT, gift_data)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_joined_room(self):
|
||||||
|
return self.room_id is not None
|
||||||
|
|
||||||
def send_message(self, cmd, data):
|
def send_message(self, cmd, data):
|
||||||
body = json.dumps({'cmd': cmd, 'data': data})
|
body = json.dumps({'cmd': cmd, 'data': data})
|
||||||
try:
|
try:
|
||||||
|
@ -112,9 +112,21 @@ export default {
|
|||||||
onWsMessage(event) {
|
onWsMessage(event) {
|
||||||
let {cmd, data} = JSON.parse(event.data)
|
let {cmd, data} = JSON.parse(event.data)
|
||||||
let message = null
|
let message = null
|
||||||
let time = data.timestamp ? new Date(data.timestamp * 1000) : new Date()
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case COMMAND_ADD_TEXT:
|
case COMMAND_ADD_TEXT:
|
||||||
|
data = {
|
||||||
|
avatarUrl: data[0],
|
||||||
|
timestamp: data[1],
|
||||||
|
authorName: data[2],
|
||||||
|
authorType: data[3],
|
||||||
|
content: data[4],
|
||||||
|
privilegeType: data[5],
|
||||||
|
isGiftDanmaku: !!data[6],
|
||||||
|
authorLevel: data[7],
|
||||||
|
isNewbie: !!data[8],
|
||||||
|
isMobileVerified: !!data[9],
|
||||||
|
medalLevel: data[10]
|
||||||
|
}
|
||||||
if (!this.config.showDanmaku || !this.filterTextMessage(data) || this.mergeSimilarText(data.content)) {
|
if (!this.config.showDanmaku || !this.filterTextMessage(data) || this.mergeSimilarText(data.content)) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -122,7 +134,7 @@ export default {
|
|||||||
id: `text_${this.nextId++}`,
|
id: `text_${this.nextId++}`,
|
||||||
type: constants.MESSAGE_TYPE_TEXT,
|
type: constants.MESSAGE_TYPE_TEXT,
|
||||||
avatarUrl: data.avatarUrl,
|
avatarUrl: data.avatarUrl,
|
||||||
time: time,
|
time: new Date(data.timestamp * 1000),
|
||||||
authorName: data.authorName,
|
authorName: data.authorName,
|
||||||
authorType: data.authorType,
|
authorType: data.authorType,
|
||||||
content: data.content,
|
content: data.content,
|
||||||
@ -147,7 +159,7 @@ export default {
|
|||||||
avatarUrl: data.avatarUrl,
|
avatarUrl: data.avatarUrl,
|
||||||
authorName: data.authorName,
|
authorName: data.authorName,
|
||||||
price: price,
|
price: price,
|
||||||
time: time,
|
time: new Date(data.timestamp * 1000),
|
||||||
content: '' // 有了SC,礼物不需要内容了
|
content: '' // 有了SC,礼物不需要内容了
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
@ -160,7 +172,7 @@ export default {
|
|||||||
id: `member_${this.nextId++}`,
|
id: `member_${this.nextId++}`,
|
||||||
type: constants.MESSAGE_TYPE_MEMBER,
|
type: constants.MESSAGE_TYPE_MEMBER,
|
||||||
avatarUrl: data.avatarUrl,
|
avatarUrl: data.avatarUrl,
|
||||||
time: time,
|
time: new Date(data.timestamp * 1000),
|
||||||
authorName: data.authorName,
|
authorName: data.authorName,
|
||||||
title: 'NEW MEMBER!',
|
title: 'NEW MEMBER!',
|
||||||
content: `Welcome ${data.authorName}!`
|
content: `Welcome ${data.authorName}!`
|
||||||
@ -179,7 +191,7 @@ export default {
|
|||||||
avatarUrl: data.avatarUrl,
|
avatarUrl: data.avatarUrl,
|
||||||
authorName: data.authorName,
|
authorName: data.authorName,
|
||||||
price: data.price,
|
price: data.price,
|
||||||
time: time,
|
time: new Date(data.timestamp * 1000),
|
||||||
content: data.content.trim()
|
content: data.content.trim()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
Loading…
Reference in New Issue
Block a user