mirror of
https://github.com/xfgryujk/blivechat.git
synced 2026-08-23 20:03:28 +08:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9565c52e23 | ||
|
|
bb1acaecd6 | ||
|
|
dcbb90a7bd | ||
|
|
c46542bee0 | ||
|
|
709ec0585e | ||
|
|
a9c6709080 | ||
|
|
938f758920 | ||
|
|
ce4974be94 | ||
|
|
040bad0eba |
20
README.md
20
README.md
@@ -1,5 +1,5 @@
|
||||
# blivechat
|
||||
用于OBS的仿YouTube风格的bilibili直播聊天层
|
||||
用于OBS的仿YouTube风格的bilibili直播评论栏
|
||||
|
||||
最近喜欢看VTuber,想为此写些程序,于是有了这个东西。~~写到一半发现有类似项目了:[bilibili-live-chat](https://github.com/Tsuk1ko/bilibili-live-chat)、[BiliChat](https://github.com/3Shain/BiliChat)。~~ 本项目就当做练手吧,而且对YouTube的模仿程度更高
|
||||
|
||||
@@ -8,14 +8,14 @@
|
||||

|
||||
|
||||
## 特性
|
||||
* 兼容YouTube直播chat的样式
|
||||
* 金瓜子礼物模仿Super Chat显示
|
||||
* 兼容YouTube直播评论栏的样式
|
||||
* 金瓜子礼物模仿醒目留言显示
|
||||
* 高亮舰队、房管、主播的用户名
|
||||
* 支持屏蔽弹幕、限制最大速度等设置
|
||||
* 自带样式生成器
|
||||
|
||||
## 使用方法
|
||||
### 发布版
|
||||
### 本地使用
|
||||
1. 下载[发布版](https://github.com/xfgryujk/blivechat/releases)(仅提供x64 Windows版)
|
||||
2. 双击`blivechat.exe`运行服务器,或者用命令行可以指定host和端口号:
|
||||
```bat
|
||||
@@ -25,6 +25,12 @@
|
||||
4. 用样式生成器生成样式,复制CSS
|
||||
5. 在OBS中添加浏览器源,输入URL和自定义CSS,或者可以在首页的样式设置里输入CSS
|
||||
|
||||
### 公共服务器
|
||||
请优先在本地使用,使用公共服务器会有更大的弹幕延迟,而且服务器故障时可能出现直播事故
|
||||
|
||||
* [第三方公共服务器](http://chat.bilisc.com/)
|
||||
* [仅样式生成器](https://style.vtbs.moe/)
|
||||
|
||||
### 源代码版
|
||||
1. 编译前端(需要安装Node.js和npm):
|
||||
```sh
|
||||
@@ -41,12 +47,10 @@
|
||||
```sh
|
||||
python3 main.py --host 127.0.0.1 --port 12450
|
||||
```
|
||||
3. 用浏览器打开[http://localhost:12450](http://localhost:12450),输入房间ID,保存配置,复制房间URL
|
||||
4. 用样式生成器生成样式,复制CSS
|
||||
5. 在OBS中添加浏览器源,输入URL和自定义CSS,或者可以在首页的样式设置里输入CSS
|
||||
3. 用浏览器打开[http://localhost:12450](http://localhost:12450),以下略
|
||||
|
||||
### Docker
|
||||
1. ```sh
|
||||
docker run -d -p 12450:12450 xfgryujk/blivechat:latest
|
||||
```
|
||||
2. 以下略
|
||||
2. 用浏览器打开[http://localhost:12450](http://localhost:12450),以下略
|
||||
|
||||
2
blivedm
2
blivedm
Submodule blivedm updated: 1613238420...efc7f3b3f8
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<yt-live-chat-paid-message-renderer class="style-scope yt-live-chat-item-list-renderer" allow-animations :style="{
|
||||
<yt-live-chat-paid-message-renderer class="style-scope yt-live-chat-item-list-renderer" allow-animations
|
||||
:show-only-header="!content" :style="{
|
||||
'--yt-live-chat-paid-message-primary-color': color.contentBg,
|
||||
'--yt-live-chat-paid-message-secondary-color': color.headerBg,
|
||||
'--yt-live-chat-paid-message-header-color': color.header,
|
||||
@@ -21,7 +22,7 @@
|
||||
<span id="timestamp" class="style-scope yt-live-chat-paid-message-renderer">{{timeText}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="content" class="style-scope yt-live-chat-paid-message-renderer" v-show="content">
|
||||
<div id="content" class="style-scope yt-live-chat-paid-message-renderer">
|
||||
<div id="message" dir="auto" class="style-scope yt-live-chat-paid-message-renderer">
|
||||
{{content}}
|
||||
</div>
|
||||
|
||||
@@ -37,6 +37,9 @@ import LegacyPaidMessage from './LegacyPaidMessage.vue'
|
||||
import PaidMessage from './PaidMessage.vue'
|
||||
import * as constants from './constants'
|
||||
|
||||
// const CHAT_SMOOTH_ANIMATION_TIME_MS = 84
|
||||
const SCROLLED_TO_BOTTOM_EPSILON = 15
|
||||
|
||||
export default {
|
||||
name: 'ChatRenderer',
|
||||
components: {
|
||||
@@ -83,8 +86,8 @@ export default {
|
||||
this.$refs.scroller.scrollTop = this.$refs.scroller.scrollHeight
|
||||
},
|
||||
onScroll() {
|
||||
this.canAutoScroll = Math.abs(this.$refs.scroller.scrollHeight - this.$refs.scroller.scrollTop
|
||||
- this.$refs.scroller.clientHeight) < 2
|
||||
let scroller = this.$refs.scroller
|
||||
this.canAutoScroll = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight < SCROLLED_TO_BOTTOM_EPSILON
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ export default {
|
||||
sidebar: {
|
||||
home: 'Home',
|
||||
stylegen: 'Style generator',
|
||||
projectAddress: 'Project address'
|
||||
projectAddress: 'Project address',
|
||||
giftRecord: 'Super Chat record'
|
||||
},
|
||||
home: {
|
||||
roomIdEmpty: "Room ID can't be empty",
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
sidebar: {
|
||||
home: 'トップページ',
|
||||
stylegen: 'スタイルジェネレータ',
|
||||
projectAddress: 'プロジェクトアドレス'
|
||||
projectAddress: 'プロジェクトアドレス',
|
||||
giftRecord: 'スーパーチャット記録'
|
||||
},
|
||||
home: {
|
||||
roomIdEmpty: 'ルームのIDを空白にすることはできません',
|
||||
|
||||
@@ -2,7 +2,8 @@ export default {
|
||||
sidebar: {
|
||||
home: '首页',
|
||||
stylegen: '样式生成器',
|
||||
projectAddress: '项目地址'
|
||||
projectAddress: '项目地址',
|
||||
giftRecord: '打赏记录'
|
||||
},
|
||||
home: {
|
||||
roomIdEmpty: '房间ID不能为空',
|
||||
@@ -11,9 +12,9 @@ export default {
|
||||
general: '常规',
|
||||
roomId: '房间ID',
|
||||
showDanmaku: '显示弹幕',
|
||||
showGift: '显示礼物和新舰长',
|
||||
showGift: '显示打赏和新舰长',
|
||||
mergeSimilarDanmaku: '合并相似弹幕',
|
||||
minGiftPrice: '最低显示礼物价格(元)',
|
||||
minGiftPrice: '最低显示打赏价格(元)',
|
||||
maxSpeed: '弹幕最大速度(条/秒,0为无限制)',
|
||||
maxNumber: '最大弹幕数',
|
||||
|
||||
@@ -76,7 +77,7 @@ export default {
|
||||
moderatorMessageBgColor: '房管消息背景色',
|
||||
memberMessageBgColor: '舰长消息背景色',
|
||||
|
||||
scAndNewMember: '礼物、舰长',
|
||||
scAndNewMember: '打赏、舰长',
|
||||
firstLineFont: '第一行字体',
|
||||
firstLineFontSize: '第一行字体尺寸',
|
||||
firstLineLineHeight: '第一行行高(0为默认)',
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
<i class="el-icon-share"></i>{{$t('sidebar.projectAddress')}}
|
||||
</el-menu-item>
|
||||
</a>
|
||||
<a href="https://bilisc.com/" target="_blank">
|
||||
<el-menu-item>
|
||||
<i class="el-icon-share"></i>{{$t('sidebar.giftRecord')}}
|
||||
</el-menu-item>
|
||||
</a>
|
||||
<el-submenu index="null">
|
||||
<template slot="title">
|
||||
<i class="el-icon-chat-line-square"></i>Language
|
||||
|
||||
@@ -7,7 +7,7 @@ import config from '@/api/config'
|
||||
import ChatRenderer from '@/components/ChatRenderer'
|
||||
import * as constants from '@/components/ChatRenderer/constants'
|
||||
|
||||
// const COMMAND_HEARTBEAT = 0
|
||||
const COMMAND_HEARTBEAT = 0
|
||||
const COMMAND_JOIN_ROOM = 1
|
||||
const COMMAND_ADD_TEXT = 2
|
||||
const COMMAND_ADD_GIFT = 3
|
||||
@@ -27,7 +27,12 @@ export default {
|
||||
cfg.maxSpeed = 0
|
||||
return {
|
||||
config: cfg,
|
||||
|
||||
websocket: null,
|
||||
retryCount: 0,
|
||||
isDestroying: false,
|
||||
heartbeatTimerId: null,
|
||||
|
||||
messagesBufferTimerId: null,
|
||||
nextId: 0,
|
||||
messagesBuffer: [], // 暂时不显示的消息,可能会丢弃
|
||||
@@ -35,28 +40,17 @@ export default {
|
||||
paidMessages: []
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
// 开发时使用localhost:12450
|
||||
const url = process.env.NODE_ENV === 'development' ? 'ws://localhost:12450/chat' : `ws://${window.location.host}/chat`
|
||||
this.websocket = new WebSocket(url)
|
||||
this.websocket.onopen = this.onWsOpen
|
||||
this.websocket.onmessage = this.onWsMessage
|
||||
|
||||
created() {
|
||||
this.wsConnect()
|
||||
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.config = cfg
|
||||
} catch (e) {
|
||||
this.$message.error('获取配置失败:' + e)
|
||||
}
|
||||
this.updateConfig(this.$route.query.config_id)
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.messagesBufferTimerId) {
|
||||
window.clearInterval(this.messagesBufferTimerId)
|
||||
}
|
||||
this.isDestroying = true
|
||||
this.websocket.close()
|
||||
},
|
||||
watch: {
|
||||
@@ -66,12 +60,39 @@ export default {
|
||||
this.messagesBufferTimerId = null
|
||||
}
|
||||
if (val.maxSpeed > 0) {
|
||||
this.messagesBufferTimerId = window.setInterval(this.handleMessagesBuffer.bind(this), 1000 / val.maxSpeed)
|
||||
this.messagesBufferTimerId = window.setInterval(this.handleMessagesBuffer, 1000 / val.maxSpeed)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async updateConfig(configId) {
|
||||
try {
|
||||
let cfg = await config.getRemoteConfig(configId)
|
||||
cfg.blockKeywords = cfg.blockKeywords.split('\n').filter(val => val)
|
||||
cfg.blockUsers = cfg.blockUsers.split('\n').filter(val => val)
|
||||
this.config = cfg
|
||||
} catch (e) {
|
||||
this.$message.error('获取配置失败:' + e)
|
||||
}
|
||||
},
|
||||
wsConnect() {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'
|
||||
// 开发时使用localhost:12450
|
||||
const host = process.env.NODE_ENV === 'development' ? 'localhost:12450' : window.location.host
|
||||
const url = `${protocol}://${host}/chat`
|
||||
this.websocket = new WebSocket(url)
|
||||
this.websocket.onopen = this.onWsOpen
|
||||
this.websocket.onclose = this.onWsClose
|
||||
this.websocket.onmessage = this.onWsMessage
|
||||
this.heartbeatTimerId = window.setInterval(this.sendHeartbeat, 10 * 1000)
|
||||
},
|
||||
sendHeartbeat() {
|
||||
this.websocket.send(JSON.stringify({
|
||||
cmd: COMMAND_HEARTBEAT
|
||||
}))
|
||||
},
|
||||
onWsOpen() {
|
||||
this.retryCount = 0
|
||||
this.websocket.send(JSON.stringify({
|
||||
cmd: COMMAND_JOIN_ROOM,
|
||||
data: {
|
||||
@@ -79,16 +100,28 @@ export default {
|
||||
}
|
||||
}))
|
||||
},
|
||||
onWsClose() {
|
||||
if (this.heartbeatTimerId) {
|
||||
window.clearInterval(this.heartbeatTimerId)
|
||||
this.heartbeatTimerId = null
|
||||
}
|
||||
if (this.isDestroying) {
|
||||
return
|
||||
}
|
||||
window.console.log(`掉线重连中${++this.retryCount}`)
|
||||
this.wsConnect()
|
||||
},
|
||||
onWsMessage(event) {
|
||||
let {cmd, data} = JSON.parse(event.data)
|
||||
let message = null
|
||||
let time = data.timestamp ? new Date(data.timestamp * 1000) : new Date()
|
||||
switch(cmd) {
|
||||
switch (cmd) {
|
||||
case COMMAND_ADD_TEXT:
|
||||
if (!this.config.showDanmaku || !this.filterTextMessage(data) || this.mergeSimilar(data.content)) {
|
||||
break
|
||||
}
|
||||
message = {
|
||||
id: `text_${this.nextId++}`,
|
||||
type: constants.MESSAGE_TYPE_TEXT,
|
||||
avatarUrl: data.avatarUrl,
|
||||
time: time,
|
||||
@@ -108,6 +141,7 @@ export default {
|
||||
break
|
||||
}
|
||||
message = {
|
||||
id: `gift_${this.nextId++}`,
|
||||
type: constants.MESSAGE_TYPE_SUPER_CHAT,
|
||||
avatarUrl: data.avatarUrl,
|
||||
authorName: data.authorName,
|
||||
@@ -122,6 +156,7 @@ export default {
|
||||
break
|
||||
}
|
||||
message = {
|
||||
id: `member_${this.nextId++}`,
|
||||
type: constants.MESSAGE_TYPE_MEMBER,
|
||||
avatarUrl: data.avatarUrl,
|
||||
time: time,
|
||||
@@ -138,6 +173,7 @@ export default {
|
||||
break
|
||||
}
|
||||
message = {
|
||||
id: `sc_${data.id}`,
|
||||
type: constants.MESSAGE_TYPE_SUPER_CHAT,
|
||||
avatarUrl: data.avatarUrl,
|
||||
authorName: data.authorName,
|
||||
@@ -146,10 +182,21 @@ export default {
|
||||
content: data.content
|
||||
}
|
||||
break
|
||||
case COMMAND_DEL_SUPER_CHAT:
|
||||
// TODO 删除SC
|
||||
case COMMAND_DEL_SUPER_CHAT: {
|
||||
let arrays = [this.messages, this.messagesBuffer, this.paidMessages]
|
||||
for (let id of data.ids) {
|
||||
id = `sc_${id}`
|
||||
for (let arr of arrays) {
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (arr[i].id === id) {
|
||||
arr.splice(i, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if (message) {
|
||||
this.addMessageBuffer(message)
|
||||
}
|
||||
@@ -208,7 +255,7 @@ export default {
|
||||
return false
|
||||
},
|
||||
addMessageBuffer(message) {
|
||||
if (this.config.maxSpeed > 0 && message.type === 0) {
|
||||
if (this.config.maxSpeed > 0 && message.type === constants.MESSAGE_TYPE_TEXT) {
|
||||
message.addTime = new Date()
|
||||
this.messagesBuffer.push(message)
|
||||
} else {
|
||||
@@ -217,7 +264,6 @@ export default {
|
||||
}
|
||||
},
|
||||
addMessageShow(message) {
|
||||
message.id = this.nextId++
|
||||
message.addTime = new Date()
|
||||
this.messages.push(message)
|
||||
if (message.type !== constants.MESSAGE_TYPE_TEXT) {
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 113 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 288 KiB After Width: | Height: | Size: 309 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 154 KiB |
@@ -2,7 +2,7 @@
|
||||
|
||||
import aiohttp
|
||||
|
||||
VERSION = 'v1.2.0'
|
||||
VERSION = 'v1.2.2'
|
||||
|
||||
|
||||
async def check_update():
|
||||
|
||||
@@ -19,7 +19,10 @@ class ApiHandler(tornado.web.RequestHandler):
|
||||
|
||||
def prepare(self):
|
||||
if self.request.headers.get('Content-Type', '').startswith('application/json'):
|
||||
self.json_args = json.loads(self.request.body)
|
||||
try:
|
||||
self.json_args = json.loads(self.request.body)
|
||||
except json.JSONDecodeError:
|
||||
self.json_args = None
|
||||
else:
|
||||
self.json_args = None
|
||||
|
||||
|
||||
151
views/chat.py
151
views/chat.py
@@ -181,70 +181,49 @@ class RoomManager:
|
||||
def __init__(self):
|
||||
self._rooms: Dict[int, Room] = {}
|
||||
|
||||
def add_client(self, room_id, client: 'ChatHandler'):
|
||||
if room_id in self._rooms:
|
||||
room = self._rooms[room_id]
|
||||
else:
|
||||
logger.info('Creating room %d', room_id)
|
||||
room = Room(room_id)
|
||||
self._rooms[room_id] = room
|
||||
room.start()
|
||||
async def add_client(self, room_id, client: 'ChatHandler'):
|
||||
if room_id not in self._rooms:
|
||||
if not await self._add_room(room_id):
|
||||
client.close()
|
||||
return
|
||||
room = self._rooms[room_id]
|
||||
room.clients.append(client)
|
||||
logger.info('%d clients in room %s', len(room.clients), room_id)
|
||||
|
||||
if client.application.settings['debug']:
|
||||
self.__send_test_message(room)
|
||||
client.send_test_message()
|
||||
|
||||
def del_client(self, room_id, client: 'ChatHandler'):
|
||||
if room_id not in self._rooms:
|
||||
return
|
||||
room = self._rooms[room_id]
|
||||
room.clients.remove(client)
|
||||
logger.info('%d clients in room %s', len(room.clients), room_id)
|
||||
if not room.clients:
|
||||
logger.info('Removing room %d', room_id)
|
||||
room.stop_and_close()
|
||||
del self._rooms[room_id]
|
||||
self._del_room(room_id)
|
||||
|
||||
# 测试用
|
||||
@staticmethod
|
||||
def __send_test_message(room):
|
||||
base_data = {
|
||||
'avatarUrl': 'https://i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@48w_48h',
|
||||
'timestamp': time.time(),
|
||||
'authorName': 'xfgryujk',
|
||||
}
|
||||
text_data = {
|
||||
**base_data,
|
||||
'authorType': 0,
|
||||
'content': '我能吞下玻璃而不伤身体',
|
||||
'privilegeType': 0,
|
||||
'isGiftDanmaku': False,
|
||||
'authorLevel': 20,
|
||||
'isNewbie': False,
|
||||
'isMobileVerified': True
|
||||
}
|
||||
vip_data = base_data
|
||||
gift_data = {
|
||||
**base_data,
|
||||
'giftName': '礼花',
|
||||
'giftNum': 1,
|
||||
'totalCoin': 28000
|
||||
}
|
||||
room.send_message(Command.ADD_TEXT, text_data)
|
||||
text_data['authorName'] = '主播'
|
||||
text_data['authorType'] = 3
|
||||
text_data['content'] = "I can eat glass, it doesn't hurt me."
|
||||
room.send_message(Command.ADD_TEXT, text_data)
|
||||
room.send_message(Command.ADD_MEMBER, vip_data)
|
||||
room.send_message(Command.ADD_GIFT, gift_data)
|
||||
gift_data['giftName'] = '节奏风暴'
|
||||
gift_data['totalCoin'] = 100000
|
||||
room.send_message(Command.ADD_GIFT, gift_data)
|
||||
gift_data['giftName'] = '摩天大楼'
|
||||
gift_data['totalCoin'] = 450000
|
||||
room.send_message(Command.ADD_GIFT, gift_data)
|
||||
gift_data['giftName'] = '小电视飞船'
|
||||
gift_data['totalCoin'] = 1245000
|
||||
room.send_message(Command.ADD_GIFT, gift_data)
|
||||
async def _add_room(self, room_id):
|
||||
if room_id in self._rooms:
|
||||
return True
|
||||
logger.info('Creating room %d', room_id)
|
||||
room = Room(room_id)
|
||||
self._rooms[room_id] = room
|
||||
if await room.init_room():
|
||||
room.start()
|
||||
return True
|
||||
else:
|
||||
self._del_room(room_id)
|
||||
return False
|
||||
|
||||
def _del_room(self, room_id):
|
||||
if room_id not in self._rooms:
|
||||
return
|
||||
logger.info('Removing room %d', room_id)
|
||||
room = self._rooms[room_id]
|
||||
for client in room.clients:
|
||||
client.close()
|
||||
room.stop_and_close()
|
||||
del self._rooms[room_id]
|
||||
|
||||
|
||||
room_manager = RoomManager()
|
||||
@@ -260,18 +239,21 @@ class ChatHandler(tornado.websocket.WebSocketHandler):
|
||||
logger.info('Websocket connected %s', self.request.remote_ip)
|
||||
|
||||
def on_message(self, message):
|
||||
if self.room_id is not None:
|
||||
return
|
||||
body = json.loads(message)
|
||||
if body['cmd'] == Command.JOIN_ROOM:
|
||||
cmd = body['cmd']
|
||||
if cmd == Command.HEARTBEAT:
|
||||
pass
|
||||
elif cmd == Command.JOIN_ROOM:
|
||||
if self.room_id is not None:
|
||||
return
|
||||
self.room_id = int(body['data']['roomId'])
|
||||
logger.info('Client %s is joining room %d', self.request.remote_ip, self.room_id)
|
||||
room_manager.add_client(self.room_id, self)
|
||||
asyncio.ensure_future(room_manager.add_client(self.room_id, self))
|
||||
else:
|
||||
logger.warning('Unknown cmd: %s data: %s', body['cmd'], body['data'])
|
||||
logger.warning('Unknown cmd: %s body: %s', cmd, body)
|
||||
|
||||
def on_close(self):
|
||||
logger.info('Websocket disconnected %s room: %s', self.request.remote_ip, 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:
|
||||
room_manager.del_client(self.room_id, self)
|
||||
|
||||
@@ -280,3 +262,54 @@ class ChatHandler(tornado.websocket.WebSocketHandler):
|
||||
if self.application.settings['debug']:
|
||||
return True
|
||||
return super().check_origin(origin)
|
||||
|
||||
# 测试用
|
||||
def send_test_message(self):
|
||||
base_data = {
|
||||
'avatarUrl': 'https://i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@48w_48h',
|
||||
'timestamp': time.time(),
|
||||
'authorName': 'xfgryujk',
|
||||
}
|
||||
text_data = {
|
||||
**base_data,
|
||||
'authorType': 0,
|
||||
'content': '我能吞下玻璃而不伤身体',
|
||||
'privilegeType': 0,
|
||||
'isGiftDanmaku': False,
|
||||
'authorLevel': 20,
|
||||
'isNewbie': False,
|
||||
'isMobileVerified': True
|
||||
}
|
||||
member_data = base_data
|
||||
gift_data = {
|
||||
**base_data,
|
||||
'giftName': '摩天大楼',
|
||||
'giftNum': 1,
|
||||
'totalCoin': 450000
|
||||
}
|
||||
sc_data = {
|
||||
**base_data,
|
||||
'price': 30,
|
||||
'content': 'The quick brown fox jumps over the lazy dog',
|
||||
'id': 1
|
||||
}
|
||||
self.send_message(Command.ADD_TEXT, text_data)
|
||||
text_data['authorName'] = '主播'
|
||||
text_data['authorType'] = 3
|
||||
text_data['content'] = "I can eat glass, it doesn't hurt me."
|
||||
self.send_message(Command.ADD_TEXT, text_data)
|
||||
self.send_message(Command.ADD_MEMBER, member_data)
|
||||
self.send_message(Command.ADD_SUPER_CHAT, sc_data)
|
||||
sc_data['price'] = 100
|
||||
sc_data['content'] = '敏捷的棕色狐狸跳过了懒狗'
|
||||
sc_data['id'] = 2
|
||||
self.send_message(Command.ADD_SUPER_CHAT, sc_data)
|
||||
# self.send_message(Command.DEL_SUPER_CHAT, {'ids': [1, 2]})
|
||||
self.send_message(Command.ADD_GIFT, gift_data)
|
||||
gift_data['giftName'] = '小电视飞船'
|
||||
gift_data['totalCoin'] = 1245000
|
||||
self.send_message(Command.ADD_GIFT, gift_data)
|
||||
|
||||
def send_message(self, cmd, data):
|
||||
body = json.dumps({'cmd': cmd, 'data': data})
|
||||
self.write_message(body)
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import uuid
|
||||
|
||||
import views.base
|
||||
from typing import *
|
||||
|
||||
configs: Dict[str, dict] = {}
|
||||
MAX_CONFIG_SIZE = 100 * 1024
|
||||
|
||||
ALLOWED_FIELDS = (
|
||||
'showDanmaku', 'showGift', 'mergeSimilarDanmaku', 'minGiftPrice', 'maxSpeed',
|
||||
'maxNumber', 'blockGiftDanmaku', 'blockLevel', 'blockNewbie', 'blockNotMobileVerified',
|
||||
'blockKeywords', 'blockUsers', 'blockMedalLevel', 'css'
|
||||
)
|
||||
configs: Dict[str, dict] = {}
|
||||
|
||||
|
||||
# noinspection PyAbstractClass
|
||||
class ConfigsHandler(views.base.ApiHandler):
|
||||
async def post(self):
|
||||
if not isinstance(self.json_args, dict):
|
||||
self.set_status(400)
|
||||
return
|
||||
|
||||
config = self.json_args
|
||||
config_id = str(uuid.uuid4())
|
||||
config = {
|
||||
name: self.json_args[name] for name in ALLOWED_FIELDS
|
||||
}
|
||||
config['id'] = config_id
|
||||
config_str = json.dumps(config)
|
||||
if len(config_str) > MAX_CONFIG_SIZE:
|
||||
self.set_status(413)
|
||||
return
|
||||
|
||||
configs[config_id] = config
|
||||
self.write(config_str)
|
||||
self.set_status(201)
|
||||
self.write(config)
|
||||
self.set_header('Content-Type', 'application/json; charset=UTF-8')
|
||||
|
||||
if len(configs) > 10000:
|
||||
for _, key in zip(range(100), configs):
|
||||
@@ -34,13 +39,23 @@ class ConfigsHandler(views.base.ApiHandler):
|
||||
# noinspection PyAbstractClass
|
||||
class ConfigHandler(views.base.ApiHandler):
|
||||
async def put(self, config_id):
|
||||
config = configs.get(config_id, None)
|
||||
if config is None:
|
||||
if config_id not in configs:
|
||||
self.set_status(404)
|
||||
return
|
||||
for name in ALLOWED_FIELDS:
|
||||
config[name] = self.json_args[name]
|
||||
self.write(config)
|
||||
if not isinstance(self.json_args, dict):
|
||||
self.set_status(400)
|
||||
return
|
||||
|
||||
config = self.json_args
|
||||
config['id'] = config_id
|
||||
config_str = json.dumps(config)
|
||||
if len(config_str) > MAX_CONFIG_SIZE:
|
||||
self.set_status(413)
|
||||
return
|
||||
|
||||
configs[config_id] = config
|
||||
self.write(config_str)
|
||||
self.set_header('Content-Type', 'application/json; charset=UTF-8')
|
||||
|
||||
async def get(self, config_id):
|
||||
config = configs.get(config_id, None)
|
||||
|
||||
Reference in New Issue
Block a user