mirror of
https://github.com/xfgryujk/blivechat.git
synced 2025-03-12 10:40:57 +08:00
添加自动翻译前端
This commit is contained in:
parent
0c9560f8ca
commit
d3e9300fa4
111
api/chat.py
111
api/chat.py
@ -6,13 +6,16 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
import uuid
|
||||||
from typing import *
|
from typing import *
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import tornado.websocket
|
import tornado.websocket
|
||||||
|
|
||||||
import blivedm.blivedm as blivedm
|
import blivedm.blivedm as blivedm
|
||||||
|
import config
|
||||||
import models.avatar
|
import models.avatar
|
||||||
|
import models.translate
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -25,6 +28,7 @@ class Command(enum.IntEnum):
|
|||||||
ADD_MEMBER = 4
|
ADD_MEMBER = 4
|
||||||
ADD_SUPER_CHAT = 5
|
ADD_SUPER_CHAT = 5
|
||||||
DEL_SUPER_CHAT = 6
|
DEL_SUPER_CHAT = 6
|
||||||
|
UPDATE_TRANSLATION = 7
|
||||||
|
|
||||||
|
|
||||||
_http_session = aiohttp.ClientSession()
|
_http_session = aiohttp.ClientSession()
|
||||||
@ -94,6 +98,7 @@ class Room(blivedm.BLiveClient):
|
|||||||
def __init__(self, room_id):
|
def __init__(self, room_id):
|
||||||
super().__init__(room_id, session=_http_session, heartbeat_interval=10)
|
super().__init__(room_id, session=_http_session, heartbeat_interval=10)
|
||||||
self.clients: List['ChatHandler'] = []
|
self.clients: List['ChatHandler'] = []
|
||||||
|
self.auto_translate_count = 0
|
||||||
|
|
||||||
def stop_and_close(self):
|
def stop_and_close(self):
|
||||||
if self.is_running:
|
if self.is_running:
|
||||||
@ -110,6 +115,14 @@ class Room(blivedm.BLiveClient):
|
|||||||
except tornado.websocket.WebSocketClosedError:
|
except tornado.websocket.WebSocketClosedError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def send_message_if(self, can_send_func: Callable[['ChatHandler'], bool], cmd, data):
|
||||||
|
body = json.dumps({'cmd': cmd, 'data': data})
|
||||||
|
for client in filter(can_send_func, self.clients):
|
||||||
|
try:
|
||||||
|
client.write_message(body)
|
||||||
|
except tornado.websocket.WebSocketClosedError:
|
||||||
|
pass
|
||||||
|
|
||||||
async def _on_receive_danmaku(self, danmaku: blivedm.DanmakuMessage):
|
async def _on_receive_danmaku(self, danmaku: blivedm.DanmakuMessage):
|
||||||
asyncio.ensure_future(self.__on_receive_danmaku(danmaku))
|
asyncio.ensure_future(self.__on_receive_danmaku(danmaku))
|
||||||
|
|
||||||
@ -122,6 +135,19 @@ class Room(blivedm.BLiveClient):
|
|||||||
author_type = 1 # 舰队
|
author_type = 1 # 舰队
|
||||||
else:
|
else:
|
||||||
author_type = 0
|
author_type = 0
|
||||||
|
|
||||||
|
need_translate = self._need_translate(danmaku.msg)
|
||||||
|
if need_translate:
|
||||||
|
translation = models.translate.get_translation_from_cache(danmaku.msg)
|
||||||
|
if translation is None:
|
||||||
|
# 没有缓存,需要后面异步翻译后通知
|
||||||
|
translation = ''
|
||||||
|
else:
|
||||||
|
need_translate = False
|
||||||
|
else:
|
||||||
|
translation = ''
|
||||||
|
|
||||||
|
id_ = uuid.uuid4().hex
|
||||||
# 为了节省带宽用list而不是dict
|
# 为了节省带宽用list而不是dict
|
||||||
self.send_message(Command.ADD_TEXT, [
|
self.send_message(Command.ADD_TEXT, [
|
||||||
# 0: avatarUrl
|
# 0: avatarUrl
|
||||||
@ -145,15 +171,24 @@ class Room(blivedm.BLiveClient):
|
|||||||
# 9: isMobileVerified
|
# 9: isMobileVerified
|
||||||
1 if danmaku.mobile_verify else 0,
|
1 if danmaku.mobile_verify else 0,
|
||||||
# 10: medalLevel
|
# 10: medalLevel
|
||||||
0 if danmaku.room_id != self.room_id else danmaku.medal_level
|
0 if danmaku.room_id != self.room_id else danmaku.medal_level,
|
||||||
|
# 11: id
|
||||||
|
id_,
|
||||||
|
# 12: translation
|
||||||
|
translation
|
||||||
])
|
])
|
||||||
|
|
||||||
|
if need_translate:
|
||||||
|
await self._translate_and_response(danmaku.msg, id_)
|
||||||
|
|
||||||
async def _on_receive_gift(self, gift: blivedm.GiftMessage):
|
async def _on_receive_gift(self, gift: blivedm.GiftMessage):
|
||||||
avatar_url = models.avatar.process_avatar_url(gift.face)
|
avatar_url = models.avatar.process_avatar_url(gift.face)
|
||||||
models.avatar.update_avatar_cache(gift.uid, avatar_url)
|
models.avatar.update_avatar_cache(gift.uid, avatar_url)
|
||||||
if gift.coin_type != 'gold': # 丢人
|
if gift.coin_type != 'gold': # 丢人
|
||||||
return
|
return
|
||||||
|
id_ = uuid.uuid4().hex
|
||||||
self.send_message(Command.ADD_GIFT, {
|
self.send_message(Command.ADD_GIFT, {
|
||||||
|
'id': id_,
|
||||||
'avatarUrl': avatar_url,
|
'avatarUrl': avatar_url,
|
||||||
'timestamp': gift.timestamp,
|
'timestamp': gift.timestamp,
|
||||||
'authorName': gift.uname,
|
'authorName': gift.uname,
|
||||||
@ -164,7 +199,9 @@ class Room(blivedm.BLiveClient):
|
|||||||
asyncio.ensure_future(self.__on_buy_guard(message))
|
asyncio.ensure_future(self.__on_buy_guard(message))
|
||||||
|
|
||||||
async def __on_buy_guard(self, message: blivedm.GuardBuyMessage):
|
async def __on_buy_guard(self, message: blivedm.GuardBuyMessage):
|
||||||
|
id_ = uuid.uuid4().hex
|
||||||
self.send_message(Command.ADD_MEMBER, {
|
self.send_message(Command.ADD_MEMBER, {
|
||||||
|
'id': id_,
|
||||||
'avatarUrl': await models.avatar.get_avatar_url(message.uid),
|
'avatarUrl': await models.avatar.get_avatar_url(message.uid),
|
||||||
'timestamp': message.start_time,
|
'timestamp': message.start_time,
|
||||||
'authorName': message.username
|
'authorName': message.username
|
||||||
@ -173,20 +210,59 @@ class Room(blivedm.BLiveClient):
|
|||||||
async def _on_super_chat(self, message: blivedm.SuperChatMessage):
|
async def _on_super_chat(self, message: blivedm.SuperChatMessage):
|
||||||
avatar_url = models.avatar.process_avatar_url(message.face)
|
avatar_url = models.avatar.process_avatar_url(message.face)
|
||||||
models.avatar.update_avatar_cache(message.uid, avatar_url)
|
models.avatar.update_avatar_cache(message.uid, avatar_url)
|
||||||
|
|
||||||
|
need_translate = self._need_translate(message.message)
|
||||||
|
if need_translate:
|
||||||
|
translation = models.translate.get_translation_from_cache(message.message)
|
||||||
|
if translation is None:
|
||||||
|
# 没有缓存,需要后面异步翻译后通知
|
||||||
|
translation = ''
|
||||||
|
else:
|
||||||
|
need_translate = False
|
||||||
|
else:
|
||||||
|
translation = ''
|
||||||
|
|
||||||
|
id_ = str(message.id)
|
||||||
self.send_message(Command.ADD_SUPER_CHAT, {
|
self.send_message(Command.ADD_SUPER_CHAT, {
|
||||||
|
'id': id_,
|
||||||
'avatarUrl': avatar_url,
|
'avatarUrl': avatar_url,
|
||||||
'timestamp': message.start_time,
|
'timestamp': message.start_time,
|
||||||
'authorName': message.uname,
|
'authorName': message.uname,
|
||||||
'price': message.price,
|
'price': message.price,
|
||||||
'content': message.message,
|
'content': message.message,
|
||||||
'id': message.id
|
'translation': translation
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if need_translate:
|
||||||
|
asyncio.ensure_future(self._translate_and_response(message.message, id_))
|
||||||
|
|
||||||
async def _on_super_chat_delete(self, message: blivedm.SuperChatDeleteMessage):
|
async def _on_super_chat_delete(self, message: blivedm.SuperChatDeleteMessage):
|
||||||
self.send_message(Command.ADD_SUPER_CHAT, {
|
self.send_message(Command.ADD_SUPER_CHAT, {
|
||||||
'ids': message.ids
|
'ids': list(map(str, message.ids))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def _need_translate(self, text):
|
||||||
|
return (
|
||||||
|
config.get_config().enable_translate
|
||||||
|
and self.auto_translate_count > 0
|
||||||
|
and models.translate.need_translate(text)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _translate_and_response(self, text, msg_id):
|
||||||
|
translation = await models.translate.translate(text)
|
||||||
|
if translation is None:
|
||||||
|
return
|
||||||
|
self.send_message_if(
|
||||||
|
lambda client: client.auto_translate,
|
||||||
|
Command.UPDATE_TRANSLATION,
|
||||||
|
[
|
||||||
|
# 0: id
|
||||||
|
msg_id,
|
||||||
|
# 1: translation
|
||||||
|
translation
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RoomManager:
|
class RoomManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -200,6 +276,8 @@ class RoomManager:
|
|||||||
room = self._rooms[room_id]
|
room = self._rooms[room_id]
|
||||||
room.clients.append(client)
|
room.clients.append(client)
|
||||||
logger.info('%d clients in room %s', len(room.clients), room_id)
|
logger.info('%d clients in room %s', len(room.clients), room_id)
|
||||||
|
if client.auto_translate:
|
||||||
|
room.auto_translate_count += 1
|
||||||
|
|
||||||
if client.application.settings['debug']:
|
if client.application.settings['debug']:
|
||||||
await client.send_test_message()
|
await client.send_test_message()
|
||||||
@ -210,6 +288,8 @@ class RoomManager:
|
|||||||
room = self._rooms[room_id]
|
room = self._rooms[room_id]
|
||||||
room.clients.remove(client)
|
room.clients.remove(client)
|
||||||
logger.info('%d clients in room %s', len(room.clients), room_id)
|
logger.info('%d clients in room %s', len(room.clients), room_id)
|
||||||
|
if client.auto_translate:
|
||||||
|
room.auto_translate_count -= 1
|
||||||
if not room.clients:
|
if not room.clients:
|
||||||
self._del_room(room_id)
|
self._del_room(room_id)
|
||||||
|
|
||||||
@ -243,6 +323,7 @@ class ChatHandler(tornado.websocket.WebSocketHandler):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self._close_on_timeout_future = None
|
self._close_on_timeout_future = None
|
||||||
self.room_id = None
|
self.room_id = None
|
||||||
|
self.auto_translate = False
|
||||||
|
|
||||||
def open(self):
|
def open(self):
|
||||||
logger.info('Websocket connected %s', self.request.remote_ip)
|
logger.info('Websocket connected %s', self.request.remote_ip)
|
||||||
@ -268,6 +349,11 @@ class ChatHandler(tornado.websocket.WebSocketHandler):
|
|||||||
return
|
return
|
||||||
self.room_id = int(body['data']['roomId'])
|
self.room_id = int(body['data']['roomId'])
|
||||||
logger.info('Client %s is joining room %d', self.request.remote_ip, self.room_id)
|
logger.info('Client %s is joining room %d', self.request.remote_ip, self.room_id)
|
||||||
|
try:
|
||||||
|
cfg = body['data']['config']
|
||||||
|
self.auto_translate = cfg['autoTranslate']
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
asyncio.ensure_future(room_manager.add_client(self.room_id, self))
|
asyncio.ensure_future(room_manager.add_client(self.room_id, self))
|
||||||
self._close_on_timeout_future.cancel()
|
self._close_on_timeout_future.cancel()
|
||||||
@ -320,32 +406,43 @@ class ChatHandler(tornado.websocket.WebSocketHandler):
|
|||||||
# 9: isMobileVerified
|
# 9: isMobileVerified
|
||||||
1,
|
1,
|
||||||
# 10: medalLevel
|
# 10: medalLevel
|
||||||
0
|
0,
|
||||||
|
# 11: id
|
||||||
|
uuid.uuid4().hex,
|
||||||
|
# 12: translation
|
||||||
|
''
|
||||||
]
|
]
|
||||||
member_data = base_data.copy()
|
member_data = {
|
||||||
|
**base_data,
|
||||||
|
'id': uuid.uuid4().hex
|
||||||
|
}
|
||||||
gift_data = {
|
gift_data = {
|
||||||
**base_data,
|
**base_data,
|
||||||
|
'id': uuid.uuid4().hex,
|
||||||
'totalCoin': 450000
|
'totalCoin': 450000
|
||||||
}
|
}
|
||||||
sc_data = {
|
sc_data = {
|
||||||
**base_data,
|
**base_data,
|
||||||
|
'id': str(random.randint(1, 65535)),
|
||||||
'price': 30,
|
'price': 30,
|
||||||
'content': 'The quick brown fox jumps over the lazy dog',
|
'content': 'The quick brown fox jumps over the lazy dog',
|
||||||
'id': random.randint(1, 65535)
|
'translation': ''
|
||||||
}
|
}
|
||||||
self.send_message(Command.ADD_TEXT, text_data)
|
self.send_message(Command.ADD_TEXT, text_data)
|
||||||
text_data[2] = '主播'
|
text_data[2] = '主播'
|
||||||
text_data[3] = 3
|
text_data[3] = 3
|
||||||
text_data[4] = "I can eat glass, it doesn't hurt me."
|
text_data[4] = "I can eat glass, it doesn't hurt me."
|
||||||
|
text_data[11] = uuid.uuid4().hex
|
||||||
self.send_message(Command.ADD_TEXT, text_data)
|
self.send_message(Command.ADD_TEXT, text_data)
|
||||||
self.send_message(Command.ADD_MEMBER, member_data)
|
self.send_message(Command.ADD_MEMBER, member_data)
|
||||||
self.send_message(Command.ADD_SUPER_CHAT, sc_data)
|
self.send_message(Command.ADD_SUPER_CHAT, sc_data)
|
||||||
|
sc_data['id'] = str(random.randint(1, 65535))
|
||||||
sc_data['price'] = 100
|
sc_data['price'] = 100
|
||||||
sc_data['content'] = '敏捷的棕色狐狸跳过了懒狗'
|
sc_data['content'] = '敏捷的棕色狐狸跳过了懒狗'
|
||||||
sc_data['id'] = random.randint(1, 65535)
|
|
||||||
self.send_message(Command.ADD_SUPER_CHAT, sc_data)
|
self.send_message(Command.ADD_SUPER_CHAT, sc_data)
|
||||||
# self.send_message(Command.DEL_SUPER_CHAT, {'ids': [sc_data['id']]})
|
# self.send_message(Command.DEL_SUPER_CHAT, {'ids': [sc_data['id']]})
|
||||||
self.send_message(Command.ADD_GIFT, gift_data)
|
self.send_message(Command.ADD_GIFT, gift_data)
|
||||||
|
gift_data['id'] = uuid.uuid4().hex
|
||||||
gift_data['totalCoin'] = 1245000
|
gift_data['totalCoin'] = 1245000
|
||||||
self.send_message(Command.ADD_GIFT, gift_data)
|
self.send_message(Command.ADD_GIFT, gift_data)
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ def get_config():
|
|||||||
class AppConfig:
|
class AppConfig:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.database_url = 'sqlite:///data/database.db'
|
self.database_url = 'sqlite:///data/database.db'
|
||||||
|
self.enable_translate = True
|
||||||
|
|
||||||
def load(self, path):
|
def load(self, path):
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
@ -37,6 +38,7 @@ class AppConfig:
|
|||||||
try:
|
try:
|
||||||
app_section = config['app']
|
app_section = config['app']
|
||||||
self.database_url = app_section['database_url']
|
self.database_url = app_section['database_url']
|
||||||
|
self.enable_translate = app_section.getboolean('enable_translate')
|
||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError):
|
||||||
logger.exception('Failed to load config:')
|
logger.exception('Failed to load config:')
|
||||||
return False
|
return False
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
[app]
|
[app]
|
||||||
# See https://docs.sqlalchemy.org/en/13/core/engines.html#database-urls
|
# See https://docs.sqlalchemy.org/en/13/core/engines.html#database-urls
|
||||||
database_url = sqlite:///data/database.db
|
database_url = sqlite:///data/database.db
|
||||||
|
# Enable auto translate to Japanese
|
||||||
|
enable_translate = true
|
||||||
|
|
||||||
|
|
||||||
# DON'T modify this section
|
# DON'T modify this section
|
||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
database_url = sqlite:///data/database.db
|
database_url = sqlite:///data/database.db
|
||||||
|
enable_translate = true
|
||||||
|
@ -13,7 +13,9 @@ export const DEFAULT_CONFIG = {
|
|||||||
blockNotMobileVerified: true,
|
blockNotMobileVerified: true,
|
||||||
blockKeywords: '',
|
blockKeywords: '',
|
||||||
blockUsers: '',
|
blockUsers: '',
|
||||||
blockMedalLevel: 0
|
blockMedalLevel: 0,
|
||||||
|
|
||||||
|
autoTranslate: false
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setLocalConfig (config) {
|
export function setLocalConfig (config) {
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<text-message :key="message.id" v-if="message.type === MESSAGE_TYPE_TEXT"
|
<text-message :key="message.id" v-if="message.type === MESSAGE_TYPE_TEXT"
|
||||||
class="style-scope yt-live-chat-item-list-renderer"
|
class="style-scope yt-live-chat-item-list-renderer"
|
||||||
:avatarUrl="message.avatarUrl" :time="message.time" :authorName="message.authorName"
|
:avatarUrl="message.avatarUrl" :time="message.time" :authorName="message.authorName"
|
||||||
:authorType="message.authorType" :content="message.content" :privilegeType="message.privilegeType"
|
:authorType="message.authorType" :content="getShowContent(message)" :privilegeType="message.privilegeType"
|
||||||
:repeated="message.repeated"
|
:repeated="message.repeated"
|
||||||
></text-message>
|
></text-message>
|
||||||
<legacy-paid-message :key="message.id" v-else-if="message.type === MESSAGE_TYPE_MEMBER"
|
<legacy-paid-message :key="message.id" v-else-if="message.type === MESSAGE_TYPE_MEMBER"
|
||||||
@ -24,7 +24,7 @@
|
|||||||
<paid-message :key="message.id" v-else-if="message.type === MESSAGE_TYPE_SUPER_CHAT"
|
<paid-message :key="message.id" v-else-if="message.type === MESSAGE_TYPE_SUPER_CHAT"
|
||||||
class="style-scope yt-live-chat-item-list-renderer"
|
class="style-scope yt-live-chat-item-list-renderer"
|
||||||
:price="message.price" :avatarUrl="message.avatarUrl" :authorName="message.authorName"
|
:price="message.price" :avatarUrl="message.avatarUrl" :authorName="message.authorName"
|
||||||
:time="message.time" :content="message.content"
|
:time="message.time" :content="getShowContent(message)"
|
||||||
></paid-message>
|
></paid-message>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -118,6 +118,13 @@ export default {
|
|||||||
this.clearMessages()
|
this.clearMessages()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getShowContent(message) {
|
||||||
|
if (message.translation) {
|
||||||
|
return `${message.content}(${message.translation})`
|
||||||
|
}
|
||||||
|
return message.content
|
||||||
|
},
|
||||||
|
|
||||||
addMessage(message) {
|
addMessage(message) {
|
||||||
this.addMessages([message])
|
this.addMessages([message])
|
||||||
},
|
},
|
||||||
@ -212,6 +219,29 @@ export default {
|
|||||||
this.scrollToBottom()
|
this.scrollToBottom()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updateMessage(id, newValuesObj) {
|
||||||
|
// 遍历滚动的消息
|
||||||
|
this.forEachRecentMessage(999999999, message => {
|
||||||
|
if (message.id !== id) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for (let name in newValuesObj) {
|
||||||
|
message[name] = newValuesObj[name]
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
// 遍历固定的消息
|
||||||
|
for (let message of this.paidMessages) {
|
||||||
|
if (message.id !== id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for (let name in newValuesObj) {
|
||||||
|
message[name] = newValuesObj[name]
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// TODO 解决自动滚动的问题
|
||||||
|
},
|
||||||
|
|
||||||
enqueueMessages(messages) {
|
enqueueMessages(messages) {
|
||||||
if (this.lastEnqueueTime) {
|
if (this.lastEnqueueTime) {
|
||||||
|
@ -28,6 +28,9 @@ export default {
|
|||||||
blockUsers: 'Block users',
|
blockUsers: 'Block users',
|
||||||
blockMedalLevel: 'Block medal level lower than',
|
blockMedalLevel: 'Block medal level lower than',
|
||||||
|
|
||||||
|
advanced: 'Advanced',
|
||||||
|
autoTranslate: 'Auto translate messages to Japanese',
|
||||||
|
|
||||||
roomUrl: 'Room URL',
|
roomUrl: 'Room URL',
|
||||||
copy: 'Copy',
|
copy: 'Copy',
|
||||||
enterRoom: 'Enter room',
|
enterRoom: 'Enter room',
|
||||||
|
@ -28,6 +28,9 @@ export default {
|
|||||||
blockUsers: 'ブロックユーザー',
|
blockUsers: 'ブロックユーザー',
|
||||||
blockMedalLevel: 'ブロック勲章等級がx未満',
|
blockMedalLevel: 'ブロック勲章等級がx未満',
|
||||||
|
|
||||||
|
advanced: 'アドバンス',
|
||||||
|
autoTranslate: '自動翻訳コメントから日本語へ',
|
||||||
|
|
||||||
roomUrl: 'ルームのURL',
|
roomUrl: 'ルームのURL',
|
||||||
copy: 'コピー',
|
copy: 'コピー',
|
||||||
enterRoom: 'ルームに入る',
|
enterRoom: 'ルームに入る',
|
||||||
|
@ -28,6 +28,9 @@ export default {
|
|||||||
blockUsers: '屏蔽用户',
|
blockUsers: '屏蔽用户',
|
||||||
blockMedalLevel: '屏蔽当前直播间勋章等级低于',
|
blockMedalLevel: '屏蔽当前直播间勋章等级低于',
|
||||||
|
|
||||||
|
advanced: '高级',
|
||||||
|
autoTranslate: '自动翻译弹幕到日语',
|
||||||
|
|
||||||
roomUrl: '房间URL',
|
roomUrl: '房间URL',
|
||||||
copy: '复制',
|
copy: '复制',
|
||||||
enterRoom: '进入房间',
|
enterRoom: '进入房间',
|
||||||
|
@ -8,7 +8,7 @@ export function mergeConfig (config, defaultConfig) {
|
|||||||
|
|
||||||
export function toBool (val) {
|
export function toBool (val) {
|
||||||
if (typeof val === 'string') {
|
if (typeof val === 'string') {
|
||||||
return val !== 'false' && val !== ''
|
return ['false', 'no', 'off', '0', ''].indexOf(val.toLowerCase()) === -1
|
||||||
}
|
}
|
||||||
return !!val
|
return !!val
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,12 @@
|
|||||||
<el-slider v-model="form.blockMedalLevel" show-input :min="0" :max="20"></el-slider>
|
<el-slider v-model="form.blockMedalLevel" show-input :min="0" :max="20"></el-slider>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane :label="$t('home.advanced')">
|
||||||
|
<el-form-item :label="$t('home.autoTranslate')">
|
||||||
|
<el-switch v-model="form.autoTranslate"></el-switch>
|
||||||
|
</el-form-item>
|
||||||
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
|
||||||
<el-divider></el-divider>
|
<el-divider></el-divider>
|
||||||
|
@ -15,6 +15,7 @@ const COMMAND_ADD_GIFT = 3
|
|||||||
const COMMAND_ADD_MEMBER = 4
|
const COMMAND_ADD_MEMBER = 4
|
||||||
const COMMAND_ADD_SUPER_CHAT = 5
|
const COMMAND_ADD_SUPER_CHAT = 5
|
||||||
const COMMAND_DEL_SUPER_CHAT = 6
|
const COMMAND_DEL_SUPER_CHAT = 6
|
||||||
|
const COMMAND_UPDATE_TRANSLATION = 7
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Room',
|
name: 'Room',
|
||||||
@ -28,9 +29,7 @@ export default {
|
|||||||
websocket: null,
|
websocket: null,
|
||||||
retryCount: 0,
|
retryCount: 0,
|
||||||
isDestroying: false,
|
isDestroying: false,
|
||||||
heartbeatTimerId: null,
|
heartbeatTimerId: null
|
||||||
|
|
||||||
nextId: 0,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -42,8 +41,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.wsConnect()
|
|
||||||
this.updateConfig()
|
this.updateConfig()
|
||||||
|
this.wsConnect()
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.isDestroying = true
|
this.isDestroying = true
|
||||||
@ -70,6 +69,7 @@ export default {
|
|||||||
cfg.blockNewbie = toBool(cfg.blockNewbie)
|
cfg.blockNewbie = toBool(cfg.blockNewbie)
|
||||||
cfg.blockNotMobileVerified = toBool(cfg.blockNotMobileVerified)
|
cfg.blockNotMobileVerified = toBool(cfg.blockNotMobileVerified)
|
||||||
cfg.blockMedalLevel = toInt(cfg.blockMedalLevel, config.DEFAULT_CONFIG.blockMedalLevel)
|
cfg.blockMedalLevel = toInt(cfg.blockMedalLevel, config.DEFAULT_CONFIG.blockMedalLevel)
|
||||||
|
cfg.autoTranslate = toBool(cfg.autoTranslate)
|
||||||
|
|
||||||
this.config = cfg
|
this.config = cfg
|
||||||
},
|
},
|
||||||
@ -94,7 +94,10 @@ export default {
|
|||||||
this.websocket.send(JSON.stringify({
|
this.websocket.send(JSON.stringify({
|
||||||
cmd: COMMAND_JOIN_ROOM,
|
cmd: COMMAND_JOIN_ROOM,
|
||||||
data: {
|
data: {
|
||||||
roomId: parseInt(this.$route.params.roomId)
|
roomId: parseInt(this.$route.params.roomId),
|
||||||
|
config: {
|
||||||
|
autoTranslate: this.config.autoTranslate
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
@ -125,13 +128,15 @@ export default {
|
|||||||
authorLevel: data[7],
|
authorLevel: data[7],
|
||||||
isNewbie: !!data[8],
|
isNewbie: !!data[8],
|
||||||
isMobileVerified: !!data[9],
|
isMobileVerified: !!data[9],
|
||||||
medalLevel: data[10]
|
medalLevel: data[10],
|
||||||
|
id: data[11],
|
||||||
|
translation: data[12]
|
||||||
}
|
}
|
||||||
if (!this.config.showDanmaku || !this.filterTextMessage(data) || this.mergeSimilarText(data.content)) {
|
if (!this.config.showDanmaku || !this.filterTextMessage(data) || this.mergeSimilarText(data.content)) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
message = {
|
message = {
|
||||||
id: `text_${this.nextId++}`,
|
id: data.id,
|
||||||
type: constants.MESSAGE_TYPE_TEXT,
|
type: constants.MESSAGE_TYPE_TEXT,
|
||||||
avatarUrl: data.avatarUrl,
|
avatarUrl: data.avatarUrl,
|
||||||
time: new Date(data.timestamp * 1000),
|
time: new Date(data.timestamp * 1000),
|
||||||
@ -139,7 +144,8 @@ export default {
|
|||||||
authorType: data.authorType,
|
authorType: data.authorType,
|
||||||
content: data.content,
|
content: data.content,
|
||||||
privilegeType: data.privilegeType,
|
privilegeType: data.privilegeType,
|
||||||
repeated: 1
|
repeated: 1,
|
||||||
|
translation: data.translation
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case COMMAND_ADD_GIFT: {
|
case COMMAND_ADD_GIFT: {
|
||||||
@ -154,7 +160,7 @@ export default {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
message = {
|
message = {
|
||||||
id: `gift_${this.nextId++}`,
|
id: data.id,
|
||||||
type: constants.MESSAGE_TYPE_SUPER_CHAT,
|
type: constants.MESSAGE_TYPE_SUPER_CHAT,
|
||||||
avatarUrl: data.avatarUrl,
|
avatarUrl: data.avatarUrl,
|
||||||
authorName: data.authorName,
|
authorName: data.authorName,
|
||||||
@ -169,7 +175,7 @@ export default {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
message = {
|
message = {
|
||||||
id: `member_${this.nextId++}`,
|
id: data.id,
|
||||||
type: constants.MESSAGE_TYPE_MEMBER,
|
type: constants.MESSAGE_TYPE_MEMBER,
|
||||||
avatarUrl: data.avatarUrl,
|
avatarUrl: data.avatarUrl,
|
||||||
time: new Date(data.timestamp * 1000),
|
time: new Date(data.timestamp * 1000),
|
||||||
@ -186,7 +192,7 @@ export default {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
message = {
|
message = {
|
||||||
id: `sc_${data.id}`,
|
id: data.id,
|
||||||
type: constants.MESSAGE_TYPE_SUPER_CHAT,
|
type: constants.MESSAGE_TYPE_SUPER_CHAT,
|
||||||
avatarUrl: data.avatarUrl,
|
avatarUrl: data.avatarUrl,
|
||||||
authorName: data.authorName,
|
authorName: data.authorName,
|
||||||
@ -197,10 +203,19 @@ export default {
|
|||||||
break
|
break
|
||||||
case COMMAND_DEL_SUPER_CHAT:
|
case COMMAND_DEL_SUPER_CHAT:
|
||||||
for (let id of data.ids) {
|
for (let id of data.ids) {
|
||||||
id = `sc_${id}`
|
|
||||||
this.$refs.renderer.delMessage(id)
|
this.$refs.renderer.delMessage(id)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case COMMAND_UPDATE_TRANSLATION:
|
||||||
|
if (!this.config.autoTranslate) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
id: data[0],
|
||||||
|
translation: data[1]
|
||||||
|
}
|
||||||
|
this.$refs.renderer.updateMessage(data.id, {translation: data.translation})
|
||||||
|
break
|
||||||
}
|
}
|
||||||
if (message) {
|
if (message) {
|
||||||
this.$refs.renderer.addMessage(message)
|
this.$refs.renderer.addMessage(message)
|
||||||
|
@ -216,7 +216,8 @@ let textMessageTemplate = {
|
|||||||
authorType: constants.AUTHRO_TYPE_NORMAL,
|
authorType: constants.AUTHRO_TYPE_NORMAL,
|
||||||
content: '',
|
content: '',
|
||||||
privilegeType: 0,
|
privilegeType: 0,
|
||||||
repeated: 1
|
repeated: 1,
|
||||||
|
translation: ''
|
||||||
}
|
}
|
||||||
let legacyPaidMessageTemplate = {
|
let legacyPaidMessageTemplate = {
|
||||||
id: 0,
|
id: 0,
|
||||||
@ -236,20 +237,21 @@ let paidMessageTemplate = {
|
|||||||
authorName: '',
|
authorName: '',
|
||||||
price: 0,
|
price: 0,
|
||||||
time: time,
|
time: time,
|
||||||
content: ''
|
content: '',
|
||||||
|
translation: ''
|
||||||
}
|
}
|
||||||
let nextId = 0
|
let nextId = 0
|
||||||
const EXAMPLE_MESSAGES = [
|
const EXAMPLE_MESSAGES = [
|
||||||
{
|
{
|
||||||
...textMessageTemplate,
|
...textMessageTemplate,
|
||||||
id: nextId++,
|
id: (nextId++).toString(),
|
||||||
authorName: 'mob路人',
|
authorName: 'mob路人',
|
||||||
content: '8888888888',
|
content: '8888888888',
|
||||||
repeated: 12
|
repeated: 12
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...textMessageTemplate,
|
...textMessageTemplate,
|
||||||
id: nextId++,
|
id: (nextId++).toString(),
|
||||||
authorName: 'member舰长',
|
authorName: 'member舰长',
|
||||||
authorType: constants.AUTHRO_TYPE_MEMBER,
|
authorType: constants.AUTHRO_TYPE_MEMBER,
|
||||||
content: '草',
|
content: '草',
|
||||||
@ -258,34 +260,34 @@ const EXAMPLE_MESSAGES = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
...textMessageTemplate,
|
...textMessageTemplate,
|
||||||
id: nextId++,
|
id: (nextId++).toString(),
|
||||||
authorName: 'admin房管',
|
authorName: 'admin房管',
|
||||||
authorType: constants.AUTHRO_TYPE_ADMIN,
|
authorType: constants.AUTHRO_TYPE_ADMIN,
|
||||||
content: 'kksk'
|
content: 'kksk'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...legacyPaidMessageTemplate,
|
...legacyPaidMessageTemplate,
|
||||||
id: nextId++,
|
id: (nextId++).toString(),
|
||||||
authorName: '少年Pi',
|
authorName: '少年Pi',
|
||||||
content: 'Welcome 少年Pi!'
|
content: 'Welcome 少年Pi!'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...paidMessageTemplate,
|
...paidMessageTemplate,
|
||||||
id: nextId++,
|
id: (nextId++).toString(),
|
||||||
authorName: '无火的残渣',
|
authorName: '无火的残渣',
|
||||||
price: 66600,
|
price: 66600,
|
||||||
content: 'Sent 小电视飞船x100'
|
content: 'Sent 小电视飞船x100'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...textMessageTemplate,
|
...textMessageTemplate,
|
||||||
id: nextId++,
|
id: (nextId++).toString(),
|
||||||
authorName: 'streamer主播',
|
authorName: 'streamer主播',
|
||||||
authorType: constants.AUTHRO_TYPE_OWNER,
|
authorType: constants.AUTHRO_TYPE_OWNER,
|
||||||
content: '老板大气,老板身体健康'
|
content: '老板大气,老板身体健康'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...paidMessageTemplate,
|
...paidMessageTemplate,
|
||||||
id: nextId++,
|
id: (nextId++).toString(),
|
||||||
authorName: '夏色祭保護協会会長',
|
authorName: '夏色祭保護協会会長',
|
||||||
price: 30,
|
price: 30,
|
||||||
content: '言いたいことがあるんだよ!'
|
content: '言いたいことがあるんだよ!'
|
||||||
|
Loading…
Reference in New Issue
Block a user