diff --git a/chat.py b/chat.py index e6dafba..ec09019 100644 --- a/chat.py +++ b/chat.py @@ -4,6 +4,7 @@ import asyncio import enum import json import logging +import time from typing import * import aiohttp @@ -65,19 +66,38 @@ class Room(blivedm.BLiveClient): client.write_message(body) async def __my_on_get_danmaku(self, command): - data = { + self.send_message(Command.ADD_TEXT, { 'avatarUrl': await get_avatar_url(command['info'][2][0]), 'timestamp': command['info'][0][4], - 'content': command['info'][1], - 'authorName': command['info'][2][1] - } - self.send_message(Command.ADD_TEXT, data) + 'authorName': command['info'][2][1], + 'content': command['info'][1] + }) _COMMAND_HANDLERS['DANMU_MSG'] = __my_on_get_danmaku - # 新舰长 {'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}} + async def __my_on_gift(self, command): + if command['data']['coin_type'] != 'gold': # 丢人 + 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: @@ -94,6 +114,9 @@ class RoomManager: room.start() room.clients.append(client) + # 测试用 + # self.__send_test_message(room) + def del_client(self, room_id, client: 'ChatHandler'): if room_id not in self._rooms: return @@ -104,6 +127,54 @@ class RoomManager: room.stop() 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() @@ -134,5 +205,5 @@ class ChatHandler(tornado.websocket.WebSocketHandler): room_manager.del_client(self.room_id, self) # 测试用 - def check_origin(self, origin): - return True + # def check_origin(self, origin): + # return True diff --git a/frontend/src/components/LegacyPaidMessage.vue b/frontend/src/components/LegacyPaidMessage.vue new file mode 100644 index 0000000..6cee9bf --- /dev/null +++ b/frontend/src/components/LegacyPaidMessage.vue @@ -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> diff --git a/frontend/src/components/PaidMessage.vue b/frontend/src/components/PaidMessage.vue new file mode 100644 index 0000000..2314f1b --- /dev/null +++ b/frontend/src/components/PaidMessage.vue @@ -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> diff --git a/frontend/src/components/Room.vue b/frontend/src/components/Room.vue index 1ebefbf..5844db9 100644 --- a/frontend/src/components/Room.vue +++ b/frontend/src/components/Room.vue @@ -16,9 +16,16 @@ </yt-live-chat-ticker-renderer> --> <yt-live-chat-item-list-renderer> <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" ></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> </yt-live-chat-item-list-renderer> </yt-live-chat-renderer> @@ -26,11 +33,15 @@ <script> import TextMessage from './TextMessage.vue' +import LegacyPaidMessage from './LegacyPaidMessage.vue' +import PaidMessage from './PaidMessage.vue' export default { name: 'Room', components: { - TextMessage + TextMessage, + LegacyPaidMessage, + PaidMessage }, data() { return { @@ -40,9 +51,9 @@ export default { } }, 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({ cmd: 0, // JOIN_ROOM data: { @@ -51,23 +62,57 @@ export default { })) this.websocket.onmessage = (event) => { let body = JSON.parse(event.data) - let time = new Date(body.data.timestamp * 1000) - let message = { - id: this.nextId++, - time: `${time.getHours()}:${time.getMinutes()}`, - ...body.data - } + let message = null + let time, price, level switch(body.cmd) { case 1: // ADD_TEXT - this.messages.push(message) - if (this.messages.length > 50) - this.messages.shift() + time = new Date(body.data.timestamp * 1000) + message = { + id: this.nextId++, + type: 0, // TextMessage + avatarUrl: body.data.avatarUrl, + time: `${time.getHours()}:${time.getMinutes()}`, + authorName: body.data.authorName, + content: body.data.content + } break; 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; case 3: // ADD_VIP + message = { + id: this.nextId++, + type: 1, // LegacyPaidMessage + avatarUrl: body.data.avatarUrl, + title: `NEW MEMBER!`, + content: `Welcome ${body.data.authorName}` + } break; } + if (message) { + this.messages.push(message) + if (this.messages.length > 50) + this.messages.shift() + } } }, beforeDestroy() {