mirror of
https://github.com/xfgryujk/blivechat.git
synced 2025-01-31 06:40:09 +08:00
添加super chat消息
This commit is contained in:
parent
83882c53a0
commit
efcecf2715
91
chat.py
91
chat.py
@ -4,6 +4,7 @@ import asyncio
|
|||||||
import enum
|
import enum
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
from typing import *
|
from typing import *
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
@ -65,19 +66,38 @@ class Room(blivedm.BLiveClient):
|
|||||||
client.write_message(body)
|
client.write_message(body)
|
||||||
|
|
||||||
async def __my_on_get_danmaku(self, command):
|
async def __my_on_get_danmaku(self, command):
|
||||||
data = {
|
self.send_message(Command.ADD_TEXT, {
|
||||||
'avatarUrl': await get_avatar_url(command['info'][2][0]),
|
'avatarUrl': await get_avatar_url(command['info'][2][0]),
|
||||||
'timestamp': command['info'][0][4],
|
'timestamp': command['info'][0][4],
|
||||||
'content': command['info'][1],
|
'authorName': command['info'][2][1],
|
||||||
'authorName': command['info'][2][1]
|
'content': command['info'][1]
|
||||||
}
|
})
|
||||||
self.send_message(Command.ADD_TEXT, data)
|
|
||||||
|
|
||||||
_COMMAND_HANDLERS['DANMU_MSG'] = __my_on_get_danmaku
|
_COMMAND_HANDLERS['DANMU_MSG'] = __my_on_get_danmaku
|
||||||
|
|
||||||
# 新舰长 {'cmd': 'GUARD_BUY', 'data': {'uid': 1822222, 'username': 'MRSKING', 'guard_level': 3,
|
async def __my_on_gift(self, command):
|
||||||
# 'num': 1, 'price': 198000, 'gift_id': 10003, 'gift_name': '舰长', 'start_time': 1558506165,
|
if command['data']['coin_type'] != 'gold': # 丢人
|
||||||
# 'end_time': 1558506165}}
|
return
|
||||||
|
self.send_message(Command.ADD_GIFT, {
|
||||||
|
'avatarUrl': await get_avatar_url(command['data']['uid']),
|
||||||
|
'authorName': command['data']['uname'],
|
||||||
|
'giftName': command['data']['giftName'],
|
||||||
|
'giftNum': command['data']['num'],
|
||||||
|
'totalCoin': command['data']['total_coin']
|
||||||
|
})
|
||||||
|
|
||||||
|
_COMMAND_HANDLERS['SEND_GIFT'] = __my_on_gift
|
||||||
|
|
||||||
|
async def __on_new_member(self, command):
|
||||||
|
# 新舰长 {'cmd': 'GUARD_BUY', 'data': {'uid': 1822222, 'username': 'MRSKING', 'guard_level': 3,
|
||||||
|
# 'num': 1, 'price': 198000, 'gift_id': 10003, 'gift_name': '舰长', 'start_time': 1558506165,
|
||||||
|
# 'end_time': 1558506165}}
|
||||||
|
self.send_message(Command.ADD_VIP, {
|
||||||
|
'avatarUrl': await get_avatar_url(command['data']['uid']),
|
||||||
|
'authorName': command['data']['username'],
|
||||||
|
})
|
||||||
|
|
||||||
|
_COMMAND_HANDLERS['GUARD_BUY'] = __on_new_member
|
||||||
|
|
||||||
|
|
||||||
class RoomManager:
|
class RoomManager:
|
||||||
@ -94,6 +114,9 @@ class RoomManager:
|
|||||||
room.start()
|
room.start()
|
||||||
room.clients.append(client)
|
room.clients.append(client)
|
||||||
|
|
||||||
|
# 测试用
|
||||||
|
# self.__send_test_message(room)
|
||||||
|
|
||||||
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:
|
||||||
return
|
return
|
||||||
@ -104,6 +127,54 @@ class RoomManager:
|
|||||||
room.stop()
|
room.stop()
|
||||||
del self._rooms[room_id]
|
del self._rooms[room_id]
|
||||||
|
|
||||||
|
# 测试用
|
||||||
|
@staticmethod
|
||||||
|
def __send_test_message(room):
|
||||||
|
room.send_message(Command.ADD_TEXT, {
|
||||||
|
'avatarUrl': 'https://i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@24w_24h.webp',
|
||||||
|
'timestamp': time.time(),
|
||||||
|
'authorName': 'xfgryujk',
|
||||||
|
'content': '我能吞下玻璃而不伤身体'
|
||||||
|
})
|
||||||
|
room.send_message(Command.ADD_TEXT, {
|
||||||
|
'avatarUrl': 'https://i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@24w_24h.webp',
|
||||||
|
'timestamp': time.time(),
|
||||||
|
'authorName': 'xfgryujk',
|
||||||
|
'content': "I can eat glass, it doesn't hurt me."
|
||||||
|
})
|
||||||
|
room.send_message(Command.ADD_VIP, {
|
||||||
|
'avatarUrl': 'https://i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@24w_24h.webp',
|
||||||
|
'authorName': 'xfgryujk',
|
||||||
|
})
|
||||||
|
room.send_message(Command.ADD_GIFT, {
|
||||||
|
'avatarUrl': 'https://i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@24w_24h.webp',
|
||||||
|
'authorName': 'xfgryujk',
|
||||||
|
'giftName': '礼花',
|
||||||
|
'giftNum': 1,
|
||||||
|
'totalCoin': 28000
|
||||||
|
})
|
||||||
|
room.send_message(Command.ADD_GIFT, {
|
||||||
|
'avatarUrl': 'https://i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@24w_24h.webp',
|
||||||
|
'authorName': 'xfgryujk',
|
||||||
|
'giftName': '节奏风暴',
|
||||||
|
'giftNum': 1,
|
||||||
|
'totalCoin': 100000
|
||||||
|
})
|
||||||
|
room.send_message(Command.ADD_GIFT, {
|
||||||
|
'avatarUrl': 'https://i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@24w_24h.webp',
|
||||||
|
'authorName': 'xfgryujk',
|
||||||
|
'giftName': '摩天大楼',
|
||||||
|
'giftNum': 1,
|
||||||
|
'totalCoin': 450000
|
||||||
|
})
|
||||||
|
room.send_message(Command.ADD_GIFT, {
|
||||||
|
'avatarUrl': 'https://i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@24w_24h.webp',
|
||||||
|
'authorName': 'xfgryujk',
|
||||||
|
'giftName': '小电视飞船',
|
||||||
|
'giftNum': 1,
|
||||||
|
'totalCoin': 1245000
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
room_manager = RoomManager()
|
room_manager = RoomManager()
|
||||||
|
|
||||||
@ -134,5 +205,5 @@ class ChatHandler(tornado.websocket.WebSocketHandler):
|
|||||||
room_manager.del_client(self.room_id, self)
|
room_manager.del_client(self.room_id, self)
|
||||||
|
|
||||||
# 测试用
|
# 测试用
|
||||||
def check_origin(self, origin):
|
# def check_origin(self, origin):
|
||||||
return True
|
# return True
|
||||||
|
20
frontend/src/components/LegacyPaidMessage.vue
Normal file
20
frontend/src/components/LegacyPaidMessage.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<yt-live-chat-legacy-paid-message-renderer>
|
||||||
|
<div id="author-photo" :style="`background-image: url(${avatarUrl})`"></div>
|
||||||
|
<div id="content">
|
||||||
|
<div id="event-text">{{title}}</div>
|
||||||
|
<div id="detail-text">{{content}}</div>
|
||||||
|
</div>
|
||||||
|
</yt-live-chat-legacy-paid-message-renderer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'LegacyPaidMessage',
|
||||||
|
props: {
|
||||||
|
avatarUrl: String,
|
||||||
|
title: String,
|
||||||
|
content: String
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
56
frontend/src/components/PaidMessage.vue
Normal file
56
frontend/src/components/PaidMessage.vue
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<yt-live-chat-paid-message-renderer>
|
||||||
|
<div id="header" :style="'background-color: ' + headerColor">
|
||||||
|
<div id="author-photo" :style="`background-image: url(${avatarUrl})`"></div>
|
||||||
|
<div id="header-content">
|
||||||
|
<div id="author-name">{{authorName}}</div>
|
||||||
|
<div id="purchase-amount">{{title}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="content" :style="'background-color: ' + contentColor">
|
||||||
|
<div id="message" dir="auto">{{content}}</div>
|
||||||
|
</div>
|
||||||
|
</yt-live-chat-paid-message-renderer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let LEVEL_TO_HEADER_COLOR = [
|
||||||
|
'rgba(0,184,212,1)', // $2浅蓝
|
||||||
|
'rgba(255,176,0,1)', // $10黄
|
||||||
|
'rgba(245,91,0,1)', // $20橙
|
||||||
|
'rgba(208,0,0,1)' // $100红
|
||||||
|
]
|
||||||
|
let LEVEL_TO_CONTENT_COLOR = [
|
||||||
|
'rgba(0,229,255,1)', // $2浅蓝
|
||||||
|
'rgba(236,182,29,1)', // $10黄
|
||||||
|
'rgba(255,127,0,1)', // $20橙
|
||||||
|
'rgba(230,33,23,1)' // $100红
|
||||||
|
]
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'PaidMessage',
|
||||||
|
props: {
|
||||||
|
level: Number, // 高亮等级,决定颜色
|
||||||
|
avatarUrl: String,
|
||||||
|
authorName: String,
|
||||||
|
title: String,
|
||||||
|
content: String
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
headerColor() {
|
||||||
|
if (this.level < 0)
|
||||||
|
return LEVEL_TO_HEADER_COLOR[0]
|
||||||
|
if (this.level >= LEVEL_TO_HEADER_COLOR.length)
|
||||||
|
return LEVEL_TO_HEADER_COLOR[LEVEL_TO_HEADER_COLOR.length - 1]
|
||||||
|
return LEVEL_TO_HEADER_COLOR[this.level]
|
||||||
|
},
|
||||||
|
contentColor() {
|
||||||
|
if (this.level < 0)
|
||||||
|
return LEVEL_TO_CONTENT_COLOR[0]
|
||||||
|
if (this.level >= LEVEL_TO_CONTENT_COLOR.length)
|
||||||
|
return LEVEL_TO_CONTENT_COLOR[LEVEL_TO_CONTENT_COLOR.length - 1]
|
||||||
|
return LEVEL_TO_CONTENT_COLOR[this.level]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -16,9 +16,16 @@
|
|||||||
</yt-live-chat-ticker-renderer> -->
|
</yt-live-chat-ticker-renderer> -->
|
||||||
<yt-live-chat-item-list-renderer>
|
<yt-live-chat-item-list-renderer>
|
||||||
<template v-for="message in messages">
|
<template v-for="message in messages">
|
||||||
<text-message :key="message.id"
|
<text-message :key="message.id" v-if="message.type == 0"
|
||||||
:avatarUrl="message.avatarUrl" :time="message.time" :authorName="message.authorName" :content="message.content"
|
:avatarUrl="message.avatarUrl" :time="message.time" :authorName="message.authorName" :content="message.content"
|
||||||
></text-message>
|
></text-message>
|
||||||
|
<legacy-paid-message :key="message.id" v-else-if="message.type == 1"
|
||||||
|
:avatarUrl="message.avatarUrl" :title="message.title" :content="message.content"
|
||||||
|
></legacy-paid-message>
|
||||||
|
<paid-message :key="message.id" v-else
|
||||||
|
:level="message.level" :avatarUrl="message.avatarUrl" :authorName="message.authorName"
|
||||||
|
:title="message.title" :content="message.content"
|
||||||
|
></paid-message>
|
||||||
</template>
|
</template>
|
||||||
</yt-live-chat-item-list-renderer>
|
</yt-live-chat-item-list-renderer>
|
||||||
</yt-live-chat-renderer>
|
</yt-live-chat-renderer>
|
||||||
@ -26,11 +33,15 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TextMessage from './TextMessage.vue'
|
import TextMessage from './TextMessage.vue'
|
||||||
|
import LegacyPaidMessage from './LegacyPaidMessage.vue'
|
||||||
|
import PaidMessage from './PaidMessage.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Room',
|
name: 'Room',
|
||||||
components: {
|
components: {
|
||||||
TextMessage
|
TextMessage,
|
||||||
|
LegacyPaidMessage,
|
||||||
|
PaidMessage
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@ -40,9 +51,9 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
// this.websocket = new WebSocket(`ws://${window.location.host}/chat`)
|
this.websocket = new WebSocket(`ws://${window.location.host}/chat`)
|
||||||
// 测试用
|
// 测试用
|
||||||
this.websocket = new WebSocket('ws://localhost/chat')
|
// this.websocket = new WebSocket('ws://localhost/chat')
|
||||||
this.websocket.onopen = () => this.websocket.send(JSON.stringify({
|
this.websocket.onopen = () => this.websocket.send(JSON.stringify({
|
||||||
cmd: 0, // JOIN_ROOM
|
cmd: 0, // JOIN_ROOM
|
||||||
data: {
|
data: {
|
||||||
@ -51,23 +62,57 @@ export default {
|
|||||||
}))
|
}))
|
||||||
this.websocket.onmessage = (event) => {
|
this.websocket.onmessage = (event) => {
|
||||||
let body = JSON.parse(event.data)
|
let body = JSON.parse(event.data)
|
||||||
let time = new Date(body.data.timestamp * 1000)
|
let message = null
|
||||||
let message = {
|
let time, price, level
|
||||||
id: this.nextId++,
|
|
||||||
time: `${time.getHours()}:${time.getMinutes()}`,
|
|
||||||
...body.data
|
|
||||||
}
|
|
||||||
switch(body.cmd) {
|
switch(body.cmd) {
|
||||||
case 1: // ADD_TEXT
|
case 1: // ADD_TEXT
|
||||||
this.messages.push(message)
|
time = new Date(body.data.timestamp * 1000)
|
||||||
if (this.messages.length > 50)
|
message = {
|
||||||
this.messages.shift()
|
id: this.nextId++,
|
||||||
|
type: 0, // TextMessage
|
||||||
|
avatarUrl: body.data.avatarUrl,
|
||||||
|
time: `${time.getHours()}:${time.getMinutes()}`,
|
||||||
|
authorName: body.data.authorName,
|
||||||
|
content: body.data.content
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 2: // ADD_GIFT
|
case 2: // ADD_GIFT
|
||||||
|
price = body.data.totalCoin / 1000
|
||||||
|
if (price < 9.9) // 丢人
|
||||||
|
break
|
||||||
|
else if (price < 100) // B坷垃~打call
|
||||||
|
level = 0
|
||||||
|
else if (price < 300) // 节奏风暴、天空之翼
|
||||||
|
level = 1
|
||||||
|
else if (price < 500) // 摩天大楼
|
||||||
|
level = 2
|
||||||
|
else // 小电视飞船
|
||||||
|
level = 3
|
||||||
|
message = {
|
||||||
|
id: this.nextId++,
|
||||||
|
type: 2, // PaidMessage
|
||||||
|
level: level,
|
||||||
|
avatarUrl: body.data.avatarUrl,
|
||||||
|
authorName: body.data.authorName,
|
||||||
|
title: `CNY${price}`,
|
||||||
|
content: `Sent ${body.data.giftName}x${body.data.giftNum}`
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 3: // ADD_VIP
|
case 3: // ADD_VIP
|
||||||
|
message = {
|
||||||
|
id: this.nextId++,
|
||||||
|
type: 1, // LegacyPaidMessage
|
||||||
|
avatarUrl: body.data.avatarUrl,
|
||||||
|
title: `NEW MEMBER!`,
|
||||||
|
content: `Welcome ${body.data.authorName}`
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (message) {
|
||||||
|
this.messages.push(message)
|
||||||
|
if (this.messages.length > 50)
|
||||||
|
this.messages.shift()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
Loading…
Reference in New Issue
Block a user