完成配置功能

pull/3/head
John Smith 5 years ago
parent 920e6fff96
commit 1cbaed4127

@ -5,6 +5,9 @@
<span id="timestamp">{{time}}</span>
<span id="author-name" :type="authorTypeText">{{authorName}}</span>
<span id="message">{{content}}</span>
<el-badge :value="repeated" :max="99" v-show="repeated > 1"
:style="`--repeated-mark-color: ${repeatedMarkColor}`"
></el-badge>
</div>
</yt-live-chat-text-message-renderer>
</template>
@ -16,6 +19,8 @@ const AUTHOR_TYPE_TO_TEXT = [
'moderator', //
'owner' //
]
const REPEATED_MARK_COLOR_START = [0x21, 0x96, 0xF3]
const REPEATED_MARK_COLOR_END = [0xFF, 0x57, 0x22]
export default {
name: 'TextMessage',
@ -24,12 +29,40 @@ export default {
time: String,
authorName: String,
authorType: Number,
content: String
content: String,
repeated: Number
},
computed: {
authorTypeText() {
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>
<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>

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

@ -36,7 +36,7 @@ def main():
(r'/config', views.config.ConfigsHandler),
(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'/.*', views.main.MainHandler, {'path': WEB_ROOT})
],

@ -72,7 +72,12 @@ class Room(blivedm.BLiveClient):
'timestamp': danmaku.timestamp,
'authorName': danmaku.uname,
'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):
@ -80,6 +85,7 @@ class Room(blivedm.BLiveClient):
return
self.send_message(Command.ADD_GIFT, {
'avatarUrl': await get_avatar_url(gift.uid),
'timestamp': gift.timestamp,
'authorName': gift.uname,
'giftName': gift.gift_name,
'giftNum': gift.num,
@ -89,6 +95,7 @@ class Room(blivedm.BLiveClient):
async def _on_buy_guard(self, message: blivedm.GuardBuyMessage):
self.send_message(Command.ADD_VIP, {
'avatarUrl': await get_avatar_url(message.uid),
'timestamp': message.start_time,
'authorName': message.username
})
@ -128,14 +135,24 @@ class RoomManager:
'timestamp': time.time(),
'authorName': 'xfgryujk',
'authorType': 0,
'content': '我能吞下玻璃而不伤身体'
'content': '我能吞下玻璃而不伤身体',
'privilegeType': 0,
'isGiftDanmaku': False,
'authorLevel': 20,
'isNewbie': False,
'isMobileVerified': True
})
room.send_message(Command.ADD_TEXT, {
'avatarUrl': 'https://i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@24w_24h.webp',
'timestamp': time.time(),
'authorName': '主播',
'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, {
'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'])
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:
room_manager.del_client(self.room_id, self)

Loading…
Cancel
Save