完成配置功能

This commit is contained in:
John Smith 2019-06-12 19:20:17 +08:00
parent 920e6fff96
commit 1cbaed4127
4 changed files with 138 additions and 33 deletions

View File

@ -5,6 +5,9 @@
<span id="timestamp">{{time}}</span> <span id="timestamp">{{time}}</span>
<span id="author-name" :type="authorTypeText">{{authorName}}</span> <span id="author-name" :type="authorTypeText">{{authorName}}</span>
<span id="message">{{content}}</span> <span id="message">{{content}}</span>
<el-badge :value="repeated" :max="99" v-show="repeated > 1"
:style="`--repeated-mark-color: ${repeatedMarkColor}`"
></el-badge>
</div> </div>
</yt-live-chat-text-message-renderer> </yt-live-chat-text-message-renderer>
</template> </template>
@ -16,6 +19,8 @@ const AUTHOR_TYPE_TO_TEXT = [
'moderator', // 'moderator', //
'owner' // 'owner' //
] ]
const REPEATED_MARK_COLOR_START = [0x21, 0x96, 0xF3]
const REPEATED_MARK_COLOR_END = [0xFF, 0x57, 0x22]
export default { export default {
name: 'TextMessage', name: 'TextMessage',
@ -24,12 +29,40 @@ export default {
time: String, time: String,
authorName: String, authorName: String,
authorType: Number, authorType: Number,
content: String content: String,
repeated: Number
}, },
computed: { computed: {
authorTypeText() { authorTypeText() {
return AUTHOR_TYPE_TO_TEXT[this.authorType] return AUTHOR_TYPE_TO_TEXT[this.authorType]
},
repeatedMarkColor() {
let color
if (this.repeated <= 2) {
color = REPEATED_MARK_COLOR_START
} else if (this.repeated >= 10) {
color = REPEATED_MARK_COLOR_END
} else {
color = [0, 0, 0]
let t = (this.repeated - 2) / (10 - 2)
for (let i = 0; i < 3; i++) {
color[i] = REPEATED_MARK_COLOR_START[i] + (REPEATED_MARK_COLOR_END[i] - REPEATED_MARK_COLOR_START[i]) * t
}
}
return `rgb(${color.join(', ')})`
} }
} }
} }
</script> </script>
<style>
yt-live-chat-text-message-renderer #content .el-badge {
margin-left: 0.5em;
}
yt-live-chat-text-message-renderer #content .el-badge * {
text-shadow: none !important;
font-family: sans-serif !important;
background-color: var(--repeated-mark-color) !important;
}
</style>

View File

@ -18,7 +18,7 @@
<template v-for="message in messages"> <template v-for="message in messages">
<text-message :key="message.id" v-if="message.type == 0" <text-message :key="message.id" v-if="message.type == 0"
:avatarUrl="message.avatarUrl" :time="message.time" :authorName="message.authorName" :avatarUrl="message.avatarUrl" :time="message.time" :authorName="message.authorName"
:authorType="message.authorType" :content="message.content" :authorType="message.authorType" :content="message.content" :repeated="message.repeated"
></text-message> ></text-message>
<legacy-paid-message :key="message.id" v-else-if="message.type == 1" <legacy-paid-message :key="message.id" v-else-if="message.type == 1"
:avatarUrl="message.avatarUrl" :title="message.title" :content="message.content" :avatarUrl="message.avatarUrl" :title="message.title" :content="message.content"
@ -51,11 +51,14 @@ export default {
PaidMessage PaidMessage
}, },
data() { data() {
let cfg = {...config.DEFAULT_CONFIG}
cfg.blockKeywords = cfg.blockKeywords.split('\n').filter(val => val)
cfg.blockUsers = cfg.blockUsers.split('\n').filter(val => val)
let styleElement = document.createElement('style') let styleElement = document.createElement('style')
styleElement.innerText = config.DEFAULT_CONFIG.css styleElement.innerText = cfg.css
document.head.appendChild(styleElement) document.head.appendChild(styleElement)
return { return {
config: config.DEFAULT_CONFIG, config: cfg,
styleElement, styleElement,
websocket: null, websocket: null,
messages: [], messages: [],
@ -66,18 +69,46 @@ export default {
// 使localhost:80 // 使localhost:80
const url = process.env.NODE_ENV === 'development' ? 'ws://localhost/chat' : `ws://${window.location.host}/chat` const url = process.env.NODE_ENV === 'development' ? 'ws://localhost/chat' : `ws://${window.location.host}/chat`
this.websocket = new WebSocket(url) this.websocket = new WebSocket(url)
this.websocket.onopen = () => this.websocket.send(JSON.stringify({ this.websocket.onopen = this.onWsOpen.bind(this)
cmd: COMMAND_JOIN_ROOM, this.websocket.onmessage = this.onWsMessage.bind(this)
data: {
roomId: parseInt(this.$route.params.roomId) if (this.$route.query.config_id) {
try {
let cfg = await config.getRemoteConfig(this.$route.query.config_id)
cfg.blockKeywords = cfg.blockKeywords.split('\n').filter(val => val)
cfg.blockUsers = cfg.blockUsers.split('\n').filter(val => val)
this.styleElement.innerText = cfg.css
this.config = cfg
} catch (e) {
this.$message.error('获取配置失败:' + e)
} }
})) }
this.websocket.onmessage = (event) => { },
beforeDestroy() {
document.head.removeChild(this.styleElement)
this.websocket.close()
},
updated() {
window.scrollTo(0, document.body.scrollHeight)
},
methods: {
onWsOpen() {
this.websocket.send(JSON.stringify({
cmd: COMMAND_JOIN_ROOM,
data: {
roomId: parseInt(this.$route.params.roomId)
}
}))
},
onWsMessage(event) {
let body = JSON.parse(event.data) let body = JSON.parse(event.data)
let message = null let message = null
let time, price let time, price
switch(body.cmd) { switch(body.cmd) {
case COMMAND_ADD_TEXT: case COMMAND_ADD_TEXT:
if (!this.filterTextMessage(body.data) || this.mergeSimilar(body.data.content)) {
break
}
time = new Date(body.data.timestamp * 1000) time = new Date(body.data.timestamp * 1000)
message = { message = {
id: this.nextId++, id: this.nextId++,
@ -86,7 +117,8 @@ export default {
time: `${time.getHours()}:${time.getMinutes()}`, time: `${time.getHours()}:${time.getMinutes()}`,
authorName: body.data.authorName, authorName: body.data.authorName,
authorType: body.data.authorType, authorType: body.data.authorType,
content: body.data.content content: body.data.content,
repeated: 1
} }
break break
case COMMAND_ADD_GIFT: case COMMAND_ADD_GIFT:
@ -114,26 +146,49 @@ export default {
} }
if (message) { if (message) {
this.messages.push(message) this.messages.push(message)
if (this.messages.length > 50) if (this.messages.length > 50) {
this.messages.shift() this.messages.splice(0, this.messages.length - 50)
}
} }
} },
filterTextMessage(data) {
if (this.$route.query.config_id) { if (this.config.blockGiftDanmaku && data.isGiftDanmaku) {
try { return false
this.config = await config.getRemoteConfig(this.$route.query.config_id) } else if (this.config.blockLevel > 0 && data.authorLevel < this.config.blockLevel) {
this.styleElement.innerText = this.config.css return false
} catch (e) { } else if (this.config.blockNewbie && data.isNewbie) {
this.$message.error('获取配置失败:' + e) return false
} else if (this.config.blockNotMobileVerified && !data.isMobileVerified) {
return false
} }
for (let keyword of this.config.blockKeywords) {
if (data.content.indexOf(keyword) !== -1) {
return false
}
}
for (let user of this.config.blockUsers) {
if (data.authorName === user) {
return false
}
}
return true
},
mergeSimilar(content) {
if (!this.config.mergeSimilarDanmaku) {
return false
}
for (let i = this.messages.length - 1; i >= 0 && i >= this.messages.length - 5; i--) {
let message = this.messages[i]
if (
(message.content.indexOf(content) !== -1 || content.indexOf(message.content) !== -1) //
&& Math.abs(message.content.length - content.length) < Math.min(message.content.length, content.length) //
) {
message.repeated++
return true
}
}
return false
} }
},
beforeDestroy() {
document.head.removeChild(this.styleElement)
this.websocket.close()
},
updated() {
window.scrollTo(0, document.body.scrollHeight)
} }
} }
</script> </script>

View File

@ -36,7 +36,7 @@ def main():
(r'/config', views.config.ConfigsHandler), (r'/config', views.config.ConfigsHandler),
(r'/config/(.+)', views.config.ConfigHandler), (r'/config/(.+)', views.config.ConfigHandler),
(r'/((css|img|js)/.*)', tornado.web.StaticFileHandler, {'path': WEB_ROOT}), (r'/((css|fonts|img|js)/.*)', tornado.web.StaticFileHandler, {'path': WEB_ROOT}),
(r'/(favicon\.ico)', tornado.web.StaticFileHandler, {'path': WEB_ROOT}), (r'/(favicon\.ico)', tornado.web.StaticFileHandler, {'path': WEB_ROOT}),
(r'/.*', views.main.MainHandler, {'path': WEB_ROOT}) (r'/.*', views.main.MainHandler, {'path': WEB_ROOT})
], ],

View File

@ -72,7 +72,12 @@ class Room(blivedm.BLiveClient):
'timestamp': danmaku.timestamp, 'timestamp': danmaku.timestamp,
'authorName': danmaku.uname, 'authorName': danmaku.uname,
'authorType': author_type, 'authorType': author_type,
'content': danmaku.msg 'content': danmaku.msg,
'privilegeType': danmaku.privilege_type,
'isGiftDanmaku': bool(danmaku.msg_type),
'authorLevel': danmaku.user_level,
'isNewbie': danmaku.urank < 10000,
'isMobileVerified': bool(danmaku.mobile_verify)
}) })
async def _on_receive_gift(self, gift: blivedm.GiftMessage): async def _on_receive_gift(self, gift: blivedm.GiftMessage):
@ -80,6 +85,7 @@ class Room(blivedm.BLiveClient):
return return
self.send_message(Command.ADD_GIFT, { self.send_message(Command.ADD_GIFT, {
'avatarUrl': await get_avatar_url(gift.uid), 'avatarUrl': await get_avatar_url(gift.uid),
'timestamp': gift.timestamp,
'authorName': gift.uname, 'authorName': gift.uname,
'giftName': gift.gift_name, 'giftName': gift.gift_name,
'giftNum': gift.num, 'giftNum': gift.num,
@ -89,6 +95,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_VIP, { self.send_message(Command.ADD_VIP, {
'avatarUrl': await get_avatar_url(message.uid), 'avatarUrl': await get_avatar_url(message.uid),
'timestamp': message.start_time,
'authorName': message.username 'authorName': message.username
}) })
@ -128,14 +135,24 @@ class RoomManager:
'timestamp': time.time(), 'timestamp': time.time(),
'authorName': 'xfgryujk', 'authorName': 'xfgryujk',
'authorType': 0, 'authorType': 0,
'content': '我能吞下玻璃而不伤身体' 'content': '我能吞下玻璃而不伤身体',
'privilegeType': 0,
'isGiftDanmaku': False,
'authorLevel': 20,
'isNewbie': False,
'isMobileVerified': True
}) })
room.send_message(Command.ADD_TEXT, { room.send_message(Command.ADD_TEXT, {
'avatarUrl': 'https://i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@24w_24h.webp', 'avatarUrl': 'https://i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@24w_24h.webp',
'timestamp': time.time(), 'timestamp': time.time(),
'authorName': '主播', 'authorName': '主播',
'authorType': 3, 'authorType': 3,
'content': "I can eat glass, it doesn't hurt me." 'content': "I can eat glass, it doesn't hurt me.",
'privilegeType': 0,
'isGiftDanmaku': False,
'authorLevel': 20,
'isNewbie': False,
'isMobileVerified': True
}) })
room.send_message(Command.ADD_VIP, { room.send_message(Command.ADD_VIP, {
'avatarUrl': 'https://i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@24w_24h.webp', 'avatarUrl': 'https://i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@24w_24h.webp',
@ -195,7 +212,7 @@ class ChatHandler(tornado.websocket.WebSocketHandler):
logger.warning('未知的命令: %s data: %s', body['cmd'], body['data']) logger.warning('未知的命令: %s data: %s', body['cmd'], body['data'])
def on_close(self): def on_close(self):
logger.info('Websocket断开 %s room: %d', self.request.remote_ip, self.room_id) logger.info('Websocket断开 %s room: %s', self.request.remote_ip, self.room_id)
if self.room_id is not None: if self.room_id is not None:
room_manager.del_client(self.room_id, self) room_manager.del_client(self.room_id, self)