Compare commits
70 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60e6b5ec99 | ||
|
|
695a111c9a | ||
|
|
0160549dbe | ||
|
|
3fd0d9ab28 | ||
|
|
b1061ae103 | ||
|
|
6e737ce33f | ||
|
|
10db6b9ae2 | ||
|
|
1514bfed5b | ||
|
|
dc264d40f0 | ||
|
|
104e647f61 | ||
|
|
d5d7fdde6d | ||
|
|
23aa88d564 | ||
|
|
ac1bda0392 | ||
|
|
5cdb786966 | ||
|
|
2b5ca2e219 | ||
|
|
fc77b246c2 | ||
|
|
083832cb3e | ||
|
|
804db2fec1 | ||
|
|
769964d5db | ||
|
|
aba0efca34 | ||
|
|
9ff18fe8b5 | ||
|
|
ee78cbf90e | ||
|
|
ffc3c9d8a0 | ||
|
|
a69b6b6406 | ||
|
|
1252402e5e | ||
|
|
53be587189 | ||
|
|
d3cc8b0c77 | ||
|
|
4f4d117ce0 | ||
|
|
0d1e38346e | ||
|
|
273dc0f43f | ||
|
|
26f19b6630 | ||
|
|
f2d2528afe | ||
|
|
d0fd997415 | ||
|
|
7086acb001 | ||
|
|
2914a19288 | ||
|
|
64cca50982 | ||
|
|
702bd56869 | ||
|
|
1eedee1119 | ||
|
|
b95ef95cfd | ||
|
|
01a3ec367e | ||
|
|
2d86449c24 | ||
|
|
290b09f49a | ||
|
|
4d5a38bf7d | ||
|
|
b8cb5e96e9 | ||
|
|
3722bf25ad | ||
|
|
c3bcbacbfa | ||
|
|
b370ce931a | ||
|
|
5cd3776d8e | ||
|
|
f5ef539eac | ||
|
|
36136af08d | ||
|
|
82f7a84793 | ||
|
|
46baa83458 | ||
|
|
833c366707 | ||
|
|
0ef11439ff | ||
|
|
ec27b1bbcc | ||
|
|
ef4c0daede | ||
|
|
fe598369b7 | ||
|
|
e0df7bc3d6 | ||
|
|
05180c3acb | ||
|
|
45173e5162 | ||
|
|
846e830f9c | ||
|
|
da242fc853 | ||
|
|
1cbaed4127 | ||
|
|
920e6fff96 | ||
|
|
22e8f88533 | ||
|
|
874c9e84e0 | ||
|
|
ce40899231 | ||
|
|
8dbba50548 | ||
|
|
82bfb69a0a | ||
|
|
51a5199718 |
16
.dockerignore
Normal file
@@ -0,0 +1,16 @@
|
||||
# compiled output
|
||||
**/__pycache__/
|
||||
build/
|
||||
**/dist/
|
||||
|
||||
# dependencies
|
||||
**/node_modules/
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
|
||||
# misc
|
||||
**/.git*
|
||||
*.spec
|
||||
screenshots/
|
||||
README.md
|
||||
33
Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
# 运行时
|
||||
FROM python:3.6.8-slim-stretch
|
||||
RUN mv /etc/apt/sources.list /etc/apt/sources.list.bak \
|
||||
&& echo "deb http://mirrors.tuna.tsinghua.edu.cn/debian/ stretch main contrib non-free">>/etc/apt/sources.list \
|
||||
&& echo "deb http://mirrors.tuna.tsinghua.edu.cn/debian/ stretch-updates main contrib non-free">>/etc/apt/sources.list \
|
||||
&& echo "deb http://mirrors.tuna.tsinghua.edu.cn/debian/ stretch-backports main contrib non-free">>/etc/apt/sources.list \
|
||||
&& echo "deb http://mirrors.tuna.tsinghua.edu.cn/debian-security stretch/updates main contrib non-free">>/etc/apt/sources.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y wget tar xz-utils
|
||||
RUN wget https://nodejs.org/dist/v10.16.0/node-v10.16.0-linux-x64.tar.xz \
|
||||
&& tar -xvf node-v10.16.0-linux-x64.tar.xz \
|
||||
&& rm node-v10.16.0-linux-x64.tar.xz \
|
||||
&& ln -s /node-v10.16.0-linux-x64/bin/node /usr/local/bin/node \
|
||||
&& ln -s /node-v10.16.0-linux-x64/bin/npm /usr/local/bin/npm
|
||||
|
||||
# 后端依赖
|
||||
COPY requirements.txt /blivechat/
|
||||
RUN pip3 install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple -r /blivechat/requirements.txt
|
||||
|
||||
# 前端依赖
|
||||
WORKDIR /blivechat/frontend
|
||||
COPY frontend/package*.json ./
|
||||
RUN npm i --registry=https://registry.npm.taobao.org
|
||||
|
||||
# 编译
|
||||
COPY . /blivechat
|
||||
RUN npm run build
|
||||
|
||||
# 运行
|
||||
WORKDIR /blivechat
|
||||
EXPOSE 12450
|
||||
ENTRYPOINT ["python3", "main.py"]
|
||||
CMD ["--host", "0.0.0.0", "--port", "12450"]
|
||||
39
README.md
@@ -1,41 +1,52 @@
|
||||
# blivechat
|
||||
用于OBS的仿YouTube风格的bilibili直播聊天层
|
||||
|
||||
最近喜欢看VTuber,想为此写些程序,于是有了这个东西。~~写到一半发现有类似项目了,而且还是纯前端实现的:[bilibili-live-chat](https://github.com/Tsuk1ko/bilibili-live-chat)。~~ 本项目就当做练手吧,而且对YouTube的模仿程度更高(指样式和Super Chat)
|
||||
最近喜欢看VTuber,想为此写些程序,于是有了这个东西。~~写到一半发现有类似项目了:[bilibili-live-chat](https://github.com/Tsuk1ko/bilibili-live-chat)、[BiliChat](https://github.com/3Shain/BiliChat)。~~ 本项目就当做练手吧,而且对YouTube的模仿程度更高
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 特性
|
||||
* 兼容YouTube直播chat的样式,生成样式参考[https://chatv2.septapus.com/](https://chatv2.septapus.com/)
|
||||
* 兼容YouTube直播chat的样式
|
||||
* 金瓜子礼物模仿Super Chat显示
|
||||
* 高亮舰队、房管、主播的用户名
|
||||
* 支持屏蔽弹幕、限制最大速度等设置
|
||||
* 自带样式生成器
|
||||
|
||||
## 使用方法
|
||||
### 发布版
|
||||
1. 下载[发布版](https://github.com/xfgryujk/blivechat/releases)(仅提供Windows)
|
||||
1. 下载[发布版](https://github.com/xfgryujk/blivechat/releases)(仅提供x64 Windows版)
|
||||
2. 双击`blivechat.exe`运行服务器,或者用命令行可以指定host和端口号:
|
||||
```bat
|
||||
blivechat.exe --host 127.0.0.1 --port 80
|
||||
blivechat.exe --host 127.0.0.1 --port 12450
|
||||
```
|
||||
3. 用浏览器打开[http://localhost](http://localhost),输入房间号,进入,复制房间URL(其实就是http://localhost/room/<房间ID>)
|
||||
4. (可选)在[https://chatv2.septapus.com/](https://chatv2.septapus.com/)生成样式,复制CSS
|
||||
5. 在OBS中添加浏览器源,输入URL和自定义CSS
|
||||
3. 用浏览器打开[http://localhost:12450](http://localhost:12450),输入房间ID,保存配置,复制房间URL
|
||||
4. 用样式生成器生成样式,复制CSS
|
||||
5. 在OBS中添加浏览器源,输入URL和自定义CSS,或者可以在首页的样式设置里输入CSS
|
||||
|
||||
### 源代码版
|
||||
1. 编译前端(需要安装NPM):
|
||||
1. 编译前端(需要安装Node.js和npm):
|
||||
```sh
|
||||
cd frontend
|
||||
npm i
|
||||
npm run build
|
||||
```
|
||||
2. 运行服务器:
|
||||
2. 运行服务器(需要Python3.6以上版本):
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
python3 main.py
|
||||
```
|
||||
或者可以指定host和端口号:
|
||||
```sh
|
||||
python3 main.py --host 127.0.0.1 --port 80
|
||||
python3 main.py --host 127.0.0.1 --port 12450
|
||||
```
|
||||
3. 用浏览器打开[http://localhost](http://localhost),输入房间号,进入,复制房间URL(其实就是http://localhost/room/<房间ID>)
|
||||
4. (可选)在[https://chatv2.septapus.com/](https://chatv2.septapus.com/)生成样式,复制CSS
|
||||
5. 在OBS中添加浏览器源,输入URL和自定义CSS
|
||||
3. 用浏览器打开[http://localhost:12450](http://localhost:12450),输入房间ID,保存配置,复制房间URL
|
||||
4. 用样式生成器生成样式,复制CSS
|
||||
5. 在OBS中添加浏览器源,输入URL和自定义CSS,或者可以在首页的样式设置里输入CSS
|
||||
|
||||
### Docker
|
||||
1. ```sh
|
||||
docker run -d -p 12450:12450 xfgryujk/blivechat:latest
|
||||
```
|
||||
2. 以下略
|
||||
|
||||
2
blivedm
238
chat.py
@@ -1,238 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import asyncio
|
||||
import enum
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import *
|
||||
|
||||
import aiohttp
|
||||
import tornado.websocket
|
||||
|
||||
import blivedm.blivedm as blivedm
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(enum.IntEnum):
|
||||
JOIN_ROOM = 0
|
||||
ADD_TEXT = 1
|
||||
ADD_GIFT = 2
|
||||
ADD_VIP = 3
|
||||
|
||||
|
||||
_http_session = aiohttp.ClientSession()
|
||||
_avatar_url_cache: Dict[int, str] = {}
|
||||
|
||||
|
||||
async def get_avatar_url(user_id):
|
||||
if user_id in _avatar_url_cache:
|
||||
return _avatar_url_cache[user_id]
|
||||
async with _http_session.get('https://api.bilibili.com/x/space/acc/info',
|
||||
params={'mid': user_id}) as r:
|
||||
data = await r.json()
|
||||
url = data['data']['face']
|
||||
if not url.endswith('noface.gif'):
|
||||
url += '@24w_24h.webp'
|
||||
_avatar_url_cache[user_id] = url
|
||||
|
||||
if len(_avatar_url_cache) > 10000:
|
||||
for _, key in zip(range(100), _avatar_url_cache):
|
||||
del _avatar_url_cache[key]
|
||||
|
||||
return url
|
||||
|
||||
|
||||
class Room(blivedm.BLiveClient):
|
||||
_COMMAND_HANDLERS = blivedm.BLiveClient._COMMAND_HANDLERS.copy()
|
||||
|
||||
def __init__(self, room_id):
|
||||
super().__init__(room_id, session=_http_session)
|
||||
self.future = None
|
||||
self.clients: List['ChatHandler'] = []
|
||||
self.owner_id = None
|
||||
|
||||
def start(self):
|
||||
self.future = self.run()
|
||||
|
||||
def stop(self):
|
||||
if self.future is not None:
|
||||
self.future.cancel()
|
||||
asyncio.ensure_future(self.close())
|
||||
|
||||
async def _get_room_id(self):
|
||||
"""重载用来获取主播UID"""
|
||||
async with self._session.get(blivedm.ROOM_INIT_URL,
|
||||
params={'id': self._short_id},
|
||||
ssl=self._ssl) as res:
|
||||
if res.status == 200:
|
||||
data = await res.json()
|
||||
if data['code'] == 0:
|
||||
self._room_id = data['data']['room_id']
|
||||
self.owner_id = data['data']['uid']
|
||||
else:
|
||||
raise ConnectionAbortedError('获取房间ID失败:' + data['msg'])
|
||||
else:
|
||||
raise ConnectionAbortedError('获取房间ID失败:' + res.reason)
|
||||
|
||||
def send_message(self, cmd, data):
|
||||
body = json.dumps({'cmd': cmd, 'data': data})
|
||||
for client in self.clients:
|
||||
client.write_message(body)
|
||||
|
||||
async def __my_on_get_danmaku(self, command):
|
||||
if command['info'][2][0] == self.owner_id:
|
||||
author_type = 3 # 主播
|
||||
elif command['info'][2][2] != 0:
|
||||
author_type = 2 # 房管
|
||||
elif command['info'][7] != 0: # 1总督,2提督,3舰长
|
||||
author_type = 1 # 舰队
|
||||
else:
|
||||
author_type = 0
|
||||
self.send_message(Command.ADD_TEXT, {
|
||||
'avatarUrl': await get_avatar_url(command['info'][2][0]),
|
||||
'timestamp': command['info'][0][4],
|
||||
'authorName': command['info'][2][1],
|
||||
'authorType': author_type,
|
||||
'content': command['info'][1]
|
||||
})
|
||||
|
||||
_COMMAND_HANDLERS['DANMU_MSG'] = __my_on_get_danmaku
|
||||
|
||||
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:
|
||||
def __init__(self):
|
||||
self._rooms: Dict[int, Room] = {}
|
||||
|
||||
def add_client(self, room_id, client):
|
||||
if room_id in self._rooms:
|
||||
room = self._rooms[room_id]
|
||||
else:
|
||||
logger.info('创建房间%d', room_id)
|
||||
room = Room(room_id)
|
||||
self._rooms[room_id] = room
|
||||
room.start()
|
||||
room.clients.append(client)
|
||||
|
||||
if client.application.settings['debug']:
|
||||
self.__send_test_message(room)
|
||||
|
||||
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)
|
||||
if not room.clients:
|
||||
logger.info('移除房间%d', room_id)
|
||||
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',
|
||||
'authorType': 0,
|
||||
'content': '我能吞下玻璃而不伤身体'
|
||||
})
|
||||
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."
|
||||
})
|
||||
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()
|
||||
|
||||
|
||||
# noinspection PyAbstractClass
|
||||
class ChatHandler(tornado.websocket.WebSocketHandler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.room_id = None
|
||||
|
||||
def open(self):
|
||||
logger.info('Websocket连接 %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:
|
||||
self.room_id = int(body['data']['roomId'])
|
||||
logger.info('客户端%s加入房间%d', self.request.remote_ip, self.room_id)
|
||||
room_manager.add_client(self.room_id, self)
|
||||
else:
|
||||
logger.warning('未知的命令: %s data: %s', body['cmd'], body['data'])
|
||||
|
||||
def on_close(self):
|
||||
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)
|
||||
|
||||
# 跨域测试用
|
||||
def check_origin(self, origin):
|
||||
if self.application.settings['debug']:
|
||||
return True
|
||||
return super().check_origin(origin)
|
||||
24493
frontend/package-lock.json
generated
@@ -8,8 +8,13 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.19.0",
|
||||
"core-js": "^2.6.5",
|
||||
"downloadjs": "^1.4.7",
|
||||
"element-ui": "^2.9.1",
|
||||
"lodash": "^4.17.13",
|
||||
"vue": "^2.6.10",
|
||||
"vue-i18n": "^8.11.2",
|
||||
"vue-router": "^3.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
BIN
frontend/public/static/img/icons/guard-level-1.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
frontend/public/static/img/icons/guard-level-2.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
frontend/public/static/img/icons/guard-level-3.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
58
frontend/src/api/config.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import axios from 'axios'
|
||||
|
||||
import {mergeConfig} from '@/utils'
|
||||
|
||||
export const DEFAULT_CONFIG = {
|
||||
minGiftPrice: 7, // $1
|
||||
mergeSimilarDanmaku: true,
|
||||
showDanmaku: true,
|
||||
showGift: true,
|
||||
maxSpeed: 4,
|
||||
maxNumber: 60,
|
||||
|
||||
blockGiftDanmaku: true,
|
||||
blockLevel: 0,
|
||||
blockNewbie: true,
|
||||
blockNotMobileVerified: true,
|
||||
blockKeywords: '',
|
||||
blockUsers: '',
|
||||
blockMedalLevel: 0,
|
||||
|
||||
css: ''
|
||||
}
|
||||
|
||||
export function setLocalConfig (config) {
|
||||
config = mergeConfig(config, DEFAULT_CONFIG)
|
||||
window.localStorage.config = JSON.stringify(config)
|
||||
}
|
||||
|
||||
export function getLocalConfig () {
|
||||
if (!window.localStorage.config) {
|
||||
return DEFAULT_CONFIG
|
||||
}
|
||||
return mergeConfig(JSON.parse(window.localStorage.config), DEFAULT_CONFIG)
|
||||
}
|
||||
|
||||
export async function createRemoteConfig (config) {
|
||||
config = mergeConfig(config, DEFAULT_CONFIG)
|
||||
return (await axios.post('/config', config)).data
|
||||
}
|
||||
|
||||
export async function setRemoteConfig (id, config) {
|
||||
config = mergeConfig(config, DEFAULT_CONFIG)
|
||||
return (await axios.put(`/config/${id}`, config)).data
|
||||
}
|
||||
|
||||
export async function getRemoteConfig (id) {
|
||||
let config = (await axios.get(`/config/${id}`)).data
|
||||
return mergeConfig(config, DEFAULT_CONFIG)
|
||||
}
|
||||
|
||||
export default {
|
||||
DEFAULT_CONFIG,
|
||||
setLocalConfig,
|
||||
getLocalConfig,
|
||||
createRemoteConfig,
|
||||
setRemoteConfig,
|
||||
getRemoteConfig
|
||||
}
|
||||
BIN
frontend/src/assets/img/logo.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,426 +0,0 @@
|
||||
yt-live-chat-text-message-renderer {
|
||||
position: relative;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-ms-flex-direction: row;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-ms-flex-align: start;
|
||||
-webkit-align-items: flex-start;
|
||||
align-items: flex-start;
|
||||
padding: 4px 24px;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer #content {
|
||||
-ms-align-self: center;
|
||||
-webkit-align-self: center;
|
||||
align-self: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer #timestamp {
|
||||
display: none;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer #author-name {
|
||||
margin-right: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer #author-photo {
|
||||
display: inline-block;
|
||||
background-color: transparent;
|
||||
-ms-flex: none;
|
||||
-webkit-flex: none;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer #author-badges {
|
||||
vertical-align: text-bottom;
|
||||
display: -ms-inline-flexbox;
|
||||
display: -webkit-inline-flex;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer #message {
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
yt-live-chat-author-badge-renderer {
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: currentColor;
|
||||
margin-right: 4px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin: 8px 24px;
|
||||
padding: 8px 16px;
|
||||
background-color: #0f9d58;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
min-height: 40px;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-ms-flex-direction: row;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-ms-flex-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer #author-photo {
|
||||
background-color: transparent;
|
||||
margin-right: 16px;
|
||||
overflow: hidden;
|
||||
-ms-flex: none;
|
||||
-webkit-flex: none;
|
||||
flex: none;
|
||||
-ms-align-self: flex-start;
|
||||
-webkit-align-self: flex-start;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer #content {
|
||||
-ms-flex: 1 1 0.000000001px;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
-webkit-flex-basis: 0.000000001px;
|
||||
flex-basis: 0.000000001px;
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer #event-text {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer #detail-text {
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer {
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
margin: 8px 24px;
|
||||
font-size: 15px;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer #header {
|
||||
position: relative;
|
||||
font-weight: 500;
|
||||
padding: 8px 16px;
|
||||
min-height: 20px;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-ms-flex-direction: row;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-ms-flex-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer #header-content {
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-ms-flex-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
justify-content: space-between;
|
||||
-ms-flex: 1 1 0.000000001px;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
-webkit-flex-basis: 0.000000001px;
|
||||
flex-basis: 0.000000001px;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer #author-photo {
|
||||
background-color: transparent;
|
||||
-ms-flex: none;
|
||||
-webkit-flex: none;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer #author-name {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer #content {
|
||||
padding: 4px 16px 8px 16px;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
yt-live-chat-ticker-paid-message-item-renderer {
|
||||
display: inline-block;
|
||||
height: 24px;
|
||||
border-radius: 200px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
yt-live-chat-ticker-paid-message-item-renderer #content {
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
-ms-flex-direction: row;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-ms-flex-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
yt-live-chat-ticker-paid-message-item-renderer #fake-avatar {
|
||||
display: inline-block;
|
||||
background-color: red;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-right: 4px;
|
||||
border-radius: 24px;
|
||||
|
||||
}
|
||||
|
||||
/* 以下为自动生成:https://chatv2.septapus.com/ */
|
||||
|
||||
/* @import url("https://fonts.googleapis.com/css?family=Changa%20One"); */
|
||||
/* @import url("https://fonts.googleapis.com/css?family=Imprima"); */
|
||||
@import url("https://fonts.lug.ustc.edu.cn/css?family=Changa%20One");
|
||||
@import url("https://fonts.lug.ustc.edu.cn/css?family=Imprima");
|
||||
|
||||
/* Background colors*/
|
||||
body {
|
||||
overflow: hidden;
|
||||
background-color: rgba(0,0,0,0);
|
||||
}
|
||||
/* Transparent background. */
|
||||
yt-live-chat-renderer {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
yt-live-chat-text-message-renderer,
|
||||
yt-live-chat-text-message-renderer[is-highlighted] {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer[author-type="owner"],
|
||||
yt-live-chat-text-message-renderer[author-type="owner"][is-highlighted] {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer[author-type="moderator"],
|
||||
yt-live-chat-text-message-renderer[author-type="moderator"][is-highlighted] {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer[author-type="member"],
|
||||
yt-live-chat-text-message-renderer[author-type="member"][is-highlighted] {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
|
||||
yt-live-chat-author-chip #author-name {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
/* Outlines */
|
||||
yt-live-chat-renderer * {
|
||||
text-shadow: -2px -2px #000000,-2px -1px #000000,-2px 0px #000000,-2px 1px #000000,-2px 2px #000000,-1px -2px #000000,-1px -1px #000000,-1px 0px #000000,-1px 1px #000000,-1px 2px #000000,0px -2px #000000,0px -1px #000000,0px 0px #000000,0px 1px #000000,0px 2px #000000,1px -2px #000000,1px -1px #000000,1px 0px #000000,1px 1px #000000,1px 2px #000000,2px -2px #000000,2px -1px #000000,2px 0px #000000,2px 1px #000000,2px 2px #000000;
|
||||
font-family: "Imprima";
|
||||
font-size: 18px !important;
|
||||
line-height: 18px !important;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer #content,
|
||||
yt-live-chat-legacy-paid-message-renderer #content {
|
||||
overflow: initial !important;
|
||||
}
|
||||
|
||||
/* Hide scrollbar. */
|
||||
yt-live-chat-item-list-renderer #items{
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
yt-live-chat-item-list-renderer #item-scroller{
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* Hide header and input. */
|
||||
yt-live-chat-header-renderer,
|
||||
yt-live-chat-message-input-renderer {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Reduce side padding. */
|
||||
yt-live-chat-text-message-renderer,
|
||||
yt-live-chat-legacy-paid-message-renderer {
|
||||
padding-left: 4px !important;
|
||||
padding-right: 4px !important;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer #header {
|
||||
padding-left: 4px !important;
|
||||
padding-right: 4px !important;
|
||||
}
|
||||
|
||||
/* Avatars. */
|
||||
yt-live-chat-text-message-renderer #author-photo,
|
||||
yt-live-chat-paid-message-renderer #author-photo,
|
||||
yt-live-chat-legacy-paid-message-renderer #author-photo {
|
||||
|
||||
width: 24px !important;
|
||||
height: 24px !important;
|
||||
border-radius: 24px !important;
|
||||
margin-right: 6px !important;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
/* Hide badges. */
|
||||
yt-live-chat-text-message-renderer #author-badges {
|
||||
display: none !important;
|
||||
vertical-align: text-top !important;
|
||||
}
|
||||
|
||||
/* Timestamps. */
|
||||
yt-live-chat-text-message-renderer #timestamp {
|
||||
|
||||
color: #999999 !important;
|
||||
font-family: "Imprima";
|
||||
font-size: 16px !important;
|
||||
line-height: 16px !important;
|
||||
}
|
||||
|
||||
/* Badges. */
|
||||
yt-live-chat-text-message-renderer #author-name[type="owner"],
|
||||
yt-live-chat-text-message-renderer yt-live-chat-author-badge-renderer[type="owner"] {
|
||||
color: #ffd600 !important;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer #author-name[type="moderator"],
|
||||
yt-live-chat-text-message-renderer yt-live-chat-author-badge-renderer[type="moderator"] {
|
||||
color: #5e84f1 !important;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer #author-name[type="member"],
|
||||
yt-live-chat-text-message-renderer yt-live-chat-author-badge-renderer[type="member"] {
|
||||
color: #0f9d58 !important;
|
||||
}
|
||||
|
||||
/* Channel names. */
|
||||
yt-live-chat-text-message-renderer #author-name {
|
||||
color: #cccccc !important;
|
||||
font-family: "Changa One";
|
||||
font-size: 20px !important;
|
||||
line-height: 20px !important;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer #author-name::after {
|
||||
content: ":";
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
/* Messages. */
|
||||
yt-live-chat-text-message-renderer #message,
|
||||
yt-live-chat-text-message-renderer #message * {
|
||||
color: #ffffff !important;
|
||||
font-family: "Imprima";
|
||||
font-size: 18px !important;
|
||||
line-height: 18px !important;
|
||||
}
|
||||
|
||||
|
||||
/* SuperChat/Fan Funding Messages. */
|
||||
yt-live-chat-paid-message-renderer #author-name,
|
||||
yt-live-chat-paid-message-renderer #author-name *,
|
||||
yt-live-chat-legacy-paid-message-renderer #event-text,
|
||||
yt-live-chat-legacy-paid-message-renderer #event-text * {
|
||||
color: #ffffff !important;
|
||||
font-family: "Changa One";
|
||||
font-size: 20px !important;
|
||||
line-height: 20px !important;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer #purchase-amount,
|
||||
yt-live-chat-paid-message-renderer #purchase-amount *,
|
||||
yt-live-chat-legacy-paid-message-renderer #detail-text,
|
||||
yt-live-chat-legacy-paid-message-renderer #detail-text * {
|
||||
color: #ffffff !important;
|
||||
font-family: "Imprima";
|
||||
font-size: 18px !important;
|
||||
line-height: 18px !important;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer #content,
|
||||
yt-live-chat-paid-message-renderer #content * {
|
||||
color: #ffffff !important;
|
||||
font-family: "Imprima";
|
||||
font-size: 18px !important;
|
||||
line-height: 18px !important;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer {
|
||||
margin: 4px 0 !important;
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer {
|
||||
background-color: #0f9d58 !important;
|
||||
margin: 4px 0 !important;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer a,
|
||||
yt-live-chat-legacy-paid-message-renderer a {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer[is-deleted],
|
||||
yt-live-chat-legacy-paid-message-renderer[is-deleted] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
yt-live-chat-ticker-renderer {
|
||||
background-color: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
yt-live-chat-ticker-renderer {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
yt-live-chat-ticker-paid-message-item-renderer,
|
||||
yt-live-chat-ticker-paid-message-item-renderer *,
|
||||
yt-live-chat-ticker-sponsor-item-renderer,
|
||||
yt-live-chat-ticker-sponsor-item-renderer * {
|
||||
color: #ffffff !important;
|
||||
font-family: "Imprima";
|
||||
}
|
||||
|
||||
yt-live-chat-mode-change-message-renderer,
|
||||
yt-live-chat-viewer-engagement-message-renderer,
|
||||
yt-live-chat-restricted-participation-renderer {
|
||||
display: none !important;
|
||||
}
|
||||
121
frontend/src/components/ChatRenderer/AuthorBadge.vue
Normal file
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<yt-live-chat-author-badge-renderer :type="authorTypeText" v-if="isAdmin || privilegeType > 0">
|
||||
<el-tooltip :content="readableAuthorTypeText" placement="top">
|
||||
<div id="image" class="style-scope yt-live-chat-author-badge-renderer">
|
||||
<yt-icon v-if="isAdmin" class="style-scope yt-live-chat-author-badge-renderer">
|
||||
<svg viewBox="0 0 16 16" class="style-scope yt-icon" preserveAspectRatio="xMidYMid meet" focusable="false"
|
||||
style="pointer-events: none; display: block; width: 100%; height: 100%;"
|
||||
>
|
||||
<g class="style-scope yt-icon">
|
||||
<path class="style-scope yt-icon"
|
||||
d="M9.64589146,7.05569719 C9.83346524,6.562372 9.93617022,6.02722257 9.93617022,5.46808511 C9.93617022,3.00042984 7.93574038,1 5.46808511,1 C4.90894765,1 4.37379823,1.10270499 3.88047304,1.29027875 L6.95744681,4.36725249 L4.36725255,6.95744681 L1.29027875,3.88047305 C1.10270498,4.37379824 1,4.90894766 1,5.46808511 C1,7.93574038 3.00042984,9.93617022 5.46808511,9.93617022 C6.02722256,9.93617022 6.56237198,9.83346524 7.05569716,9.64589147 L12.4098057,15 L15,12.4098057 L9.64589146,7.05569719 Z"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
</yt-icon>
|
||||
<img v-else :src="`/static/img/icons/guard-level-${privilegeType}.png`"
|
||||
class="style-scope yt-live-chat-author-badge-renderer" :alt="readableAuthorTypeText">
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</yt-live-chat-author-badge-renderer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as constants from './constants'
|
||||
|
||||
export default {
|
||||
name: 'AuthorBadge',
|
||||
props: {
|
||||
isAdmin: Boolean,
|
||||
privilegeType: Number
|
||||
},
|
||||
computed: {
|
||||
authorTypeText() {
|
||||
if (this.isAdmin) {
|
||||
return 'moderator'
|
||||
}
|
||||
return this.privilegeType > 0 ? 'member' : ''
|
||||
},
|
||||
readableAuthorTypeText() {
|
||||
if (this.isAdmin) {
|
||||
return '管理员'
|
||||
}
|
||||
return constants.GUARD_LEVEL_TO_TEXT[this.privilegeType]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- yt-live-chat-author-badge-renderer -->
|
||||
<style>
|
||||
canvas.yt-live-chat-author-badge-renderer, caption.yt-live-chat-author-badge-renderer, center.yt-live-chat-author-badge-renderer, cite.yt-live-chat-author-badge-renderer, code.yt-live-chat-author-badge-renderer, dd.yt-live-chat-author-badge-renderer, del.yt-live-chat-author-badge-renderer, dfn.yt-live-chat-author-badge-renderer, div.yt-live-chat-author-badge-renderer, dl.yt-live-chat-author-badge-renderer, dt.yt-live-chat-author-badge-renderer, em.yt-live-chat-author-badge-renderer, embed.yt-live-chat-author-badge-renderer, fieldset.yt-live-chat-author-badge-renderer, font.yt-live-chat-author-badge-renderer, form.yt-live-chat-author-badge-renderer, h1.yt-live-chat-author-badge-renderer, h2.yt-live-chat-author-badge-renderer, h3.yt-live-chat-author-badge-renderer, h4.yt-live-chat-author-badge-renderer, h5.yt-live-chat-author-badge-renderer, h6.yt-live-chat-author-badge-renderer, hr.yt-live-chat-author-badge-renderer, i.yt-live-chat-author-badge-renderer, iframe.yt-live-chat-author-badge-renderer, img.yt-live-chat-author-badge-renderer, ins.yt-live-chat-author-badge-renderer, kbd.yt-live-chat-author-badge-renderer, label.yt-live-chat-author-badge-renderer, legend.yt-live-chat-author-badge-renderer, li.yt-live-chat-author-badge-renderer, menu.yt-live-chat-author-badge-renderer, object.yt-live-chat-author-badge-renderer, ol.yt-live-chat-author-badge-renderer, p.yt-live-chat-author-badge-renderer, pre.yt-live-chat-author-badge-renderer, q.yt-live-chat-author-badge-renderer, s.yt-live-chat-author-badge-renderer, samp.yt-live-chat-author-badge-renderer, small.yt-live-chat-author-badge-renderer, span.yt-live-chat-author-badge-renderer, strike.yt-live-chat-author-badge-renderer, strong.yt-live-chat-author-badge-renderer, sub.yt-live-chat-author-badge-renderer, sup.yt-live-chat-author-badge-renderer, table.yt-live-chat-author-badge-renderer, tbody.yt-live-chat-author-badge-renderer, td.yt-live-chat-author-badge-renderer, tfoot.yt-live-chat-author-badge-renderer, th.yt-live-chat-author-badge-renderer, thead.yt-live-chat-author-badge-renderer, tr.yt-live-chat-author-badge-renderer, tt.yt-live-chat-author-badge-renderer, u.yt-live-chat-author-badge-renderer, ul.yt-live-chat-author-badge-renderer, var.yt-live-chat-author-badge-renderer {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.yt-live-chat-author-badge-renderer[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
yt-live-chat-author-badge-renderer {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
yt-live-chat-author-badge-renderer[type='moderator'] {
|
||||
color: var(--yt-live-chat-moderator-color, #5e84f1);
|
||||
}
|
||||
|
||||
yt-live-chat-author-badge-renderer[type='owner'] {
|
||||
color: var(--yt-live-chat-owner-color, #ffd600);
|
||||
}
|
||||
|
||||
yt-live-chat-author-badge-renderer[type='member'] {
|
||||
color: var(--yt-live-chat-sponsor-color, #107516);
|
||||
}
|
||||
|
||||
yt-live-chat-author-badge-renderer[type='verified'] {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
img.yt-live-chat-author-badge-renderer, yt-icon.yt-live-chat-author-badge-renderer {
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- yt-icon -->
|
||||
<style>
|
||||
canvas.yt-icon, caption.yt-icon, center.yt-icon, cite.yt-icon, code.yt-icon, dd.yt-icon, del.yt-icon, dfn.yt-icon, div.yt-icon, dl.yt-icon, dt.yt-icon, em.yt-icon, embed.yt-icon, fieldset.yt-icon, font.yt-icon, form.yt-icon, h1.yt-icon, h2.yt-icon, h3.yt-icon, h4.yt-icon, h5.yt-icon, h6.yt-icon, hr.yt-icon, i.yt-icon, iframe.yt-icon, img.yt-icon, ins.yt-icon, kbd.yt-icon, label.yt-icon, legend.yt-icon, li.yt-icon, menu.yt-icon, object.yt-icon, ol.yt-icon, p.yt-icon, pre.yt-icon, q.yt-icon, s.yt-icon, samp.yt-icon, small.yt-icon, span.yt-icon, strike.yt-icon, strong.yt-icon, sub.yt-icon, sup.yt-icon, table.yt-icon, tbody.yt-icon, td.yt-icon, tfoot.yt-icon, th.yt-icon, thead.yt-icon, tr.yt-icon, tt.yt-icon, u.yt-icon, ul.yt-icon, var.yt-icon {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.yt-icon[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
yt-icon, .yt-icon-container.yt-icon {
|
||||
display: inline-flex;
|
||||
-ms-flex-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
-ms-flex-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
fill: currentcolor;
|
||||
stroke: none;
|
||||
width: var(--iron-icon-width, 24px);
|
||||
height: var(--iron-icon-height, 24px);
|
||||
}
|
||||
|
||||
yt-icon.external-container {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
88
frontend/src/components/ChatRenderer/ImgShadow.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<yt-img-shadow class="no-transition" :height="height" :width="width" style="background-color: transparent;" loaded>
|
||||
<img id="img" class="style-scope yt-img-shadow" alt="" :height="height" :width="width" :src="imgUrl">
|
||||
</yt-img-shadow>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ImgShadow',
|
||||
props: {
|
||||
imgUrl: String,
|
||||
height: String,
|
||||
width: String
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- yt-img-shadow -->
|
||||
<style>
|
||||
canvas.yt-img-shadow, caption.yt-img-shadow, center.yt-img-shadow, cite.yt-img-shadow, code.yt-img-shadow, dd.yt-img-shadow, del.yt-img-shadow, dfn.yt-img-shadow, div.yt-img-shadow, dl.yt-img-shadow, dt.yt-img-shadow, em.yt-img-shadow, embed.yt-img-shadow, fieldset.yt-img-shadow, font.yt-img-shadow, form.yt-img-shadow, h1.yt-img-shadow, h2.yt-img-shadow, h3.yt-img-shadow, h4.yt-img-shadow, h5.yt-img-shadow, h6.yt-img-shadow, hr.yt-img-shadow, i.yt-img-shadow, iframe.yt-img-shadow, img.yt-img-shadow, ins.yt-img-shadow, kbd.yt-img-shadow, label.yt-img-shadow, legend.yt-img-shadow, li.yt-img-shadow, menu.yt-img-shadow, object.yt-img-shadow, ol.yt-img-shadow, p.yt-img-shadow, pre.yt-img-shadow, q.yt-img-shadow, s.yt-img-shadow, samp.yt-img-shadow, small.yt-img-shadow, span.yt-img-shadow, strike.yt-img-shadow, strong.yt-img-shadow, sub.yt-img-shadow, sup.yt-img-shadow, table.yt-img-shadow, tbody.yt-img-shadow, td.yt-img-shadow, tfoot.yt-img-shadow, th.yt-img-shadow, thead.yt-img-shadow, tr.yt-img-shadow, tt.yt-img-shadow, u.yt-img-shadow, ul.yt-img-shadow, var.yt-img-shadow {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.yt-img-shadow[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
yt-img-shadow {
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
-ms-flex: none;
|
||||
-webkit-flex: none;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
yt-img-shadow.no-transition {
|
||||
opacity: 1;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
yt-img-shadow.with-placeholder {
|
||||
background-color: transparent;
|
||||
min-height: unset;
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
yt-img-shadow[loaded] {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
yt-img-shadow.empty img.yt-img-shadow {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
yt-img-shadow[object-fit="FILL"] img.yt-img-shadow, yt-img-shadow[fit] img.yt-img-shadow {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
yt-img-shadow[object-fit="COVER"] img.yt-img-shadow {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
yt-img-shadow[object-fit="CONTAIN"] img.yt-img-shadow {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
yt-img-shadow[object-position="LEFT"] img.yt-img-shadow {
|
||||
object-position: left;
|
||||
}
|
||||
|
||||
img.yt-img-shadow {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-height: none;
|
||||
max-width: 100%;
|
||||
border-radius: none;
|
||||
}
|
||||
</style>
|
||||
306
frontend/src/components/ChatRenderer/LegacyPaidMessage.vue
Normal file
@@ -0,0 +1,306 @@
|
||||
<template>
|
||||
<yt-live-chat-legacy-paid-message-renderer class="style-scope yt-live-chat-item-list-renderer">
|
||||
<div id="card" class="style-scope yt-live-chat-legacy-paid-message-renderer">
|
||||
<img-shadow id="author-photo" height="40" width="40" class="style-scope yt-live-chat-legacy-paid-message-renderer"
|
||||
:imgUrl="avatarUrl"
|
||||
></img-shadow>
|
||||
<div id="content" class="style-scope yt-live-chat-legacy-paid-message-renderer">
|
||||
<div id="content-primary-column" class="style-scope yt-live-chat-legacy-paid-message-renderer">
|
||||
<div id="author-name" class="style-scope yt-live-chat-legacy-paid-message-renderer">{{authorName}}</div>
|
||||
<div id="event-text" class="style-scope yt-live-chat-legacy-paid-message-renderer">{{title}}</div>
|
||||
<div id="detail-text" class="style-scope yt-live-chat-legacy-paid-message-renderer">{{content}}</div>
|
||||
</div>
|
||||
<div id="timestamp" class="style-scope yt-live-chat-legacy-paid-message-renderer">{{timeText}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="inline-action-button-container" class="style-scope yt-live-chat-legacy-paid-message-renderer" aria-hidden="true">
|
||||
<div id="inline-action-buttons" class="style-scope yt-live-chat-legacy-paid-message-renderer"></div>
|
||||
</div>
|
||||
</yt-live-chat-legacy-paid-message-renderer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ImgShadow from './ImgShadow.vue'
|
||||
import utils from '@/utils'
|
||||
|
||||
export default {
|
||||
name: 'LegacyPaidMessage',
|
||||
components: {
|
||||
ImgShadow
|
||||
},
|
||||
props: {
|
||||
avatarUrl: String,
|
||||
authorName: String,
|
||||
title: String,
|
||||
content: String,
|
||||
time: Date
|
||||
},
|
||||
computed: {
|
||||
timeText() {
|
||||
return utils.getTimeTextMinSec(this.time)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- yt-live-chat-legacy-paid-message-renderer -->
|
||||
<style>
|
||||
canvas.yt-live-chat-legacy-paid-message-renderer, caption.yt-live-chat-legacy-paid-message-renderer, center.yt-live-chat-legacy-paid-message-renderer, cite.yt-live-chat-legacy-paid-message-renderer, code.yt-live-chat-legacy-paid-message-renderer, dd.yt-live-chat-legacy-paid-message-renderer, del.yt-live-chat-legacy-paid-message-renderer, dfn.yt-live-chat-legacy-paid-message-renderer, div.yt-live-chat-legacy-paid-message-renderer, dl.yt-live-chat-legacy-paid-message-renderer, dt.yt-live-chat-legacy-paid-message-renderer, em.yt-live-chat-legacy-paid-message-renderer, embed.yt-live-chat-legacy-paid-message-renderer, fieldset.yt-live-chat-legacy-paid-message-renderer, font.yt-live-chat-legacy-paid-message-renderer, form.yt-live-chat-legacy-paid-message-renderer, h1.yt-live-chat-legacy-paid-message-renderer, h2.yt-live-chat-legacy-paid-message-renderer, h3.yt-live-chat-legacy-paid-message-renderer, h4.yt-live-chat-legacy-paid-message-renderer, h5.yt-live-chat-legacy-paid-message-renderer, h6.yt-live-chat-legacy-paid-message-renderer, hr.yt-live-chat-legacy-paid-message-renderer, i.yt-live-chat-legacy-paid-message-renderer, iframe.yt-live-chat-legacy-paid-message-renderer, img.yt-live-chat-legacy-paid-message-renderer, ins.yt-live-chat-legacy-paid-message-renderer, kbd.yt-live-chat-legacy-paid-message-renderer, label.yt-live-chat-legacy-paid-message-renderer, legend.yt-live-chat-legacy-paid-message-renderer, li.yt-live-chat-legacy-paid-message-renderer, menu.yt-live-chat-legacy-paid-message-renderer, object.yt-live-chat-legacy-paid-message-renderer, ol.yt-live-chat-legacy-paid-message-renderer, p.yt-live-chat-legacy-paid-message-renderer, pre.yt-live-chat-legacy-paid-message-renderer, q.yt-live-chat-legacy-paid-message-renderer, s.yt-live-chat-legacy-paid-message-renderer, samp.yt-live-chat-legacy-paid-message-renderer, small.yt-live-chat-legacy-paid-message-renderer, span.yt-live-chat-legacy-paid-message-renderer, strike.yt-live-chat-legacy-paid-message-renderer, strong.yt-live-chat-legacy-paid-message-renderer, sub.yt-live-chat-legacy-paid-message-renderer, sup.yt-live-chat-legacy-paid-message-renderer, table.yt-live-chat-legacy-paid-message-renderer, tbody.yt-live-chat-legacy-paid-message-renderer, td.yt-live-chat-legacy-paid-message-renderer, tfoot.yt-live-chat-legacy-paid-message-renderer, th.yt-live-chat-legacy-paid-message-renderer, thead.yt-live-chat-legacy-paid-message-renderer, tr.yt-live-chat-legacy-paid-message-renderer, tt.yt-live-chat-legacy-paid-message-renderer, u.yt-live-chat-legacy-paid-message-renderer, ul.yt-live-chat-legacy-paid-message-renderer, var.yt-live-chat-legacy-paid-message-renderer {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.yt-live-chat-legacy-paid-message-renderer[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#timestamp.yt-live-chat-legacy-paid-message-renderer {
|
||||
display: var(--yt-live-chat-item-timestamp-display, inline);
|
||||
margin: var(--yt-live-chat-item-timestamp-margin, 0 8px 0 0);
|
||||
color: var(--yt-live-chat-tertiary-text-color);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#author-photo.yt-live-chat-legacy-paid-message-renderer {
|
||||
display: block;
|
||||
margin-right: 16px;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
-ms-flex: none;
|
||||
-webkit-flex: none;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
#menu-button.yt-live-chat-legacy-paid-message-renderer {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#menu.yt-live-chat-legacy-paid-message-renderer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
transform: translateX(100px);
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer:hover #menu.yt-live-chat-legacy-paid-message-renderer, yt-live-chat-legacy-paid-message-renderer[menu-visible] #menu.yt-live-chat-legacy-paid-message-renderer {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer:focus-within #menu.yt-live-chat-legacy-paid-message-renderer {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
#inline-action-button-container.yt-live-chat-legacy-paid-message-renderer {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: 0;
|
||||
bottom: -4px;
|
||||
left: 0;
|
||||
background-color: var(--yt-live-chat-moderation-mode-hover-background-color);
|
||||
display: none;
|
||||
-ms-flex-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
-ms-flex-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer[has-inline-action-buttons]:hover #inline-action-button-container.yt-live-chat-legacy-paid-message-renderer {
|
||||
display: flex;
|
||||
-ms-flex-direction: row;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
display: var(--yt-live-chat-inline-action-button-container-display, none);
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer[has-inline-action-buttons][hide-inline-action-buttons]:hover #inline-action-button-container.yt-live-chat-legacy-paid-message-renderer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer[has-inline-action-buttons]:hover #menu.yt-live-chat-legacy-paid-message-renderer {
|
||||
display: var(--yt-live-chat-item-with-inline-actions-context-menu-display, block);
|
||||
}
|
||||
|
||||
#inline-action-buttons.yt-live-chat-legacy-paid-message-renderer>*.yt-live-chat-legacy-paid-message-renderer, #additional-inline-action-buttons.yt-live-chat-legacy-paid-message-renderer>*.yt-live-chat-legacy-paid-message-renderer {
|
||||
--yt-button-icon-size: 36px;
|
||||
--yt-button-icon-padding: 6px;
|
||||
color: hsl(0, 0%, 100%);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#inline-action-buttons.yt-live-chat-legacy-paid-message-renderer>*.yt-live-chat-legacy-paid-message-renderer {
|
||||
background: hsla(0, 0%, 6.7%, .8);
|
||||
}
|
||||
|
||||
#inline-action-buttons.yt-live-chat-legacy-paid-message-renderer>.yt-live-chat-legacy-paid-message-renderer:hover {
|
||||
background: hsl(0, 0%, 6.7%);
|
||||
}
|
||||
|
||||
#additional-inline-action-buttons.yt-live-chat-legacy-paid-message-renderer>*.yt-live-chat-legacy-paid-message-renderer {
|
||||
color: var(--yt-live-chat-additional-inline-action-button-color);
|
||||
background: var(--yt-live-chat-additional-inline-action-button-background-color);
|
||||
}
|
||||
|
||||
#additional-inline-action-buttons.yt-live-chat-legacy-paid-message-renderer>.yt-live-chat-legacy-paid-message-renderer:hover {
|
||||
background: var(--yt-live-chat-additional-inline-action-button-background-color-hover);
|
||||
}
|
||||
|
||||
#additional-inline-action-buttons.yt-live-chat-legacy-paid-message-renderer:not(:empty) {
|
||||
margin-left: 32px;
|
||||
}
|
||||
|
||||
#inline-action-buttons.yt-live-chat-legacy-paid-message-renderer>*.yt-live-chat-legacy-paid-message-renderer:not(:first-child), #additional-inline-action-buttons.yt-live-chat-legacy-paid-message-renderer>*.yt-live-chat-legacy-paid-message-renderer:not(:first-child) {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer {
|
||||
position: relative;
|
||||
display: block;
|
||||
--yt-live-chat-sponsor-color: #0f9d58;
|
||||
--yt-live-chat-item-timestamp-display: var(--yt-live-chat-paid-message-timestamp-display, none);
|
||||
padding: 4px 24px;
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer[dashboard-money-feed] {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#card.yt-live-chat-legacy-paid-message-renderer {
|
||||
position: relative;
|
||||
padding: 8px 16px;
|
||||
background-color: var(--yt-live-chat-sponsor-color);
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
-ms-flex-direction: row;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-ms-flex-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer[dashboard-money-feed] #card.yt-live-chat-legacy-paid-message-renderer {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
background-color: var(--yt-live-chat-background-color);
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
|
||||
#author-photo.yt-live-chat-legacy-paid-message-renderer {
|
||||
-ms-align-self: flex-start;
|
||||
-webkit-align-self: flex-start;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
#author-name.yt-live-chat-legacy-paid-message-renderer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer[dashboard-money-feed] #author-name.yt-live-chat-legacy-paid-message-renderer {
|
||||
display: block;
|
||||
margin-right: 8px;
|
||||
color: var(--yt-live-chat-secondary-text-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
#content.yt-live-chat-legacy-paid-message-renderer {
|
||||
-ms-flex: 1 1 0.000000001px;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
-webkit-flex-basis: 0.000000001px;
|
||||
flex-basis: 0.000000001px;
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer[dashboard-money-feed] #content.yt-live-chat-legacy-paid-message-renderer {
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer[dashboard-money-feed] #content-primary-column.yt-live-chat-legacy-paid-message-renderer {
|
||||
display: flex;
|
||||
-ms-flex-direction: row;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-ms-flex-align: baseline;
|
||||
-webkit-align-items: baseline;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
#event-text.yt-live-chat-legacy-paid-message-renderer {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer[dashboard-money-feed] #event-text.yt-live-chat-legacy-paid-message-renderer {
|
||||
display: inline;
|
||||
height: 24px;
|
||||
min-width: 16px;
|
||||
border-radius: 12px;
|
||||
margin-right: 8px;
|
||||
padding: 0 12px;
|
||||
background-color: var(--yt-live-chat-sponsor-color);
|
||||
color: hsl(0, 0%, 100%);
|
||||
display: inline-flex;
|
||||
-ms-flex-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
-ms-flex-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2rem;
|
||||
}
|
||||
|
||||
#detail-text.yt-live-chat-legacy-paid-message-renderer {
|
||||
font-size: 15px;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
#detail-text.yt-live-chat-legacy-paid-message-renderer .emoji.yt-live-chat-legacy-paid-message-renderer {
|
||||
width: var(--yt-live-chat-emoji-size);
|
||||
height: var(--yt-live-chat-emoji-size);
|
||||
margin: -1px 2px 1px 2px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer[dashboard-money-feed] #detail-text.yt-live-chat-legacy-paid-message-renderer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
a.yt-live-chat-legacy-paid-message-renderer {
|
||||
display: inline;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#detail-text.yt-live-chat-legacy-paid-message-renderer a.yt-live-chat-legacy-paid-message-renderer {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#detail-text.yt-live-chat-legacy-paid-message-renderer a.yt-live-chat-legacy-paid-message-renderer .mention.yt-live-chat-legacy-paid-message-renderer {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#menu.yt-live-chat-legacy-paid-message-renderer {
|
||||
background: linear-gradient(to right, transparent, var(--yt-live-chat-sponsor-color) 100%);
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer[dashboard-money-feed] #menu.yt-live-chat-legacy-paid-message-renderer {
|
||||
margin-top: 8px;
|
||||
background: linear-gradient(to right, transparent, var(--yt-live-chat-background-color) 40%);
|
||||
}
|
||||
</style>
|
||||
444
frontend/src/components/ChatRenderer/PaidMessage.vue
Normal file
@@ -0,0 +1,444 @@
|
||||
<template>
|
||||
<yt-live-chat-paid-message-renderer class="style-scope yt-live-chat-item-list-renderer" allow-animations :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,
|
||||
'--yt-live-chat-paid-message-author-name-color': color.authorName,
|
||||
'--yt-live-chat-paid-message-timestamp-color': color.time,
|
||||
'--yt-live-chat-paid-message-color': color.content
|
||||
}"
|
||||
>
|
||||
<div id="card" class="style-scope yt-live-chat-paid-message-renderer">
|
||||
<div id="header" class="style-scope yt-live-chat-paid-message-renderer">
|
||||
<img-shadow id="author-photo" height="40" width="40" class="style-scope yt-live-chat-paid-message-renderer"
|
||||
:imgUrl="avatarUrl"
|
||||
></img-shadow>
|
||||
<div id="header-content" class="style-scope yt-live-chat-paid-message-renderer">
|
||||
<div id="header-content-primary-column" class="style-scope yt-live-chat-paid-message-renderer">
|
||||
<div id="author-name" class="style-scope yt-live-chat-paid-message-renderer">{{authorName}}</div>
|
||||
<div id="purchase-amount" class="style-scope yt-live-chat-paid-message-renderer">{{priceText}}</div>
|
||||
</div>
|
||||
<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="message" dir="auto" class="style-scope yt-live-chat-paid-message-renderer">
|
||||
{{content}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</yt-live-chat-paid-message-renderer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ImgShadow from './ImgShadow.vue'
|
||||
import * as constants from './constants'
|
||||
import utils from '@/utils'
|
||||
|
||||
export default {
|
||||
name: 'PaidMessage',
|
||||
components: {
|
||||
ImgShadow
|
||||
},
|
||||
props: {
|
||||
avatarUrl: String,
|
||||
authorName: String,
|
||||
price: Number, // 价格,人民币
|
||||
time: Date,
|
||||
content: String
|
||||
},
|
||||
computed: {
|
||||
color() {
|
||||
return constants.getPriceConfig(this.price).colors
|
||||
},
|
||||
priceText() {
|
||||
return 'CN¥' + utils.formatCurrency(this.price)
|
||||
},
|
||||
timeText() {
|
||||
return utils.getTimeTextMinSec(this.time)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- yt-live-chat-paid-message-renderer -->
|
||||
<style>
|
||||
canvas.yt-live-chat-paid-message-renderer, caption.yt-live-chat-paid-message-renderer, center.yt-live-chat-paid-message-renderer, cite.yt-live-chat-paid-message-renderer, code.yt-live-chat-paid-message-renderer, dd.yt-live-chat-paid-message-renderer, del.yt-live-chat-paid-message-renderer, dfn.yt-live-chat-paid-message-renderer, div.yt-live-chat-paid-message-renderer, dl.yt-live-chat-paid-message-renderer, dt.yt-live-chat-paid-message-renderer, em.yt-live-chat-paid-message-renderer, embed.yt-live-chat-paid-message-renderer, fieldset.yt-live-chat-paid-message-renderer, font.yt-live-chat-paid-message-renderer, form.yt-live-chat-paid-message-renderer, h1.yt-live-chat-paid-message-renderer, h2.yt-live-chat-paid-message-renderer, h3.yt-live-chat-paid-message-renderer, h4.yt-live-chat-paid-message-renderer, h5.yt-live-chat-paid-message-renderer, h6.yt-live-chat-paid-message-renderer, hr.yt-live-chat-paid-message-renderer, i.yt-live-chat-paid-message-renderer, iframe.yt-live-chat-paid-message-renderer, img.yt-live-chat-paid-message-renderer, ins.yt-live-chat-paid-message-renderer, kbd.yt-live-chat-paid-message-renderer, label.yt-live-chat-paid-message-renderer, legend.yt-live-chat-paid-message-renderer, li.yt-live-chat-paid-message-renderer, menu.yt-live-chat-paid-message-renderer, object.yt-live-chat-paid-message-renderer, ol.yt-live-chat-paid-message-renderer, p.yt-live-chat-paid-message-renderer, pre.yt-live-chat-paid-message-renderer, q.yt-live-chat-paid-message-renderer, s.yt-live-chat-paid-message-renderer, samp.yt-live-chat-paid-message-renderer, small.yt-live-chat-paid-message-renderer, span.yt-live-chat-paid-message-renderer, strike.yt-live-chat-paid-message-renderer, strong.yt-live-chat-paid-message-renderer, sub.yt-live-chat-paid-message-renderer, sup.yt-live-chat-paid-message-renderer, table.yt-live-chat-paid-message-renderer, tbody.yt-live-chat-paid-message-renderer, td.yt-live-chat-paid-message-renderer, tfoot.yt-live-chat-paid-message-renderer, th.yt-live-chat-paid-message-renderer, thead.yt-live-chat-paid-message-renderer, tr.yt-live-chat-paid-message-renderer, tt.yt-live-chat-paid-message-renderer, u.yt-live-chat-paid-message-renderer, ul.yt-live-chat-paid-message-renderer, var.yt-live-chat-paid-message-renderer {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.yt-live-chat-paid-message-renderer[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#timestamp.yt-live-chat-paid-message-renderer {
|
||||
display: var(--yt-live-chat-item-timestamp-display, inline);
|
||||
margin: var(--yt-live-chat-item-timestamp-margin, 0 8px 0 0);
|
||||
color: var(--yt-live-chat-tertiary-text-color);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#author-photo.yt-live-chat-paid-message-renderer {
|
||||
display: block;
|
||||
margin-right: 16px;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
-ms-flex: none;
|
||||
-webkit-flex: none;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
#menu-button.yt-live-chat-paid-message-renderer {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#menu.yt-live-chat-paid-message-renderer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
transform: translateX(100px);
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer:hover #menu.yt-live-chat-paid-message-renderer, yt-live-chat-paid-message-renderer[menu-visible] #menu.yt-live-chat-paid-message-renderer {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer:focus-within #menu.yt-live-chat-paid-message-renderer {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
#inline-action-button-container.yt-live-chat-paid-message-renderer {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: 0;
|
||||
bottom: -4px;
|
||||
left: 0;
|
||||
background-color: var(--yt-live-chat-moderation-mode-hover-background-color);
|
||||
display: none;
|
||||
-ms-flex-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
-ms-flex-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[has-inline-action-buttons]:hover #inline-action-button-container.yt-live-chat-paid-message-renderer {
|
||||
display: flex;
|
||||
-ms-flex-direction: row;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
display: var(--yt-live-chat-inline-action-button-container-display, none);
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[has-inline-action-buttons][hide-inline-action-buttons]:hover #inline-action-button-container.yt-live-chat-paid-message-renderer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[has-inline-action-buttons]:hover #menu.yt-live-chat-paid-message-renderer {
|
||||
display: var(--yt-live-chat-item-with-inline-actions-context-menu-display, block);
|
||||
}
|
||||
|
||||
#inline-action-buttons.yt-live-chat-paid-message-renderer>*.yt-live-chat-paid-message-renderer, #additional-inline-action-buttons.yt-live-chat-paid-message-renderer>*.yt-live-chat-paid-message-renderer {
|
||||
--yt-button-icon-size: 36px;
|
||||
--yt-button-icon-padding: 6px;
|
||||
color: hsl(0, 0%, 100%);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#inline-action-buttons.yt-live-chat-paid-message-renderer>*.yt-live-chat-paid-message-renderer {
|
||||
background: hsla(0, 0%, 6.7%, .8);
|
||||
}
|
||||
|
||||
#inline-action-buttons.yt-live-chat-paid-message-renderer>.yt-live-chat-paid-message-renderer:hover {
|
||||
background: hsl(0, 0%, 6.7%);
|
||||
}
|
||||
|
||||
#additional-inline-action-buttons.yt-live-chat-paid-message-renderer>*.yt-live-chat-paid-message-renderer {
|
||||
color: var(--yt-live-chat-additional-inline-action-button-color);
|
||||
background: var(--yt-live-chat-additional-inline-action-button-background-color);
|
||||
}
|
||||
|
||||
#additional-inline-action-buttons.yt-live-chat-paid-message-renderer>.yt-live-chat-paid-message-renderer:hover {
|
||||
background: var(--yt-live-chat-additional-inline-action-button-background-color-hover);
|
||||
}
|
||||
|
||||
#additional-inline-action-buttons.yt-live-chat-paid-message-renderer:not(:empty) {
|
||||
margin-left: 32px;
|
||||
}
|
||||
|
||||
#inline-action-buttons.yt-live-chat-paid-message-renderer>*.yt-live-chat-paid-message-renderer:not(:first-child), #additional-inline-action-buttons.yt-live-chat-paid-message-renderer>*.yt-live-chat-paid-message-renderer:not(:first-child) {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 4px 24px;
|
||||
font-size: 15px;
|
||||
--yt-live-chat-paid-message-background-color: var(--yt-live-chat-paid-message-primary-color);
|
||||
--yt-live-chat-paid-message-header-background-color: var(--yt-live-chat-paid-message-secondary-color);
|
||||
--yt-live-chat-text-input-field-placeholder-color: var(--yt-live-chat-paid-message-color);
|
||||
--yt-live-chat-item-timestamp-display: var(--yt-live-chat-paid-message-timestamp-display, none);
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[dashboard-money-feed] {
|
||||
padding: 0;
|
||||
padding-bottom: 8px;
|
||||
--yt-live-chat-paid-message-background-color: transparent;
|
||||
--yt-live-chat-paid-message-header-background-color: transparent;
|
||||
--yt-live-chat-item-timestamp-display: block;
|
||||
}
|
||||
|
||||
#card.yt-live-chat-paid-message-renderer {
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[dashboard-money-feed] #card.yt-live-chat-paid-message-renderer {
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#header.yt-live-chat-paid-message-renderer {
|
||||
position: relative;
|
||||
background-color: var(--yt-live-chat-paid-message-header-background-color, #125aac);
|
||||
color: var(--yt-live-chat-paid-message-header-color, #fff);
|
||||
font-weight: 500;
|
||||
padding: 8px 16px;
|
||||
min-height: 20px;
|
||||
display: flex;
|
||||
-ms-flex-direction: row;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-ms-flex-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[show-only-header] #header.yt-live-chat-paid-message-renderer {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[dashboard-money-feed] #header.yt-live-chat-paid-message-renderer {
|
||||
color: var(--yt-live-chat-secondary-text-color);
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
#author-photo.yt-live-chat-paid-message-renderer {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
yt-icon#author-photo.yt-live-chat-paid-message-renderer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[dashboard-money-feed] yt-icon#author-photo.yt-live-chat-paid-message-renderer {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#header-content.yt-live-chat-paid-message-renderer {
|
||||
display: flex;
|
||||
-ms-flex-direction: row;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-ms-flex-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
justify-content: space-between;
|
||||
-ms-flex: 1 1 0.000000001px;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
-webkit-flex-basis: 0.000000001px;
|
||||
flex-basis: 0.000000001px;
|
||||
-ms-flex-align: baseline;
|
||||
-webkit-align-items: baseline;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[dashboard-money-feed] #header-content.yt-live-chat-paid-message-renderer {
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#header-content-primary-column.yt-live-chat-paid-message-renderer {
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-ms-flex-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
justify-content: space-between;
|
||||
-ms-flex: 1 1 0.000000001px;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
-webkit-flex-basis: 0.000000001px;
|
||||
flex-basis: 0.000000001px;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[dashboard-money-feed] #header-content-primary-column.yt-live-chat-paid-message-renderer {
|
||||
margin-bottom: 4px;
|
||||
display: flex;
|
||||
-ms-flex-direction: row;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-ms-flex-align: baseline;
|
||||
-webkit-align-items: baseline;
|
||||
align-items: baseline;
|
||||
-ms-flex: none;
|
||||
-webkit-flex: none;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
#author-name.yt-live-chat-paid-message-renderer {
|
||||
color: var(--yt-live-chat-paid-message-author-name-color, rgba(255, 255, 255, 0.7));
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[dashboard-money-feed] #author-name.yt-live-chat-paid-message-renderer {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[dashboard-money-feed] #purchase-amount.yt-live-chat-paid-message-renderer {
|
||||
display: inline;
|
||||
height: 24px;
|
||||
min-width: 16px;
|
||||
border-radius: 12px;
|
||||
margin-right: 8px;
|
||||
padding: 0 12px;
|
||||
background-color: var(--yt-live-chat-paid-message-primary-color);
|
||||
color: var(--yt-live-chat-paid-message-color);
|
||||
display: inline-flex;
|
||||
-ms-flex-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
-ms-flex-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2rem;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[dashboard-money-feed] #author-name.yt-live-chat-paid-message-renderer {
|
||||
display: block;
|
||||
color: var(--yt-live-chat-secondary-text-color);
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[dashboard-money-feed] #author-name.yt-live-chat-paid-message-renderer[is-deleted] {
|
||||
margin: 4px 0 0;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[dashboard-money-feed] #author-name.yt-live-chat-paid-message-renderer[is-deleted].yt-live-chat-paid-message-renderer::before {
|
||||
content: '​';
|
||||
display: inline;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
#menu.yt-live-chat-paid-message-renderer {
|
||||
background: linear-gradient(to right, transparent, var(--yt-live-chat-paid-message-header-background-color, #125aac) 100%);
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[dashboard-money-feed] #menu.yt-live-chat-paid-message-renderer {
|
||||
margin-top: 8px;
|
||||
background: linear-gradient(to right, transparent, var(--yt-live-chat-background-color) 40%);
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[allow-animations] #content.yt-live-chat-paid-message-renderer, yt-live-chat-paid-message-renderer[allow-animations] #header.yt-live-chat-paid-message-renderer {
|
||||
transition-property: background-color, color;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
#content.yt-live-chat-paid-message-renderer {
|
||||
background-color: var(--yt-live-chat-paid-message-background-color, #1565c0);
|
||||
color: var(--yt-live-chat-paid-message-color, #fff);
|
||||
padding: 8px 16px;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
|
||||
#content.yt-live-chat-paid-message-renderer img.yt-live-chat-paid-message-renderer {
|
||||
width: var(--yt-live-chat-emoji-size);
|
||||
height: var(--yt-live-chat-emoji-size);
|
||||
margin: -1px 2px 1px 2px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[dashboard-money-feed] #content.yt-live-chat-paid-message-renderer {
|
||||
padding: 0;
|
||||
padding-left: 72px;
|
||||
color: var(--yt-live-chat-secondary-text-color);
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[show-only-header] #content.yt-live-chat-paid-message-renderer {
|
||||
visibility: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a.yt-live-chat-paid-message-renderer {
|
||||
display: inline;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#message.yt-live-chat-paid-message-renderer a.yt-live-chat-paid-message-renderer {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[dashboard-money-feed] #message.yt-live-chat-paid-message-renderer {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
#message.yt-live-chat-paid-message-renderer a.yt-live-chat-paid-message-renderer .mention.yt-live-chat-paid-message-renderer {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#message.yt-live-chat-paid-message-renderer:empty, #author-name.yt-live-chat-paid-message-renderer:empty:not([is-deleted]).yt-live-chat-paid-message-renderer, #deleted-state.yt-live-chat-paid-message-renderer:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#deleted-state.yt-live-chat-paid-message-renderer {
|
||||
display: block;
|
||||
font-style: italic;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[dashboard-money-feed] #deleted-state.yt-live-chat-paid-message-renderer[is-deleted] {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[dashboard-money-feed] #author-name.yt-live-chat-paid-message-renderer {
|
||||
-ms-flex: none;
|
||||
-webkit-flex: none;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[show-footer-divider] #footer.yt-live-chat-paid-message-renderer::before {
|
||||
content: "";
|
||||
display: flex;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.12);
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer[is-user-editable] #footer.yt-live-chat-paid-message-renderer:not(:empty) {
|
||||
padding-top: 8px;
|
||||
}
|
||||
</style>
|
||||
394
frontend/src/components/ChatRenderer/TextMessage.vue
Normal file
@@ -0,0 +1,394 @@
|
||||
<template>
|
||||
<yt-live-chat-text-message-renderer :author-type="authorTypeText">
|
||||
<img-shadow id="author-photo" height="24" width="24" class="style-scope yt-live-chat-text-message-renderer"
|
||||
:imgUrl="avatarUrl"
|
||||
></img-shadow>
|
||||
<div id="content" class="style-scope yt-live-chat-text-message-renderer">
|
||||
<span id="timestamp" class="style-scope yt-live-chat-text-message-renderer">{{timeText}}</span>
|
||||
<yt-live-chat-author-chip class="style-scope yt-live-chat-text-message-renderer">
|
||||
<span id="author-name" dir="auto" class="style-scope yt-live-chat-author-chip" :type="authorTypeText">
|
||||
{{authorName}}
|
||||
<!-- 这里是已验证勋章 -->
|
||||
<span id="chip-badges" class="style-scope yt-live-chat-author-chip"></span>
|
||||
</span>
|
||||
<span id="chat-badges" class="style-scope yt-live-chat-author-chip">
|
||||
<author-badge class="style-scope yt-live-chat-author-chip"
|
||||
:isAdmin="authorType === 2" :privilegeType="privilegeType"
|
||||
></author-badge>
|
||||
</span>
|
||||
</yt-live-chat-author-chip>
|
||||
<span id="message" class="style-scope yt-live-chat-text-message-renderer">{{content}}</span>
|
||||
<el-badge :value="repeated" :max="99" v-show="repeated > 1" class="style-scope yt-live-chat-text-message-renderer"
|
||||
:style="{'--repeated-mark-color': repeatedMarkColor}"
|
||||
></el-badge>
|
||||
</div>
|
||||
</yt-live-chat-text-message-renderer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ImgShadow from './ImgShadow.vue'
|
||||
import AuthorBadge from './AuthorBadge.vue'
|
||||
import * as constants from './constants'
|
||||
import utils from '@/utils'
|
||||
|
||||
// HSL
|
||||
const REPEATED_MARK_COLOR_START = [210, 100.0, 62.5]
|
||||
const REPEATED_MARK_COLOR_END = [360, 87.3, 69.2]
|
||||
|
||||
export default {
|
||||
name: 'TextMessage',
|
||||
components: {
|
||||
ImgShadow,
|
||||
AuthorBadge
|
||||
},
|
||||
props: {
|
||||
avatarUrl: String,
|
||||
time: Date,
|
||||
authorName: String,
|
||||
authorType: Number,
|
||||
content: String,
|
||||
privilegeType: Number,
|
||||
repeated: Number
|
||||
},
|
||||
computed: {
|
||||
timeText() {
|
||||
return utils.getTimeTextMinSec(this.time)
|
||||
},
|
||||
authorTypeText() {
|
||||
return constants.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 `hsl(${color[0]}, ${color[1]}%, ${color[2]}%)`
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
yt-live-chat-text-message-renderer>#content>.el-badge {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer>#content>.el-badge .el-badge__content {
|
||||
font-size: 12px !important;
|
||||
line-height: 18px !important;
|
||||
text-shadow: none !important;
|
||||
font-family: sans-serif !important;
|
||||
background-color: var(--repeated-mark-color) !important;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- yt-live-chat-text-message-renderer -->
|
||||
<style>
|
||||
canvas.yt-live-chat-text-message-renderer, caption.yt-live-chat-text-message-renderer, center.yt-live-chat-text-message-renderer, cite.yt-live-chat-text-message-renderer, code.yt-live-chat-text-message-renderer, dd.yt-live-chat-text-message-renderer, del.yt-live-chat-text-message-renderer, dfn.yt-live-chat-text-message-renderer, div.yt-live-chat-text-message-renderer, dl.yt-live-chat-text-message-renderer, dt.yt-live-chat-text-message-renderer, em.yt-live-chat-text-message-renderer, embed.yt-live-chat-text-message-renderer, fieldset.yt-live-chat-text-message-renderer, font.yt-live-chat-text-message-renderer, form.yt-live-chat-text-message-renderer, h1.yt-live-chat-text-message-renderer, h2.yt-live-chat-text-message-renderer, h3.yt-live-chat-text-message-renderer, h4.yt-live-chat-text-message-renderer, h5.yt-live-chat-text-message-renderer, h6.yt-live-chat-text-message-renderer, hr.yt-live-chat-text-message-renderer, i.yt-live-chat-text-message-renderer, iframe.yt-live-chat-text-message-renderer, img.yt-live-chat-text-message-renderer, ins.yt-live-chat-text-message-renderer, kbd.yt-live-chat-text-message-renderer, label.yt-live-chat-text-message-renderer, legend.yt-live-chat-text-message-renderer, li.yt-live-chat-text-message-renderer, menu.yt-live-chat-text-message-renderer, object.yt-live-chat-text-message-renderer, ol.yt-live-chat-text-message-renderer, p.yt-live-chat-text-message-renderer, pre.yt-live-chat-text-message-renderer, q.yt-live-chat-text-message-renderer, s.yt-live-chat-text-message-renderer, samp.yt-live-chat-text-message-renderer, small.yt-live-chat-text-message-renderer, span.yt-live-chat-text-message-renderer, strike.yt-live-chat-text-message-renderer, strong.yt-live-chat-text-message-renderer, sub.yt-live-chat-text-message-renderer, sup.yt-live-chat-text-message-renderer, table.yt-live-chat-text-message-renderer, tbody.yt-live-chat-text-message-renderer, td.yt-live-chat-text-message-renderer, tfoot.yt-live-chat-text-message-renderer, th.yt-live-chat-text-message-renderer, thead.yt-live-chat-text-message-renderer, tr.yt-live-chat-text-message-renderer, tt.yt-live-chat-text-message-renderer, u.yt-live-chat-text-message-renderer, ul.yt-live-chat-text-message-renderer, var.yt-live-chat-text-message-renderer {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.yt-live-chat-text-message-renderer[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
#timestamp.yt-live-chat-text-message-renderer {
|
||||
display: var(--yt-live-chat-item-timestamp-display, inline);
|
||||
margin: var(--yt-live-chat-item-timestamp-margin, 0 8px 0 0);
|
||||
color: var(--yt-live-chat-tertiary-text-color);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#author-photo.yt-live-chat-text-message-renderer {
|
||||
display: block;
|
||||
margin-right: 16px;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
-ms-flex: none;
|
||||
-webkit-flex: none;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
#menu-button.yt-live-chat-text-message-renderer {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#menu.yt-live-chat-text-message-renderer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
transform: translateX(100px);
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer:hover #menu.yt-live-chat-text-message-renderer, yt-live-chat-text-message-renderer[menu-visible] #menu.yt-live-chat-text-message-renderer {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer:focus-within #menu.yt-live-chat-text-message-renderer {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
#inline-action-button-container.yt-live-chat-text-message-renderer {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: 0;
|
||||
bottom: -4px;
|
||||
left: 0;
|
||||
background-color: var(--yt-live-chat-moderation-mode-hover-background-color);
|
||||
display: none;
|
||||
-ms-flex-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
-ms-flex-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer[has-inline-action-buttons]:hover #inline-action-button-container.yt-live-chat-text-message-renderer {
|
||||
display: flex;
|
||||
-ms-flex-direction: row;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
display: var(--yt-live-chat-inline-action-button-container-display, none);
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer[has-inline-action-buttons][hide-inline-action-buttons]:hover #inline-action-button-container.yt-live-chat-text-message-renderer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer[has-inline-action-buttons]:hover #menu.yt-live-chat-text-message-renderer {
|
||||
display: var(--yt-live-chat-item-with-inline-actions-context-menu-display, block);
|
||||
}
|
||||
|
||||
#inline-action-buttons.yt-live-chat-text-message-renderer>*.yt-live-chat-text-message-renderer, #additional-inline-action-buttons.yt-live-chat-text-message-renderer>*.yt-live-chat-text-message-renderer {
|
||||
--yt-button-icon-size: 36px;
|
||||
--yt-button-icon-padding: 6px;
|
||||
color: hsl(0, 0%, 100%);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#inline-action-buttons.yt-live-chat-text-message-renderer>*.yt-live-chat-text-message-renderer {
|
||||
background: hsla(0, 0%, 6.7%, .8);
|
||||
}
|
||||
|
||||
#inline-action-buttons.yt-live-chat-text-message-renderer>.yt-live-chat-text-message-renderer:hover {
|
||||
background: hsl(0, 0%, 6.7%);
|
||||
}
|
||||
|
||||
#additional-inline-action-buttons.yt-live-chat-text-message-renderer>*.yt-live-chat-text-message-renderer {
|
||||
color: var(--yt-live-chat-additional-inline-action-button-color);
|
||||
background: var(--yt-live-chat-additional-inline-action-button-background-color);
|
||||
}
|
||||
|
||||
#additional-inline-action-buttons.yt-live-chat-text-message-renderer>.yt-live-chat-text-message-renderer:hover {
|
||||
background: var(--yt-live-chat-additional-inline-action-button-background-color-hover);
|
||||
}
|
||||
|
||||
#additional-inline-action-buttons.yt-live-chat-text-message-renderer:not(:empty) {
|
||||
margin-left: 32px;
|
||||
}
|
||||
|
||||
#inline-action-buttons.yt-live-chat-text-message-renderer>*.yt-live-chat-text-message-renderer:not(:first-child), #additional-inline-action-buttons.yt-live-chat-text-message-renderer>*.yt-live-chat-text-message-renderer:not(:first-child) {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer {
|
||||
position: relative;
|
||||
font-size: 13px;
|
||||
padding: 4px 24px;
|
||||
overflow: hidden;
|
||||
--yt-endpoint-color: var(--yt-live-chat-primary-text-color, hsl(0, 0%, 6.7%));
|
||||
--yt-endpoint-hover-color: var(--yt-live-chat-primary-text-color, var(--yt-endpoint-color));
|
||||
display: flex;
|
||||
-ms-flex-direction: row;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-ms-flex-align: start;
|
||||
-webkit-align-items: flex-start;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer:hover {
|
||||
overflow: initial;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer[author-is-owner] {
|
||||
background-color: var(--yt-live-chat-message-highlight-background-color);
|
||||
}
|
||||
|
||||
#content.yt-live-chat-text-message-renderer {
|
||||
-ms-align-self: center;
|
||||
-webkit-align-self: center;
|
||||
align-self: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
yt-live-chat-author-chip.yt-live-chat-text-message-renderer {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
#message.yt-live-chat-text-message-renderer {
|
||||
color: var(--yt-live-chat-primary-text-color, var(--yt-primary-text-color));
|
||||
line-height: 16px;
|
||||
overflow: hidden;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
font-style: var(--yt-live-chat-text-message-renderer-message-message-style_-_font-style);
|
||||
opacity: var(--yt-live-chat-text-message-renderer-message-message-style_-_opacity);
|
||||
}
|
||||
|
||||
#message.yt-live-chat-text-message-renderer .emoji.yt-live-chat-text-message-renderer {
|
||||
width: var(--yt-live-chat-emoji-size);
|
||||
height: var(--yt-live-chat-emoji-size);
|
||||
margin: -1px 2px 1px 2px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
a.yt-live-chat-text-message-renderer {
|
||||
display: inline;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#message.yt-live-chat-text-message-renderer a.yt-live-chat-text-message-renderer {
|
||||
display: inline;
|
||||
text-decoration: underline;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#message.yt-live-chat-text-message-renderer a.yt-live-chat-text-message-renderer .mention.yt-live-chat-text-message-renderer {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#show-original.yt-live-chat-text-message-renderer {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
#message.yt-live-chat-text-message-renderer:empty, #deleted-state.yt-live-chat-text-message-renderer:empty, #show-original.yt-live-chat-text-message-renderer:empty, yt-live-chat-text-message-renderer[show-original] #deleted-state.yt-live-chat-text-message-renderer, yt-live-chat-text-message-renderer[show-original] #show-original.yt-live-chat-text-message-renderer, yt-live-chat-text-message-renderer[is-deleted]:not([show-original]) #message.yt-live-chat-text-message-renderer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#menu.yt-live-chat-text-message-renderer {
|
||||
color: var(--yt-live-chat-secondary-text-color);
|
||||
background: linear-gradient(to right, transparent, var(--yt-live-chat-background-color, hsl(0, 0%, 100%)) 100%);
|
||||
}
|
||||
|
||||
.mention.yt-live-chat-text-message-renderer {
|
||||
background: var(--yt-live-chat-mention-background-color);
|
||||
color: var(--yt-live-chat-mention-text-color);
|
||||
padding: 2px 4px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#deleted-state.yt-live-chat-text-message-renderer, #show-original.yt-live-chat-text-message-renderer, yt-live-chat-text-message-renderer[is-deleted] #message.yt-live-chat-text-message-renderer {
|
||||
font-style: italic;
|
||||
color: var(--yt-live-chat-deleted-message-color, rgba(0, 0, 0, 0.5));
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer[show-bar]::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
left: 8px;
|
||||
top: 4px;
|
||||
bottom: 4px;
|
||||
width: 4px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer[is-deleted]::before {
|
||||
background: var(--yt-live-chat-deleted-message-bar-color, rgba(0, 0, 0, 0.5));
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer[is-dimmed] #message.yt-live-chat-text-message-renderer {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer[is-dimmed]::before {
|
||||
background: var(--yt-live-chat-error-message-color, #f44336);
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- yt-live-chat-author-chip -->
|
||||
<style>
|
||||
canvas.yt-live-chat-author-chip, caption.yt-live-chat-author-chip, center.yt-live-chat-author-chip, cite.yt-live-chat-author-chip, code.yt-live-chat-author-chip, dd.yt-live-chat-author-chip, del.yt-live-chat-author-chip, dfn.yt-live-chat-author-chip, div.yt-live-chat-author-chip, dl.yt-live-chat-author-chip, dt.yt-live-chat-author-chip, em.yt-live-chat-author-chip, embed.yt-live-chat-author-chip, fieldset.yt-live-chat-author-chip, font.yt-live-chat-author-chip, form.yt-live-chat-author-chip, h1.yt-live-chat-author-chip, h2.yt-live-chat-author-chip, h3.yt-live-chat-author-chip, h4.yt-live-chat-author-chip, h5.yt-live-chat-author-chip, h6.yt-live-chat-author-chip, hr.yt-live-chat-author-chip, i.yt-live-chat-author-chip, iframe.yt-live-chat-author-chip, img.yt-live-chat-author-chip, ins.yt-live-chat-author-chip, kbd.yt-live-chat-author-chip, label.yt-live-chat-author-chip, legend.yt-live-chat-author-chip, li.yt-live-chat-author-chip, menu.yt-live-chat-author-chip, object.yt-live-chat-author-chip, ol.yt-live-chat-author-chip, p.yt-live-chat-author-chip, pre.yt-live-chat-author-chip, q.yt-live-chat-author-chip, s.yt-live-chat-author-chip, samp.yt-live-chat-author-chip, small.yt-live-chat-author-chip, span.yt-live-chat-author-chip, strike.yt-live-chat-author-chip, strong.yt-live-chat-author-chip, sub.yt-live-chat-author-chip, sup.yt-live-chat-author-chip, table.yt-live-chat-author-chip, tbody.yt-live-chat-author-chip, td.yt-live-chat-author-chip, tfoot.yt-live-chat-author-chip, th.yt-live-chat-author-chip, thead.yt-live-chat-author-chip, tr.yt-live-chat-author-chip, tt.yt-live-chat-author-chip, u.yt-live-chat-author-chip, ul.yt-live-chat-author-chip, var.yt-live-chat-author-chip {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.yt-live-chat-author-chip[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
yt-live-chat-author-chip {
|
||||
display: inline-flex;
|
||||
-ms-flex-align: baseline;
|
||||
-webkit-align-items: baseline;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
#author-name.yt-live-chat-author-chip {
|
||||
box-sizing: border-box;
|
||||
border-radius: 2px;
|
||||
color: var(--yt-live-chat-secondary-text-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
yt-live-chat-author-chip[is-highlighted] #author-name.yt-live-chat-author-chip {
|
||||
padding: 2px 4px;
|
||||
color: var(--yt-live-chat-author-chip-verified-text-color);
|
||||
background-color: var(--yt-live-chat-author-chip-verified-background-color);
|
||||
}
|
||||
|
||||
#author-name.yt-live-chat-author-chip[type='moderator'] {
|
||||
color: var(--yt-live-chat-moderator-color);
|
||||
}
|
||||
|
||||
yt-live-chat-author-chip[is-highlighted] #author-name.yt-live-chat-author-chip[type='owner'], #author-name.yt-live-chat-author-chip[type='owner'] {
|
||||
background-color: #ffd600;
|
||||
color: var(--yt-live-chat-author-chip-owner-text-color);
|
||||
}
|
||||
|
||||
#author-name.yt-live-chat-author-chip[type='member'] {
|
||||
color: var(--yt-live-chat-sponsor-color);
|
||||
}
|
||||
|
||||
#chip-badges.yt-live-chat-author-chip:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
yt-live-chat-author-chip[is-highlighted] #chat-badges.yt-live-chat-author-chip:not(:empty) {
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
yt-live-chat-author-badge-renderer.yt-live-chat-author-chip {
|
||||
margin: 0 0 0 2px;
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
yt-live-chat-author-chip[is-highlighted] #chip-badges.yt-live-chat-author-chip yt-live-chat-author-badge-renderer.yt-live-chat-author-chip {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#chip-badges.yt-live-chat-author-chip yt-live-chat-author-badge-renderer.yt-live-chat-author-chip:last-of-type {
|
||||
margin-right: -2px;
|
||||
}
|
||||
</style>
|
||||
298
frontend/src/components/ChatRenderer/Ticker.vue
Normal file
@@ -0,0 +1,298 @@
|
||||
<template>
|
||||
<yt-live-chat-ticker-renderer>
|
||||
<div id="container" dir="ltr" class="style-scope yt-live-chat-ticker-renderer">
|
||||
<div id="items" class="style-scope yt-live-chat-ticker-renderer">
|
||||
<template v-for="message in messages">
|
||||
<yt-live-chat-ticker-paid-message-item-renderer :key="message.id" v-if="needToShow(message)"
|
||||
tabindex="0" class="style-scope yt-live-chat-ticker-renderer" style="overflow: hidden;"
|
||||
@click="onItemClick(message)"
|
||||
>
|
||||
<div id="container" dir="ltr" class="style-scope yt-live-chat-ticker-paid-message-item-renderer"
|
||||
:style="{
|
||||
background: getBgColor(message),
|
||||
}"
|
||||
>
|
||||
<div id="content" class="style-scope yt-live-chat-ticker-paid-message-item-renderer" :style="{
|
||||
color: getColor(message)
|
||||
}">
|
||||
<img-shadow id="author-photo" height="24" width="24" class="style-scope yt-live-chat-ticker-paid-message-item-renderer"
|
||||
:imgUrl="message.avatarUrl"
|
||||
></img-shadow>
|
||||
<span id="text" dir="ltr" class="style-scope yt-live-chat-ticker-paid-message-item-renderer">{{getText(message)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</yt-live-chat-ticker-paid-message-item-renderer>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="pinnedMessage">
|
||||
<legacy-paid-message :key="pinnedMessage.id" v-if="pinnedMessage.type === MESSAGE_TYPE_MEMBER"
|
||||
class="style-scope yt-live-chat-ticker-renderer"
|
||||
:avatarUrl="pinnedMessage.avatarUrl" :title="pinnedMessage.title" :content="pinnedMessage.content"
|
||||
:time="pinnedMessage.time"
|
||||
></legacy-paid-message>
|
||||
<paid-message :key="pinnedMessage.id" v-else
|
||||
class="style-scope yt-live-chat-ticker-renderer"
|
||||
:price="pinnedMessage.price" :avatarUrl="pinnedMessage.avatarUrl" :authorName="pinnedMessage.authorName"
|
||||
:time="pinnedMessage.time" :content="pinnedMessage.content"
|
||||
></paid-message>
|
||||
</template>
|
||||
</yt-live-chat-ticker-renderer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ImgShadow from './ImgShadow.vue'
|
||||
import LegacyPaidMessage from './LegacyPaidMessage.vue'
|
||||
import PaidMessage from './PaidMessage.vue'
|
||||
import * as constants from './constants'
|
||||
import {formatCurrency} from '@/utils'
|
||||
|
||||
export default {
|
||||
name: 'Ticker',
|
||||
components: {
|
||||
ImgShadow,
|
||||
LegacyPaidMessage,
|
||||
PaidMessage
|
||||
},
|
||||
props: {
|
||||
messages: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
MESSAGE_TYPE_MEMBER: constants.MESSAGE_TYPE_MEMBER,
|
||||
|
||||
updateTimerId: window.setInterval(this.updateProgress.bind(this), 1000),
|
||||
pinnedMessage: null
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.clearInterval(this.updateTimerId)
|
||||
},
|
||||
methods: {
|
||||
needToShow(message) {
|
||||
let pinTime
|
||||
if (message.type === constants.MESSAGE_TYPE_MEMBER) {
|
||||
pinTime = 2
|
||||
} else {
|
||||
let config = constants.getPriceConfig(message.price)
|
||||
pinTime = config.pinTime
|
||||
}
|
||||
return (new Date() - message.addTime) / (60 * 1000) < pinTime
|
||||
},
|
||||
getBgColor(message) {
|
||||
let color1, color2, pinTime
|
||||
if (message.type === constants.MESSAGE_TYPE_MEMBER) {
|
||||
color1 = 'rgba(15,157,88,1)'
|
||||
color2 = 'rgba(11,128,67,1)'
|
||||
pinTime = 2
|
||||
} else {
|
||||
let config = constants.getPriceConfig(message.type === constants.MESSAGE_TYPE_MEMBER ? 28 : message.price)
|
||||
color1 = config.colors.contentBg
|
||||
color2 = config.colors.headerBg
|
||||
pinTime = config.pinTime
|
||||
}
|
||||
let progress = (1 - (new Date() - message.addTime) / (60 * 1000) / pinTime) * 100
|
||||
if (progress < 0) {
|
||||
progress = 0
|
||||
} else if (progress > 100) {
|
||||
progress = 100
|
||||
}
|
||||
return `linear-gradient(90deg, ${color1}, ${color1} ${progress}%, ${color2} ${progress}%, ${color2})`
|
||||
},
|
||||
getColor(message) {
|
||||
if (message.type === constants.MESSAGE_TYPE_MEMBER) {
|
||||
return 'rgb(255,255,255)'
|
||||
}
|
||||
return constants.getPriceConfig(message.price).colors.header
|
||||
},
|
||||
getText(message) {
|
||||
if (message.type === constants.MESSAGE_TYPE_MEMBER) {
|
||||
return 'Member'
|
||||
}
|
||||
return 'CN¥' + formatCurrency(message.price)
|
||||
},
|
||||
updateProgress() {
|
||||
let curTime = new Date()
|
||||
for (let i = 0; i < this.messages.length;) {
|
||||
let pinTime
|
||||
if (this.messages[i].type === constants.MESSAGE_TYPE_MEMBER) {
|
||||
pinTime = 2
|
||||
} else {
|
||||
let config = constants.getPriceConfig(this.messages[i].price)
|
||||
pinTime = config.pinTime
|
||||
}
|
||||
if ((curTime - this.messages[i].addTime) / (60 * 1000) >= pinTime) {
|
||||
if (this.pinnedMessage == this.messages[i]) {
|
||||
this.pinnedMessage = null
|
||||
}
|
||||
this.messages.splice(i, 1)
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
this.$forceUpdate()
|
||||
},
|
||||
onItemClick(message) {
|
||||
if (this.pinnedMessage == message) {
|
||||
this.pinnedMessage = null
|
||||
} else {
|
||||
this.pinnedMessage = message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- yt-live-chat-ticker-renderer -->
|
||||
<style>
|
||||
canvas.yt-live-chat-ticker-renderer, caption.yt-live-chat-ticker-renderer, center.yt-live-chat-ticker-renderer, cite.yt-live-chat-ticker-renderer, code.yt-live-chat-ticker-renderer, dd.yt-live-chat-ticker-renderer, del.yt-live-chat-ticker-renderer, dfn.yt-live-chat-ticker-renderer, div.yt-live-chat-ticker-renderer, dl.yt-live-chat-ticker-renderer, dt.yt-live-chat-ticker-renderer, em.yt-live-chat-ticker-renderer, embed.yt-live-chat-ticker-renderer, fieldset.yt-live-chat-ticker-renderer, font.yt-live-chat-ticker-renderer, form.yt-live-chat-ticker-renderer, h1.yt-live-chat-ticker-renderer, h2.yt-live-chat-ticker-renderer, h3.yt-live-chat-ticker-renderer, h4.yt-live-chat-ticker-renderer, h5.yt-live-chat-ticker-renderer, h6.yt-live-chat-ticker-renderer, hr.yt-live-chat-ticker-renderer, i.yt-live-chat-ticker-renderer, iframe.yt-live-chat-ticker-renderer, img.yt-live-chat-ticker-renderer, ins.yt-live-chat-ticker-renderer, kbd.yt-live-chat-ticker-renderer, label.yt-live-chat-ticker-renderer, legend.yt-live-chat-ticker-renderer, li.yt-live-chat-ticker-renderer, menu.yt-live-chat-ticker-renderer, object.yt-live-chat-ticker-renderer, ol.yt-live-chat-ticker-renderer, p.yt-live-chat-ticker-renderer, pre.yt-live-chat-ticker-renderer, q.yt-live-chat-ticker-renderer, s.yt-live-chat-ticker-renderer, samp.yt-live-chat-ticker-renderer, small.yt-live-chat-ticker-renderer, span.yt-live-chat-ticker-renderer, strike.yt-live-chat-ticker-renderer, strong.yt-live-chat-ticker-renderer, sub.yt-live-chat-ticker-renderer, sup.yt-live-chat-ticker-renderer, table.yt-live-chat-ticker-renderer, tbody.yt-live-chat-ticker-renderer, td.yt-live-chat-ticker-renderer, tfoot.yt-live-chat-ticker-renderer, th.yt-live-chat-ticker-renderer, thead.yt-live-chat-ticker-renderer, tr.yt-live-chat-ticker-renderer, tt.yt-live-chat-ticker-renderer, u.yt-live-chat-ticker-renderer, ul.yt-live-chat-ticker-renderer, var.yt-live-chat-ticker-renderer {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.yt-live-chat-ticker-renderer[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
yt-live-chat-ticker-renderer {
|
||||
display: block;
|
||||
background-color: var(--yt-live-chat-header-background-color);
|
||||
}
|
||||
|
||||
#container.yt-live-chat-ticker-renderer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#items.yt-live-chat-ticker-renderer {
|
||||
height: 32px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
padding: 0 24px 8px 24px;
|
||||
}
|
||||
|
||||
#items.yt-live-chat-ticker-renderer>*.yt-live-chat-ticker-renderer {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
#left-arrow-container.yt-live-chat-ticker-renderer {
|
||||
background: linear-gradient(to right, var(--yt-live-chat-ticker-arrow-background) 0, var(--yt-live-chat-ticker-arrow-background) 52px, transparent 60px);
|
||||
left: 0;
|
||||
padding: 0 16px 0 12px;
|
||||
}
|
||||
|
||||
#right-arrow-container.yt-live-chat-ticker-renderer {
|
||||
background: linear-gradient(to left, var(--yt-live-chat-ticker-arrow-background) 0, var(--yt-live-chat-ticker-arrow-background) 52px, transparent 60px);
|
||||
right: 0;
|
||||
padding: 0 12px 0 16px;
|
||||
}
|
||||
|
||||
#container.yt-live-chat-ticker-renderer:hover #left-arrow-container.yt-live-chat-ticker-renderer, #container.yt-live-chat-ticker-renderer:hover #right-arrow-container.yt-live-chat-ticker-renderer {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#left-arrow-container.yt-live-chat-ticker-renderer, #right-arrow-container.yt-live-chat-ticker-renderer {
|
||||
height: 32px;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
transition: opacity 0.3s 0.1s;
|
||||
}
|
||||
|
||||
yt-icon.yt-live-chat-ticker-renderer {
|
||||
background-color: #2196f3;
|
||||
border-radius: 999px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
height: 24px;
|
||||
padding: 4px;
|
||||
width: 24px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- yt-live-chat-ticker-paid-message-item-renderer -->
|
||||
<style>
|
||||
canvas.yt-live-chat-ticker-paid-message-item-renderer, caption.yt-live-chat-ticker-paid-message-item-renderer, center.yt-live-chat-ticker-paid-message-item-renderer, cite.yt-live-chat-ticker-paid-message-item-renderer, code.yt-live-chat-ticker-paid-message-item-renderer, dd.yt-live-chat-ticker-paid-message-item-renderer, del.yt-live-chat-ticker-paid-message-item-renderer, dfn.yt-live-chat-ticker-paid-message-item-renderer, div.yt-live-chat-ticker-paid-message-item-renderer, dl.yt-live-chat-ticker-paid-message-item-renderer, dt.yt-live-chat-ticker-paid-message-item-renderer, em.yt-live-chat-ticker-paid-message-item-renderer, embed.yt-live-chat-ticker-paid-message-item-renderer, fieldset.yt-live-chat-ticker-paid-message-item-renderer, font.yt-live-chat-ticker-paid-message-item-renderer, form.yt-live-chat-ticker-paid-message-item-renderer, h1.yt-live-chat-ticker-paid-message-item-renderer, h2.yt-live-chat-ticker-paid-message-item-renderer, h3.yt-live-chat-ticker-paid-message-item-renderer, h4.yt-live-chat-ticker-paid-message-item-renderer, h5.yt-live-chat-ticker-paid-message-item-renderer, h6.yt-live-chat-ticker-paid-message-item-renderer, hr.yt-live-chat-ticker-paid-message-item-renderer, i.yt-live-chat-ticker-paid-message-item-renderer, iframe.yt-live-chat-ticker-paid-message-item-renderer, img.yt-live-chat-ticker-paid-message-item-renderer, ins.yt-live-chat-ticker-paid-message-item-renderer, kbd.yt-live-chat-ticker-paid-message-item-renderer, label.yt-live-chat-ticker-paid-message-item-renderer, legend.yt-live-chat-ticker-paid-message-item-renderer, li.yt-live-chat-ticker-paid-message-item-renderer, menu.yt-live-chat-ticker-paid-message-item-renderer, object.yt-live-chat-ticker-paid-message-item-renderer, ol.yt-live-chat-ticker-paid-message-item-renderer, p.yt-live-chat-ticker-paid-message-item-renderer, pre.yt-live-chat-ticker-paid-message-item-renderer, q.yt-live-chat-ticker-paid-message-item-renderer, s.yt-live-chat-ticker-paid-message-item-renderer, samp.yt-live-chat-ticker-paid-message-item-renderer, small.yt-live-chat-ticker-paid-message-item-renderer, span.yt-live-chat-ticker-paid-message-item-renderer, strike.yt-live-chat-ticker-paid-message-item-renderer, strong.yt-live-chat-ticker-paid-message-item-renderer, sub.yt-live-chat-ticker-paid-message-item-renderer, sup.yt-live-chat-ticker-paid-message-item-renderer, table.yt-live-chat-ticker-paid-message-item-renderer, tbody.yt-live-chat-ticker-paid-message-item-renderer, td.yt-live-chat-ticker-paid-message-item-renderer, tfoot.yt-live-chat-ticker-paid-message-item-renderer, th.yt-live-chat-ticker-paid-message-item-renderer, thead.yt-live-chat-ticker-paid-message-item-renderer, tr.yt-live-chat-ticker-paid-message-item-renderer, tt.yt-live-chat-ticker-paid-message-item-renderer, u.yt-live-chat-ticker-paid-message-item-renderer, ul.yt-live-chat-ticker-paid-message-item-renderer, var.yt-live-chat-ticker-paid-message-item-renderer {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.yt-live-chat-ticker-paid-message-item-renderer[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
canvas.yt-live-chat-ticker-paid-message-item-renderer, caption.yt-live-chat-ticker-paid-message-item-renderer, center.yt-live-chat-ticker-paid-message-item-renderer, cite.yt-live-chat-ticker-paid-message-item-renderer, code.yt-live-chat-ticker-paid-message-item-renderer, dd.yt-live-chat-ticker-paid-message-item-renderer, del.yt-live-chat-ticker-paid-message-item-renderer, dfn.yt-live-chat-ticker-paid-message-item-renderer, div.yt-live-chat-ticker-paid-message-item-renderer, dl.yt-live-chat-ticker-paid-message-item-renderer, dt.yt-live-chat-ticker-paid-message-item-renderer, em.yt-live-chat-ticker-paid-message-item-renderer, embed.yt-live-chat-ticker-paid-message-item-renderer, fieldset.yt-live-chat-ticker-paid-message-item-renderer, font.yt-live-chat-ticker-paid-message-item-renderer, form.yt-live-chat-ticker-paid-message-item-renderer, h1.yt-live-chat-ticker-paid-message-item-renderer, h2.yt-live-chat-ticker-paid-message-item-renderer, h3.yt-live-chat-ticker-paid-message-item-renderer, h4.yt-live-chat-ticker-paid-message-item-renderer, h5.yt-live-chat-ticker-paid-message-item-renderer, h6.yt-live-chat-ticker-paid-message-item-renderer, hr.yt-live-chat-ticker-paid-message-item-renderer, i.yt-live-chat-ticker-paid-message-item-renderer, iframe.yt-live-chat-ticker-paid-message-item-renderer, img.yt-live-chat-ticker-paid-message-item-renderer, ins.yt-live-chat-ticker-paid-message-item-renderer, kbd.yt-live-chat-ticker-paid-message-item-renderer, label.yt-live-chat-ticker-paid-message-item-renderer, legend.yt-live-chat-ticker-paid-message-item-renderer, li.yt-live-chat-ticker-paid-message-item-renderer, menu.yt-live-chat-ticker-paid-message-item-renderer, object.yt-live-chat-ticker-paid-message-item-renderer, ol.yt-live-chat-ticker-paid-message-item-renderer, p.yt-live-chat-ticker-paid-message-item-renderer, pre.yt-live-chat-ticker-paid-message-item-renderer, q.yt-live-chat-ticker-paid-message-item-renderer, s.yt-live-chat-ticker-paid-message-item-renderer, samp.yt-live-chat-ticker-paid-message-item-renderer, small.yt-live-chat-ticker-paid-message-item-renderer, span.yt-live-chat-ticker-paid-message-item-renderer, strike.yt-live-chat-ticker-paid-message-item-renderer, strong.yt-live-chat-ticker-paid-message-item-renderer, sub.yt-live-chat-ticker-paid-message-item-renderer, sup.yt-live-chat-ticker-paid-message-item-renderer, table.yt-live-chat-ticker-paid-message-item-renderer, tbody.yt-live-chat-ticker-paid-message-item-renderer, td.yt-live-chat-ticker-paid-message-item-renderer, tfoot.yt-live-chat-ticker-paid-message-item-renderer, th.yt-live-chat-ticker-paid-message-item-renderer, thead.yt-live-chat-ticker-paid-message-item-renderer, tr.yt-live-chat-ticker-paid-message-item-renderer, tt.yt-live-chat-ticker-paid-message-item-renderer, u.yt-live-chat-ticker-paid-message-item-renderer, ul.yt-live-chat-ticker-paid-message-item-renderer, var.yt-live-chat-ticker-paid-message-item-renderer {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.yt-live-chat-ticker-paid-message-item-renderer[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
yt-live-chat-ticker-paid-message-item-renderer {
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
transition: width 0.2s;
|
||||
vertical-align: top;
|
||||
cursor: pointer;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#container.yt-live-chat-ticker-paid-message-item-renderer {
|
||||
border-radius: 999px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
yt-live-chat-ticker-paid-message-item-renderer.sliding-down #container.yt-live-chat-ticker-paid-message-item-renderer {
|
||||
opacity: 0.5;
|
||||
transform: translateY(44px);
|
||||
transition: opacity 0.2s, transform 0.2s cubic-bezier(0.4, 0.0, 1, 1);
|
||||
}
|
||||
|
||||
yt-live-chat-ticker-paid-message-item-renderer.collapsing {
|
||||
margin-right: 0;
|
||||
transition: margin-right 0.2s cubic-bezier(0.4, 0.0, 0.2, 1), width 0.2s cubic-bezier(0.4, 0.0, 0.2, 1);
|
||||
}
|
||||
|
||||
yt-live-chat-ticker-paid-message-item-renderer[dimmed] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
yt-img-shadow.yt-live-chat-ticker-paid-message-item-renderer {
|
||||
margin-right: -4px;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
#content.yt-live-chat-ticker-paid-message-item-renderer {
|
||||
height: 24px;
|
||||
display: flex;
|
||||
-ms-flex-direction: row;
|
||||
-webkit-flex-direction: row;
|
||||
flex-direction: row;
|
||||
-ms-flex-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#text.yt-live-chat-ticker-paid-message-item-renderer {
|
||||
margin: 0 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
yt-live-chat-ticker-paid-message-item-renderer[is-deleted] #author-photo.yt-live-chat-ticker-paid-message-item-renderer {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
120
frontend/src/components/ChatRenderer/constants.js
Normal file
@@ -0,0 +1,120 @@
|
||||
export const AUTHRO_TYPE_NORMAL = 0
|
||||
export const AUTHRO_TYPE_MEMBER = 1
|
||||
export const AUTHRO_TYPE_ADMIN = 2
|
||||
export const AUTHRO_TYPE_OWNER = 3
|
||||
|
||||
export const AUTHOR_TYPE_TO_TEXT = [
|
||||
'',
|
||||
'member', // 舰队
|
||||
'moderator', // 房管
|
||||
'owner' // 主播
|
||||
]
|
||||
|
||||
export const GUARD_LEVEL_TO_TEXT = [
|
||||
'',
|
||||
'总督',
|
||||
'提督',
|
||||
'舰长'
|
||||
]
|
||||
|
||||
export const MESSAGE_TYPE_TEXT = 0
|
||||
export const MESSAGE_TYPE_MEMBER = 1
|
||||
export const MESSAGE_TYPE_SUPER_CHAT = 2
|
||||
|
||||
// 美元 -> 人民币 汇率
|
||||
const EXCHANGE_RATE = 7
|
||||
export const PRICE_CONFIGS = [
|
||||
{ // $100红
|
||||
price: 100 * EXCHANGE_RATE,
|
||||
colors: {
|
||||
contentBg: 'rgba(230,33,23,1)',
|
||||
headerBg: 'rgba(208,0,0,1)',
|
||||
header: 'rgba(255,255,255,1)',
|
||||
authorName: 'rgba(255,255,255,0.701961)',
|
||||
time: 'rgba(255,255,255,0.501961)',
|
||||
content: 'rgba(255,255,255,1)'
|
||||
},
|
||||
pinTime: 60
|
||||
},
|
||||
{ // $50品红
|
||||
price: 50 * EXCHANGE_RATE,
|
||||
colors: {
|
||||
contentBg: 'rgba(233,30,99,1)',
|
||||
headerBg: 'rgba(194,24,91,1)',
|
||||
header: 'rgba(255,255,255,1)',
|
||||
authorName: 'rgba(255,255,255,0.701961)',
|
||||
time: 'rgba(255,255,255,0.501961)',
|
||||
content: 'rgba(255,255,255,1)'
|
||||
},
|
||||
pinTime: 30
|
||||
},
|
||||
{ // $20橙
|
||||
price: 20 * EXCHANGE_RATE,
|
||||
colors: {
|
||||
contentBg: 'rgba(245,124,0,1)',
|
||||
headerBg: 'rgba(230,81,0,1)',
|
||||
header: 'rgba(255,255,255,0.87451)',
|
||||
authorName: 'rgba(255,255,255,0.701961)',
|
||||
time: 'rgba(255,255,255,0.501961)',
|
||||
content: 'rgba(255,255,255,0.87451)'
|
||||
},
|
||||
pinTime: 10
|
||||
},
|
||||
{ // $10黄
|
||||
price: 10 * EXCHANGE_RATE,
|
||||
colors: {
|
||||
contentBg: 'rgba(255,202,40,1)',
|
||||
headerBg: 'rgba(255,179,0,1)',
|
||||
header: 'rgba(0,0,0,0.87451)',
|
||||
authorName: 'rgba(0,0,0,0.541176)',
|
||||
time: 'rgba(0,0,0,0.501961)',
|
||||
content: 'rgba(0,0,0,0.87451)'
|
||||
},
|
||||
pinTime: 5
|
||||
},
|
||||
{ // $5绿
|
||||
price: 5 * EXCHANGE_RATE,
|
||||
colors: {
|
||||
contentBg: 'rgba(29,233,182,1)',
|
||||
headerBg: 'rgba(0,191,165,1)',
|
||||
header: 'rgba(0,0,0,1)',
|
||||
authorName: 'rgba(0,0,0,0.541176)',
|
||||
time: 'rgba(0,0,0,0.501961)',
|
||||
content: 'rgba(0,0,0,1)'
|
||||
},
|
||||
pinTime: 2
|
||||
},
|
||||
{ // $2浅蓝
|
||||
price: 2 * EXCHANGE_RATE,
|
||||
colors: {
|
||||
contentBg: 'rgba(0,229,255,1)',
|
||||
headerBg: 'rgba(0,184,212,1)',
|
||||
header: 'rgba(0,0,0,1)',
|
||||
authorName: 'rgba(0,0,0,0.701961)',
|
||||
time: 'rgba(0,0,0,0.501961)',
|
||||
content: 'rgba(0,0,0,1)'
|
||||
},
|
||||
pinTime: 0
|
||||
},
|
||||
{ // $1蓝
|
||||
price: 1 * EXCHANGE_RATE,
|
||||
colors: {
|
||||
contentBg: 'rgba(30,136,229,1)',
|
||||
headerBg: 'rgba(21,101,192,1)',
|
||||
header: 'rgba(255,255,255,1)',
|
||||
authorName: 'rgba(255,255,255,0.701961)',
|
||||
time: 'rgba(255,255,255,0.501961)',
|
||||
content: 'rgba(255,255,255,1)'
|
||||
},
|
||||
pinTime: 0
|
||||
}
|
||||
]
|
||||
|
||||
export function getPriceConfig (price) {
|
||||
for (const config of PRICE_CONFIGS) {
|
||||
if (price >= config.price) {
|
||||
return config
|
||||
}
|
||||
}
|
||||
return PRICE_CONFIGS[PRICE_CONFIGS.length - 1]
|
||||
}
|
||||
627
frontend/src/components/ChatRenderer/index.vue
Normal file
@@ -0,0 +1,627 @@
|
||||
<template>
|
||||
<yt-live-chat-renderer class="style-scope yt-live-chat-app" style="--scrollbar-width:11px;" hide-timestamps>
|
||||
<ticker class="style-scope yt-live-chat-renderer" :messages="paidMessages" :hidden="paidMessages.length === 0"></ticker>
|
||||
<yt-live-chat-item-list-renderer class="style-scope yt-live-chat-renderer" allow-scroll>
|
||||
<div id="item-scroller" ref="scroller" class="style-scope yt-live-chat-item-list-renderer animated" @scroll="onScroll">
|
||||
<div id="item-offset" class="style-scope yt-live-chat-item-list-renderer" style="height: 1800px;">
|
||||
<div id="items" class="style-scope yt-live-chat-item-list-renderer" style="overflow: hidden; transform: translateY(0px);">
|
||||
<template v-for="message in messages">
|
||||
<text-message :key="message.id" v-if="message.type === MESSAGE_TYPE_TEXT"
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
:avatarUrl="message.avatarUrl" :time="message.time" :authorName="message.authorName"
|
||||
:authorType="message.authorType" :content="message.content" :privilegeType="message.privilegeType"
|
||||
:repeated="message.repeated"
|
||||
></text-message>
|
||||
<legacy-paid-message :key="message.id" v-else-if="message.type === MESSAGE_TYPE_MEMBER"
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
:avatarUrl="message.avatarUrl" :title="message.title" :content="message.content"
|
||||
:time="message.time"
|
||||
></legacy-paid-message>
|
||||
<paid-message :key="message.id" v-else
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
:price="message.price" :avatarUrl="message.avatarUrl" :authorName="message.authorName"
|
||||
:time="message.time" :content="message.content"
|
||||
></paid-message>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</yt-live-chat-item-list-renderer>
|
||||
</yt-live-chat-renderer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Ticker from './Ticker.vue'
|
||||
import TextMessage from './TextMessage.vue'
|
||||
import LegacyPaidMessage from './LegacyPaidMessage.vue'
|
||||
import PaidMessage from './PaidMessage.vue'
|
||||
import * as constants from './constants'
|
||||
|
||||
export default {
|
||||
name: 'ChatRenderer',
|
||||
components: {
|
||||
Ticker,
|
||||
TextMessage,
|
||||
LegacyPaidMessage,
|
||||
PaidMessage
|
||||
},
|
||||
props: {
|
||||
paidMessages: Array,
|
||||
messages: Array,
|
||||
css: String
|
||||
},
|
||||
data() {
|
||||
let styleElement = document.createElement('style')
|
||||
document.head.appendChild(styleElement)
|
||||
return {
|
||||
MESSAGE_TYPE_TEXT: constants.MESSAGE_TYPE_TEXT,
|
||||
MESSAGE_TYPE_MEMBER: constants.MESSAGE_TYPE_MEMBER,
|
||||
|
||||
styleElement,
|
||||
canAutoScroll: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.styleElement.innerText = this.css
|
||||
this.scrollToBottom()
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.head.removeChild(this.styleElement)
|
||||
},
|
||||
updated() {
|
||||
if (this.canAutoScroll) {
|
||||
this.scrollToBottom()
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
css(val) {
|
||||
this.styleElement.innerText = val
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
scrollToBottom() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
yt-live-chat-renderer, yt-live-chat-item-list-renderer #item-scroller {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
yt-live-chat-renderer ::-webkit-scrollbar {
|
||||
content: '';
|
||||
}
|
||||
|
||||
yt-live-chat-renderer ::-webkit-scrollbar-thumb {
|
||||
background-color: hsla(0, 0%, 53.3%, .2);
|
||||
border: 2px solid #fcfcfc;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
yt-live-chat-renderer ::-webkit-scrollbar-track {
|
||||
background-color: #fcfcfc;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- html -->
|
||||
<style>
|
||||
html:not(.style-scope) {
|
||||
--yt-live-chat-background-color: hsl(0, 0%, 100%);
|
||||
--yt-live-chat-action-panel-background-color: hsla(0, 0%, 93.3%, .4);
|
||||
--yt-live-chat-action-panel-background-color-transparent: hsla(0, 0%, 97%, .8);
|
||||
--yt-live-chat-mode-change-background-color: hsla(0, 0%, 93.3%, .4);
|
||||
--yt-live-chat-primary-text-color: hsl(0, 0%, 6.7%);
|
||||
--yt-live-chat-secondary-text-color: hsla(0, 0%, 6.7%, .6);
|
||||
--yt-live-chat-tertiary-text-color: hsla(0, 0%, 6.7%, .4);
|
||||
--yt-live-chat-text-input-field-inactive-underline-color: #b8b8b8;
|
||||
--yt-live-chat-text-input-field-placeholder-color: hsla(0, 0%, 6.7%, .6);
|
||||
--yt-live-chat-icon-button-color: hsla(0, 0%, 6.7%, .4);
|
||||
--yt-live-chat-enabled-send-button-color: #4285f4;
|
||||
--yt-live-chat-disabled-icon-button-color: hsla(0, 0%, 6.7%, .2);
|
||||
--yt-live-chat-picker-button-color: hsla(0, 0%, 6.7%, .4);
|
||||
--yt-live-chat-picker-button-active-color: hsla(0, 0%, 6.7%, .8);
|
||||
--yt-live-chat-picker-button-disabled-color: var(--yt-live-chat-disabled-icon-button-color);
|
||||
--yt-live-chat-picker-button-hover-color: hsla(0, 0%, 6.7%, .6);
|
||||
--yt-live-chat-mention-background-color: #ff5722;
|
||||
--yt-live-chat-mention-text-color: hsl(0, 0%, 100%);
|
||||
--yt-live-chat-deleted-message-color: rgba(0, 0, 0, .5);
|
||||
--yt-live-chat-deleted-message-bar-color: rgba(11, 11, 11, .2);
|
||||
--yt-live-chat-disabled-button-background-color: hsl(0, 0%, 93.3%);
|
||||
--yt-live-chat-disabled-button-text-color: hsla(0, 0%, 6.7%, .4);
|
||||
--yt-live-chat-sub-panel-background-color: hsl(0, 0%, 93.3%);
|
||||
--yt-live-chat-sub-panel-background-color-transparent: hsla(0, 0%, 93%, .7);
|
||||
--yt-live-chat-header-background-color: hsla(0, 0%, 93.3%, .4);
|
||||
--yt-live-chat-header-button-color: hsl(0, 0%, 6.7%);
|
||||
--yt-live-chat-error-message-color: hsl(10, 51%, 49%);
|
||||
--yt-live-chat-reconnect-message-color: hsla(0, 0%, 7%, 0.2);
|
||||
--yt-live-chat-moderator-color: hsl(225, 84%, 66%);
|
||||
--yt-live-chat-owner-color: hsl(40, 76%, 55%);
|
||||
--yt-live-chat-author-chip-owner-text-color: rgba(0,0,0,0.87);
|
||||
--yt-live-chat-author-chip-verified-background-color: #CCCCCC;
|
||||
--yt-live-chat-author-chip-verified-text-color: #606060;
|
||||
--yt-live-chat-message-highlight-background-color: #f8f8f8;
|
||||
--yt-live-chat-sponsor-color: #107516;
|
||||
--yt-live-chat-overlay-color: hsla(0, 0%, 0%, 0.6);
|
||||
--yt-live-chat-dialog-background-color: hsl(0, 0%, 100%);
|
||||
--yt-live-chat-dialog-text-color: hsla(0, 0%, 6.7%, .6);
|
||||
--yt-live-chat-poll-choice-text-color: var(--yt-spec-text-secondary);
|
||||
--yt-live-chat-poll-choice-border-color: var(--yt-spec-10-percent-layer);
|
||||
--yt-live-chat-poll-choice-vote-bar-background-color: hsla(0, 0%, 93.3%, .8);
|
||||
--yt-live-chat-poll-choice-vote-bar-background-color-selected: #F2F8FF;
|
||||
--yt-live-chat-poll-choice-color-selected: #065FD4;
|
||||
--yt-live-chat-moderation-mode-hover-background-color: hsla(0, 0%, 6.7%, .2);
|
||||
--yt-live-chat-additional-inline-action-button-color: hsl(0, 0%, 100%);
|
||||
--yt-live-chat-additional-inline-action-button-background-color: hsla(0, 0%, 26%, 0.8);
|
||||
--yt-live-chat-additional-inline-action-button-background-color-hover: hsla(0, 0%, 26%, 1.0);
|
||||
--yt-formatted-string-emoji-size: 24px;
|
||||
--yt-live-chat-emoji-size: 24px;
|
||||
--yt-live-chat-text-input-field-suggestion-background-color: hsl(0, 0%, 100%);
|
||||
--yt-live-chat-text-input-field-suggestion-background-color-hover: #eee;
|
||||
--yt-live-chat-text-input-field-suggestion-text-color: #666;
|
||||
--yt-live-chat-text-input-field-suggestion-text-color-hover: #333;
|
||||
--yt-live-chat-ticker-arrow-background: hsl(0, 0%, 97.3%);
|
||||
--yt-emoji-picker-category-background-color: var(--yt-live-chat-action-panel-background-color-transparent);
|
||||
--yt-emoji-picker-category-color: var(--yt-live-chat-secondary-text-color);
|
||||
--yt-emoji-picker-category-button-color: var(--yt-live-chat-picker-button-color);
|
||||
--yt-emoji-picker-search-background-color: hsla(0, 0%, 100%, .6);
|
||||
--yt-emoji-picker-search-color: hsla(0, 0%, 6.7%, .8);
|
||||
--yt-emoji-picker-search-placeholder-color: hsla(0, 0%, 6.7%, .6);
|
||||
--yt-live-chat-slider-active-color: #2196f3;
|
||||
--yt-live-chat-slider-container-color: #c8c8c8;
|
||||
--yt-live-chat-slider-markers-color: #505050;
|
||||
--yt-live-chat-toast-background-color: hsl(0, 0%, 20%);
|
||||
--yt-live-chat-toast-text-color: hsl(0, 0%, 100%);
|
||||
--yt-live-chat-automod-button-background-color: hsl(0, 0%, 93.3%);
|
||||
--yt-live-chat-automod-button-background-color-hover: hsla(0, 0%, 6.7%, .2);
|
||||
--yt-live-chat-countdown-opacity: 0.3;
|
||||
--yt-live-chat-shimmer-background-color: rgba(136, 136, 136, 0.2);
|
||||
--yt-live-chat-shimmer-linear-gradient: linear-gradient(0deg, rgba(255, 255, 255, 0) 40%, rgba(255, 255, 255, 0.5) 50%, rgba(255, 255, 255, 0) 65%);
|
||||
--yt-live-chat-vem-background-color: hsl(0, 0%, 93.3%);
|
||||
--yt-live-chat-upsell-dialog-renderer-button-padding: 10px 16px;
|
||||
--yt-live-chat-product-picker-icon-color: rgba(17, 17, 17, 0.6);
|
||||
--yt-live-chat-product-picker-hover-color: rgba(17, 17, 16, 0.1);
|
||||
--yt-live-chat-product-picker-disabled-icon-color: rgba(17, 17, 17, 0.4);
|
||||
--yt-pdg-paid-stickers-tab-selection-bar-color: #065FD4;
|
||||
--yt-pdg-paid-stickers-author-name-font-size: 13px;
|
||||
--yt-pdg-paid-stickers-margin-left: 38px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- yt-live-chat-app -->
|
||||
<style>
|
||||
canvas.yt-live-chat-app, caption.yt-live-chat-app, center.yt-live-chat-app, cite.yt-live-chat-app, code.yt-live-chat-app, dd.yt-live-chat-app, del.yt-live-chat-app, dfn.yt-live-chat-app, div.yt-live-chat-app, dl.yt-live-chat-app, dt.yt-live-chat-app, em.yt-live-chat-app, embed.yt-live-chat-app, fieldset.yt-live-chat-app, font.yt-live-chat-app, form.yt-live-chat-app, h1.yt-live-chat-app, h2.yt-live-chat-app, h3.yt-live-chat-app, h4.yt-live-chat-app, h5.yt-live-chat-app, h6.yt-live-chat-app, hr.yt-live-chat-app, i.yt-live-chat-app, iframe.yt-live-chat-app, img.yt-live-chat-app, ins.yt-live-chat-app, kbd.yt-live-chat-app, label.yt-live-chat-app, legend.yt-live-chat-app, li.yt-live-chat-app, menu.yt-live-chat-app, object.yt-live-chat-app, ol.yt-live-chat-app, p.yt-live-chat-app, pre.yt-live-chat-app, q.yt-live-chat-app, s.yt-live-chat-app, samp.yt-live-chat-app, small.yt-live-chat-app, span.yt-live-chat-app, strike.yt-live-chat-app, strong.yt-live-chat-app, sub.yt-live-chat-app, sup.yt-live-chat-app, table.yt-live-chat-app, tbody.yt-live-chat-app, td.yt-live-chat-app, tfoot.yt-live-chat-app, th.yt-live-chat-app, thead.yt-live-chat-app, tr.yt-live-chat-app, tt.yt-live-chat-app, u.yt-live-chat-app, ul.yt-live-chat-app, var.yt-live-chat-app {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.yt-live-chat-app[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
yt-live-chat-app {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
--yt-report-form-modal-renderer-min-width: 0;
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#contents.yt-live-chat-app {
|
||||
/* yt-live-chat-renderer.yt-live-chat-app { */
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-ms-flex: 1 1 0.000000001px;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
-webkit-flex-basis: 0.000000001px;
|
||||
flex-basis: 0.000000001px;
|
||||
}
|
||||
|
||||
#contents.yt-live-chat-app>*.yt-live-chat-app {
|
||||
/* yt-live-chat-renderer.yt-live-chat-app { */
|
||||
-ms-flex: 1 1 0.000000001px;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
-webkit-flex-basis: 0.000000001px;
|
||||
flex-basis: 0.000000001px;
|
||||
}
|
||||
|
||||
yt-live-chat-app[dashboard-money-feed] #contents.yt-live-chat-app>yt-live-chat-message-renderer.yt-live-chat-app {
|
||||
font-size: 18px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- yt-live-chat-renderer -->
|
||||
<style>
|
||||
canvas.yt-live-chat-renderer, caption.yt-live-chat-renderer, center.yt-live-chat-renderer, cite.yt-live-chat-renderer, code.yt-live-chat-renderer, dd.yt-live-chat-renderer, del.yt-live-chat-renderer, dfn.yt-live-chat-renderer, div.yt-live-chat-renderer, dl.yt-live-chat-renderer, dt.yt-live-chat-renderer, em.yt-live-chat-renderer, embed.yt-live-chat-renderer, fieldset.yt-live-chat-renderer, font.yt-live-chat-renderer, form.yt-live-chat-renderer, h1.yt-live-chat-renderer, h2.yt-live-chat-renderer, h3.yt-live-chat-renderer, h4.yt-live-chat-renderer, h5.yt-live-chat-renderer, h6.yt-live-chat-renderer, hr.yt-live-chat-renderer, i.yt-live-chat-renderer, iframe.yt-live-chat-renderer, img.yt-live-chat-renderer, ins.yt-live-chat-renderer, kbd.yt-live-chat-renderer, label.yt-live-chat-renderer, legend.yt-live-chat-renderer, li.yt-live-chat-renderer, menu.yt-live-chat-renderer, object.yt-live-chat-renderer, ol.yt-live-chat-renderer, p.yt-live-chat-renderer, pre.yt-live-chat-renderer, q.yt-live-chat-renderer, s.yt-live-chat-renderer, samp.yt-live-chat-renderer, small.yt-live-chat-renderer, span.yt-live-chat-renderer, strike.yt-live-chat-renderer, strong.yt-live-chat-renderer, sub.yt-live-chat-renderer, sup.yt-live-chat-renderer, table.yt-live-chat-renderer, tbody.yt-live-chat-renderer, td.yt-live-chat-renderer, tfoot.yt-live-chat-renderer, th.yt-live-chat-renderer, thead.yt-live-chat-renderer, tr.yt-live-chat-renderer, tt.yt-live-chat-renderer, u.yt-live-chat-renderer, ul.yt-live-chat-renderer, var.yt-live-chat-renderer {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.yt-live-chat-renderer[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
yt-live-chat-renderer {
|
||||
font-size: 13px;
|
||||
--yt-emoji-picker-renderer-height: 180px;
|
||||
--yt-button-default-text-color: var(--yt-live-chat-button-default-text-color);
|
||||
--yt-button-default-background-color: var(--yt-live-chat-button-default-background-color);
|
||||
--yt-button-dark-text-color: var(--yt-live-chat-button-dark-text-color);
|
||||
--yt-button-dark-background-color: var(--yt-live-chat-button-dark-background-color);
|
||||
--yt-button-payment-background-color: var(--yt-live-chat-sponsor-color);
|
||||
}
|
||||
|
||||
yt-live-chat-renderer {
|
||||
position: relative;
|
||||
background: var(--yt-live-chat-background-color);
|
||||
color: var(--yt-live-chat-primary-text-color);
|
||||
overflow: hidden;
|
||||
z-index: 0;
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
yt-live-chat-renderer[hide-timestamps] {
|
||||
--yt-live-chat-item-timestamp-display: none;
|
||||
}
|
||||
|
||||
#separator.yt-live-chat-renderer {
|
||||
border-bottom: var(--yt-live-chat-header-bottom-border, none);
|
||||
}
|
||||
|
||||
#content-pages.yt-live-chat-renderer {
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-ms-flex: 1 1 0.000000001px;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
-webkit-flex-basis: 0.000000001px;
|
||||
flex-basis: 0.000000001px;
|
||||
}
|
||||
|
||||
#panel-pages.yt-live-chat-renderer {
|
||||
max-height: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#contents.yt-live-chat-renderer {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#chat-messages.yt-live-chat-renderer, #contents.yt-live-chat-renderer, #item-list.yt-live-chat-renderer {
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-ms-flex: 1 1 0.000000001px;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
-webkit-flex-basis: 0.000000001px;
|
||||
flex-basis: 0.000000001px;
|
||||
}
|
||||
|
||||
#ticker.yt-live-chat-renderer {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#chat.yt-live-chat-renderer {
|
||||
position: relative;
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-ms-flex: 1 1 0.000000001px;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
-webkit-flex-basis: 0.000000001px;
|
||||
flex-basis: 0.000000001px;
|
||||
}
|
||||
|
||||
#chat.yt-live-chat-renderer::after {
|
||||
content: '';
|
||||
display: none;
|
||||
animation: gradient-slide 1.2s ease infinite;
|
||||
animation-name: gradient-slide;
|
||||
background-color: var(--yt-live-chat-shimmer-background-color);
|
||||
background-image: var(--yt-live-chat-shimmer-linear-gradient);
|
||||
background-size: 300% 300%;
|
||||
transform: rotateX(180deg);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
yt-live-chat-renderer[loading] #chat.yt-live-chat-renderer::after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
yt-live-chat-pinned-message-renderer.yt-live-chat-renderer {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
yt-live-chat-item-list-renderer.yt-live-chat-renderer, yt-live-chat-ninja-message-renderer.yt-live-chat-renderer {
|
||||
-ms-flex: 1 1 0.000000001px;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
-webkit-flex-basis: 0.000000001px;
|
||||
flex-basis: 0.000000001px;
|
||||
}
|
||||
|
||||
#action-panel.yt-live-chat-renderer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
yt-live-chat-renderer[has-action-panel-renderer] yt-live-chat-action-panel-renderer.yt-live-chat-renderer {
|
||||
animation: slideUp cubic-bezier(0.05, 0.00, 0.00, 1.00) forwards;
|
||||
animation-duration: 0.5s;
|
||||
}
|
||||
|
||||
#action-panel-backdrop.yt-live-chat-renderer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
yt-live-chat-renderer[has-action-panel-renderer] #action-panel-backdrop.yt-live-chat-renderer {
|
||||
visibility: visible;
|
||||
animation: fadeIn cubic-bezier(0.05, 0.00, 0.00, 1.00) forwards;
|
||||
animation-duration: 0.5s;
|
||||
}
|
||||
|
||||
#input-panel.yt-live-chat-renderer {
|
||||
-ms-flex: none;
|
||||
-webkit-flex: none;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
#input-panel.yt-live-chat-renderer:not(:empty) {
|
||||
border-top: var(--yt-live-chat-action-panel-top-border, none);
|
||||
}
|
||||
|
||||
.hide-on-collapse.yt-live-chat-renderer {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
yt-live-chat-renderer[collapsed] .hide-on-collapse.yt-live-chat-renderer {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#loading.yt-live-chat-renderer {
|
||||
height: 387px;
|
||||
background-color: var(--yt-live-chat-action-panel-background-color);
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-ms-flex-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
-ms-flex-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#loading.yt-live-chat-renderer>paper-spinner-lite.yt-live-chat-renderer {
|
||||
--paper-spinner-color: var(--yt-live-chat-primary-text-color);
|
||||
}
|
||||
|
||||
#nitrate-promo.yt-live-chat-renderer>*.yt-live-chat-renderer {
|
||||
background: var(--yt-live-chat-overlay-color);
|
||||
z-index: 3;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
@keyframes gradient-slide {
|
||||
0% {
|
||||
background-position: 100% 100%;
|
||||
}
|
||||
to {
|
||||
background-position: 0% 0%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
0% {
|
||||
transform: translateY(15%);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
background-color: transparent;
|
||||
}
|
||||
100% {
|
||||
background-color: rgba(0, 0, 0, 0.60);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- yt-live-chat-item-list-renderer -->
|
||||
<style>
|
||||
canvas.yt-live-chat-item-list-renderer, caption.yt-live-chat-item-list-renderer, center.yt-live-chat-item-list-renderer, cite.yt-live-chat-item-list-renderer, code.yt-live-chat-item-list-renderer, dd.yt-live-chat-item-list-renderer, del.yt-live-chat-item-list-renderer, dfn.yt-live-chat-item-list-renderer, div.yt-live-chat-item-list-renderer, dl.yt-live-chat-item-list-renderer, dt.yt-live-chat-item-list-renderer, em.yt-live-chat-item-list-renderer, embed.yt-live-chat-item-list-renderer, fieldset.yt-live-chat-item-list-renderer, font.yt-live-chat-item-list-renderer, form.yt-live-chat-item-list-renderer, h1.yt-live-chat-item-list-renderer, h2.yt-live-chat-item-list-renderer, h3.yt-live-chat-item-list-renderer, h4.yt-live-chat-item-list-renderer, h5.yt-live-chat-item-list-renderer, h6.yt-live-chat-item-list-renderer, hr.yt-live-chat-item-list-renderer, i.yt-live-chat-item-list-renderer, iframe.yt-live-chat-item-list-renderer, img.yt-live-chat-item-list-renderer, ins.yt-live-chat-item-list-renderer, kbd.yt-live-chat-item-list-renderer, label.yt-live-chat-item-list-renderer, legend.yt-live-chat-item-list-renderer, li.yt-live-chat-item-list-renderer, menu.yt-live-chat-item-list-renderer, object.yt-live-chat-item-list-renderer, ol.yt-live-chat-item-list-renderer, p.yt-live-chat-item-list-renderer, pre.yt-live-chat-item-list-renderer, q.yt-live-chat-item-list-renderer, s.yt-live-chat-item-list-renderer, samp.yt-live-chat-item-list-renderer, small.yt-live-chat-item-list-renderer, span.yt-live-chat-item-list-renderer, strike.yt-live-chat-item-list-renderer, strong.yt-live-chat-item-list-renderer, sub.yt-live-chat-item-list-renderer, sup.yt-live-chat-item-list-renderer, table.yt-live-chat-item-list-renderer, tbody.yt-live-chat-item-list-renderer, td.yt-live-chat-item-list-renderer, tfoot.yt-live-chat-item-list-renderer, th.yt-live-chat-item-list-renderer, thead.yt-live-chat-item-list-renderer, tr.yt-live-chat-item-list-renderer, tt.yt-live-chat-item-list-renderer, u.yt-live-chat-item-list-renderer, ul.yt-live-chat-item-list-renderer, var.yt-live-chat-item-list-renderer {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.yt-live-chat-item-list-renderer[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
yt-live-chat-item-list-renderer {
|
||||
position: relative;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
yt-live-chat-item-list-renderer[moderation-mode-enabled] {
|
||||
--yt-live-chat-item-with-inline-actions-context-menu-display: none;
|
||||
--yt-live-chat-inline-action-button-container-display: flex;
|
||||
}
|
||||
|
||||
#contents.yt-live-chat-item-list-renderer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#empty-state-message.yt-live-chat-item-list-renderer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
-ms-flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-ms-flex-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#empty-state-message.yt-live-chat-item-list-renderer>yt-live-chat-message-renderer.yt-live-chat-item-list-renderer {
|
||||
color: var(--yt-live-chat-tertiary-text-color);
|
||||
background: transparent;
|
||||
font-size: 18px;
|
||||
--yt-live-chat-message-renderer-text-align: center;
|
||||
}
|
||||
|
||||
yt-icon-button.yt-live-chat-item-list-renderer {
|
||||
background-color: #2196f3;
|
||||
border-radius: 999px;
|
||||
bottom: 0;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin: 0 calc(50% - 16px) 8px calc(50% - 16px);
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
transition-property: bottom;
|
||||
transition-timing-function: cubic-bezier(0.0, 0.0, 0.2, 1);
|
||||
transition-duration: 0.15s;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
yt-icon-button.yt-live-chat-item-list-renderer[disabled] {
|
||||
bottom: -42px;
|
||||
color: #fff;
|
||||
transition-timing-function: cubic-bezier(0.4, 0.0, 1, 1);
|
||||
}
|
||||
|
||||
#item-scroller.yt-live-chat-item-list-renderer {
|
||||
-ms-flex: 1 1 0.000000001px;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
-webkit-flex-basis: 0.000000001px;
|
||||
flex-basis: 0.000000001px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
padding-right: var(--scrollbar-width);
|
||||
}
|
||||
|
||||
yt-live-chat-item-list-renderer[allow-scroll] #item-scroller.yt-live-chat-item-list-renderer {
|
||||
overflow-y: scroll;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
#item-offset.yt-live-chat-item-list-renderer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#item-scroller.animated.yt-live-chat-item-list-renderer #item-offset.yt-live-chat-item-list-renderer {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#items.yt-live-chat-item-list-renderer {
|
||||
-ms-flex: 1 1 0.000000001px;
|
||||
-webkit-flex: 1;
|
||||
flex: 1;
|
||||
-webkit-flex-basis: 0.000000001px;
|
||||
flex-basis: 0.000000001px;
|
||||
padding: var(--yt-live-chat-item-list-renderer-padding, 4px 0);
|
||||
}
|
||||
|
||||
#items.yt-live-chat-item-list-renderer>*.yt-live-chat-item-list-renderer:not(:first-child) {
|
||||
border-top: var(--yt-live-chat-item-list-item-border, none);
|
||||
}
|
||||
|
||||
#item-scroller.animated.yt-live-chat-item-list-renderer #items.yt-live-chat-item-list-renderer {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
#docked-messages.yt-live-chat-item-list-renderer {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-sticker-renderer.yt-live-chat-item-list-renderer {
|
||||
padding: 4px 24px;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-sticker-renderer.yt-live-chat-item-list-renderer[dashboard-money-feed] {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,24 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
房间ID:
|
||||
<input type="number" min="1" v-model="roomId" />
|
||||
<button @click="go">进入</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Home',
|
||||
data() {
|
||||
return {
|
||||
roomId: window.localStorage.roomId || '1'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
go() {
|
||||
window.localStorage.roomId = this.roomId
|
||||
this.$router.push({name: 'room', params: {roomId: this.roomId}})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,20 +0,0 @@
|
||||
<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>
|
||||
@@ -1,69 +0,0 @@
|
||||
<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">CN¥{{price}}</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>
|
||||
const LEVEL_TO_HEADER_COLOR = [
|
||||
'rgba(21,101,192,1)', //$1蓝
|
||||
'rgba(0,184,212,1)', // $2浅蓝
|
||||
'rgba(0,191,165,1)', // $5绿
|
||||
'rgba(255,179,0,1)', // $10黄
|
||||
'rgba(230,81,0,1)', // $20橙
|
||||
'rgba(194,24,91,1)', // $50品红
|
||||
'rgba(208,0,0,1)' // $100红
|
||||
]
|
||||
const LEVEL_TO_CONTENT_COLOR = [
|
||||
'rgba(30,136,229,1)', //$1蓝
|
||||
'rgba(0,229,255,1)', // $2浅蓝
|
||||
'rgba(29,233,182,1)', // $5绿
|
||||
'rgba(255,202,40,1)', // $10黄
|
||||
'rgba(245,124,0,1)', // $20橙
|
||||
'rgba(233,30,99,1)', // $50品红
|
||||
'rgba(230,33,23,1)' // $100红
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'PaidMessage',
|
||||
props: {
|
||||
price: Number, // 价格,人民币
|
||||
avatarUrl: String,
|
||||
authorName: String,
|
||||
content: String
|
||||
},
|
||||
computed: {
|
||||
level() {
|
||||
if (this.price < 9.9) // 0~9.9,丢人
|
||||
return 0
|
||||
else if (this.price < 28) // 9.9~28,B坷垃
|
||||
return 1
|
||||
else if (this.price < 52) // 28~52,礼花
|
||||
return 2
|
||||
else if (this.price < 100) // 52~100,疯狂打call
|
||||
return 3
|
||||
else if (this.price < 450) // 100~450,节奏风暴、天空之翼
|
||||
return 4
|
||||
else if (this.price < 1245) // 450~1245,摩天大楼
|
||||
return 5
|
||||
else // 1245,小电视飞船
|
||||
return 6
|
||||
},
|
||||
headerColor() {
|
||||
return LEVEL_TO_HEADER_COLOR[this.level]
|
||||
},
|
||||
contentColor() {
|
||||
return LEVEL_TO_CONTENT_COLOR[this.level]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,125 +0,0 @@
|
||||
<template>
|
||||
<yt-live-chat-renderer>
|
||||
<!-- <yt-live-chat-ticker-renderer>
|
||||
<yt-live-chat-ticker-paid-message-item-renderer style="background-color: rgba(0,184,212,1);">
|
||||
<div id="content">
|
||||
<span id="fake-avatar"></span>
|
||||
<span>$5.00</span>
|
||||
</div>
|
||||
</yt-live-chat-ticker-paid-message-item-renderer>
|
||||
<yt-live-chat-ticker-paid-message-item-renderer style="background-color: rgba(208,0,0,1);">
|
||||
<div id="content">
|
||||
<span id="fake-avatar"></span>
|
||||
<span>$500.00</span>
|
||||
</div>
|
||||
</yt-live-chat-ticker-paid-message-item-renderer>
|
||||
</yt-live-chat-ticker-renderer> -->
|
||||
<yt-live-chat-item-list-renderer>
|
||||
<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"
|
||||
></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
|
||||
:price="message.price" :avatarUrl="message.avatarUrl" :authorName="message.authorName"
|
||||
:content="message.content"
|
||||
></paid-message>
|
||||
</template>
|
||||
</yt-live-chat-item-list-renderer>
|
||||
</yt-live-chat-renderer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TextMessage from './TextMessage.vue'
|
||||
import LegacyPaidMessage from './LegacyPaidMessage.vue'
|
||||
import PaidMessage from './PaidMessage.vue'
|
||||
|
||||
const COMMAND_JOIN_ROOM = 0
|
||||
const COMMAND_ADD_TEXT = 1
|
||||
const COMMAND_ADD_GIFT = 2
|
||||
const COMMAND_ADD_VIP = 3
|
||||
|
||||
export default {
|
||||
name: 'Room',
|
||||
components: {
|
||||
TextMessage,
|
||||
LegacyPaidMessage,
|
||||
PaidMessage
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
websocket: null,
|
||||
messages: [],
|
||||
nextId: 0
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.websocket = new WebSocket(`ws://${window.location.host}/chat`)
|
||||
// 测试用
|
||||
// this.websocket = new WebSocket('ws://localhost/chat')
|
||||
this.websocket.onopen = () => this.websocket.send(JSON.stringify({
|
||||
cmd: COMMAND_JOIN_ROOM,
|
||||
data: {
|
||||
roomId: parseInt(this.$route.params.roomId)
|
||||
}
|
||||
}))
|
||||
this.websocket.onmessage = (event) => {
|
||||
let body = JSON.parse(event.data)
|
||||
let message = null
|
||||
let time, price
|
||||
switch(body.cmd) {
|
||||
case COMMAND_ADD_TEXT:
|
||||
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,
|
||||
authorType: body.data.authorType,
|
||||
content: body.data.content
|
||||
}
|
||||
break
|
||||
case COMMAND_ADD_GIFT:
|
||||
price = body.data.totalCoin / 1000
|
||||
if (price < 6.911) // 丢人
|
||||
break
|
||||
message = {
|
||||
id: this.nextId++,
|
||||
type: 2, // PaidMessage
|
||||
price: price,
|
||||
avatarUrl: body.data.avatarUrl,
|
||||
authorName: body.data.authorName,
|
||||
content: `Sent ${body.data.giftName}x${body.data.giftNum}`
|
||||
}
|
||||
break
|
||||
case COMMAND_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() {
|
||||
this.websocket.close()
|
||||
},
|
||||
updated() {
|
||||
window.scrollTo(0, document.body.scrollHeight)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style src="../assets/room.css"></style>
|
||||
@@ -1,35 +0,0 @@
|
||||
<template>
|
||||
<yt-live-chat-text-message-renderer :author-type="authorTypeText">
|
||||
<div id="author-photo" :style="`background-image: url(${avatarUrl})`"></div>
|
||||
<div id="content">
|
||||
<span id="timestamp">{{time}}</span>
|
||||
<span id="author-name" :type="authorTypeText">{{authorName}}</span>
|
||||
<span id="message">{{content}}</span>
|
||||
</div>
|
||||
</yt-live-chat-text-message-renderer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const AUTHOR_TYPE_TO_TEXT = [
|
||||
'',
|
||||
'member', // 舰队
|
||||
'moderator', // 房管
|
||||
'owner' // 主播
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'TextMessage',
|
||||
props: {
|
||||
avatarUrl: String,
|
||||
time: String,
|
||||
authorName: String,
|
||||
authorType: Number,
|
||||
content: String
|
||||
},
|
||||
computed: {
|
||||
authorTypeText() {
|
||||
return AUTHOR_TYPE_TO_TEXT[this.authorType]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
110
frontend/src/lang/en.js
Normal file
@@ -0,0 +1,110 @@
|
||||
export default {
|
||||
sidebar: {
|
||||
home: 'Home',
|
||||
stylegen: 'Style generator',
|
||||
projectAddress: 'Project address'
|
||||
},
|
||||
home: {
|
||||
roomIdEmpty: "Room ID can't be empty",
|
||||
roomIdInteger: 'Room ID must be positive integer',
|
||||
|
||||
general: 'General',
|
||||
roomId: 'Room ID',
|
||||
showDanmaku: 'Show messages',
|
||||
showGift: 'Show Super Chats',
|
||||
mergeSimilarDanmaku: 'Merge similar messages',
|
||||
minGiftPrice: 'Min price of Super Chats to show (CNY)',
|
||||
maxSpeed: 'Max speed of messages (/second, 0 for unlimited)',
|
||||
maxNumber: 'Max number of messages',
|
||||
|
||||
block: 'Block',
|
||||
giftDanmaku: 'Block system messages (gift effect)',
|
||||
blockLevel: 'Block user level lower than',
|
||||
informalUser: 'Block informal users',
|
||||
unverifiedUser: 'Block unverified users',
|
||||
blockKeywords: 'Block keywords',
|
||||
onePerLine: 'One per line',
|
||||
blockUsers: 'Block users',
|
||||
blockMedalLevel: 'Block medal level lower than',
|
||||
|
||||
style: 'Style',
|
||||
|
||||
roomUrl: 'Room URL',
|
||||
copy: 'Copy',
|
||||
saveConfig: 'Save config',
|
||||
enterRoom: 'Enter room',
|
||||
exportConfig: 'Export config',
|
||||
importConfig: 'Import config',
|
||||
|
||||
failedToSave: 'Failed to save: ',
|
||||
successfullySaved: 'Successfully saved',
|
||||
failedToParseConfig: 'Failed to parse config: '
|
||||
},
|
||||
stylegen: {
|
||||
outlines: 'Outlines',
|
||||
showOutlines: 'Show outlines',
|
||||
outlineSize: 'Outline size',
|
||||
outlineColor: 'Outline color',
|
||||
|
||||
avatars: 'Avatars',
|
||||
showAvatars: 'Show avatars',
|
||||
avatarSize: 'Avatar size',
|
||||
|
||||
userNames: 'User names',
|
||||
font: 'Font',
|
||||
fontSize: 'Font size',
|
||||
lineHeight: 'Line height (0 for default)',
|
||||
normalColor: 'Normal color',
|
||||
ownerColor: 'Owner color',
|
||||
moderatorColor: 'Moderator color',
|
||||
memberColor: 'Member color',
|
||||
showBadges: 'Show badges',
|
||||
showColon: 'Show colon after name',
|
||||
|
||||
messages: 'Messages',
|
||||
color: 'Color',
|
||||
onNewLine: 'On new line',
|
||||
|
||||
time: 'Timestamps',
|
||||
showTime: 'Show timestamps',
|
||||
|
||||
backgrounds: 'Backgrounds',
|
||||
bgColor: 'Background color',
|
||||
useBarsInsteadOfBg: 'Use bars instead of backgrounds',
|
||||
messageBgColor: 'Message background color',
|
||||
ownerMessageBgColor: 'Owner background color',
|
||||
moderatorMessageBgColor: 'Moderator background color',
|
||||
memberMessageBgColor: 'Member background color',
|
||||
|
||||
scAndNewMember: 'Super Chat / New member',
|
||||
firstLineFont: 'First line font',
|
||||
firstLineFontSize: 'First line font size',
|
||||
firstLineLineHeight: 'First line line height (0 for default)',
|
||||
firstLineColor: 'First line color',
|
||||
secondLineFont: 'Second line font',
|
||||
secondLineFontSize: 'Second line font size',
|
||||
secondLineLineHeight: 'Second line line height (0 for default)',
|
||||
secondLineColor: 'Second line color',
|
||||
scContentLineFont: 'Super Chat content font',
|
||||
scContentLineFontSize: 'Super Chat content font size',
|
||||
scContentLineLineHeight: 'Super Chat content line height (0 for default)',
|
||||
scContentLineColor: 'Super Chat content color',
|
||||
showNewMemberBg: 'Show new member background',
|
||||
showScTicker: 'Show Super Chat ticker',
|
||||
showOtherThings: 'Show everything other than Super Chat ticker',
|
||||
|
||||
animation: 'Animation',
|
||||
animateIn: 'Animate in',
|
||||
fadeInTime: 'Fade in time (miliseconds)',
|
||||
animateOut: 'Animate out (remove old messages)',
|
||||
animateOutWaitTime: 'Wait time (seconds)',
|
||||
fadeOutTime: 'Fade out time (miliseconds)',
|
||||
slide: 'Slide',
|
||||
reverseSlide: 'Reverse slide',
|
||||
playAnimation: 'Play animation',
|
||||
|
||||
result: 'Result',
|
||||
copy: 'Copy',
|
||||
resetConfig: 'Reset config'
|
||||
}
|
||||
}
|
||||
110
frontend/src/lang/ja.js
Normal file
@@ -0,0 +1,110 @@
|
||||
export default {
|
||||
sidebar: {
|
||||
home: 'トップページ',
|
||||
stylegen: 'スタイルジェネレータ',
|
||||
projectAddress: 'プロジェクトアドレス'
|
||||
},
|
||||
home: {
|
||||
roomIdEmpty: 'ルームのIDを空白にすることはできません',
|
||||
roomIdInteger: 'ルームは正の整数でなければなりません',
|
||||
|
||||
general: '常規',
|
||||
roomId: 'ルームID',
|
||||
showDanmaku: 'コメントを表示する',
|
||||
showGift: 'スーパーチャットと新メンバーを表示する',
|
||||
mergeSimilarDanmaku: '同じコメントを合併する',
|
||||
minGiftPrice: '最低表示スーパーチャット価格(CNY)',
|
||||
maxSpeed: 'コメントの最大速度(バー/秒、0は無制限)',
|
||||
maxNumber: '最大コメント数',
|
||||
|
||||
block: 'ブロック',
|
||||
giftDanmaku: 'ブロックシステムコメント(プレゼント効果)',
|
||||
blockLevel: 'ブロックユーザーレベルがx未満',
|
||||
informalUser: 'ブロック非公式ユーザー',
|
||||
unverifiedUser: 'ブロック認証されていないユーザー',
|
||||
blockKeywords: 'ブロックキーワード',
|
||||
onePerLine: '1行に1つずつ',
|
||||
blockUsers: 'ブロックユーザー',
|
||||
blockMedalLevel: 'ブロック勲章等級がx未満',
|
||||
|
||||
style: 'スタイル',
|
||||
|
||||
roomUrl: 'ルームのURL',
|
||||
copy: 'コピー',
|
||||
saveConfig: 'コンフィグを保存する',
|
||||
enterRoom: 'ルームに入る',
|
||||
exportConfig: 'コンフィグの導出',
|
||||
importConfig: 'コンフィグの導入',
|
||||
|
||||
failedToSave: '保存に失敗しました:',
|
||||
successfullySaved: '保存に成功しました',
|
||||
failedToParseConfig: 'コンフィグ解析に失敗しました'
|
||||
},
|
||||
stylegen: {
|
||||
outlines: 'アウトライン',
|
||||
showOutlines: 'アウトラインを表示する',
|
||||
outlineSize: 'アウトラインのサイズ',
|
||||
outlineColor: 'アウトラインの色',
|
||||
|
||||
avatars: 'アイコン',
|
||||
showAvatars: 'アイコンを表示する',
|
||||
avatarSize: 'アイコンのサイズ',
|
||||
|
||||
userNames: 'ユーザー名',
|
||||
font: 'フォント',
|
||||
fontSize: 'フォントサイズ',
|
||||
lineHeight: '行の高さ(0はデフォルト)',
|
||||
normalColor: 'ノーマルの色',
|
||||
ownerColor: 'オーナーの色',
|
||||
moderatorColor: '管理者の色',
|
||||
memberColor: 'メンバーの色',
|
||||
showBadges: '勲章を見せる',
|
||||
showColon: 'ユーザー名の後にコロンが表示されます',
|
||||
|
||||
messages: 'コメント',
|
||||
color: '色',
|
||||
onNewLine: '新しい行に表示する',
|
||||
|
||||
time: '時間',
|
||||
showTime: '時間を表示する',
|
||||
|
||||
backgrounds: '背景',
|
||||
bgColor: '背景色',
|
||||
useBarsInsteadOfBg: '背景に代わります',
|
||||
messageBgColor: 'コメント背景色',
|
||||
ownerMessageBgColor: 'オーナーコメント背景色',
|
||||
moderatorMessageBgColor: '管理者コメント背景色',
|
||||
memberMessageBgColor: 'メンバーコメント背景色',
|
||||
|
||||
scAndNewMember: 'スーパーチャット、新メンバー',
|
||||
firstLineFont: '1行目のフォント',
|
||||
firstLineFontSize: '1行目のフォントサイズ',
|
||||
firstLineLineHeight: '1行目の高さ(0はデフォルト)',
|
||||
firstLineColor: '1行目の色',
|
||||
secondLineFont: '2行目のフォント',
|
||||
secondLineFontSize: '2行目のフォントサイズ',
|
||||
secondLineLineHeight: '2行目の高さ(0はデフォルト)',
|
||||
secondLineColor: '2行目の色',
|
||||
scContentLineFont: 'スーパーチャットのコンテンツフォント',
|
||||
scContentLineFontSize: 'スーパーチャットコンテンツフォントサイズ',
|
||||
scContentLineLineHeight: 'スーパーチャットコンテンツライン高さ(0がデフォルト)',
|
||||
scContentLineColor: 'スーパーチャットコンテンツライン色',
|
||||
showNewMemberBg: '新メンバーの背景を表示する',
|
||||
showScTicker: 'スーパーチャットチカーの表示',
|
||||
showOtherThings: 'スーパーチャットチカー以外のコンテンツを表示します',
|
||||
|
||||
animation: 'アニメーション',
|
||||
animateIn: '入場アニメーション',
|
||||
fadeInTime: 'フェードイン時間(ミリ秒)',
|
||||
animateOut: '古いコメントを除去する',
|
||||
animateOutWaitTime: '待ち時間(秒)',
|
||||
fadeOutTime: 'フェードアウト時間(ミリ秒)',
|
||||
slide: '滑る',
|
||||
reverseSlide: '逆の滑る',
|
||||
playAnimation: 'アニメーションを再生する',
|
||||
|
||||
result: '結果',
|
||||
copy: 'コピー',
|
||||
resetConfig: 'デフォルトに戻す'
|
||||
}
|
||||
}
|
||||
110
frontend/src/lang/zh.js
Normal file
@@ -0,0 +1,110 @@
|
||||
export default {
|
||||
sidebar: {
|
||||
home: '首页',
|
||||
stylegen: '样式生成器',
|
||||
projectAddress: '项目地址'
|
||||
},
|
||||
home: {
|
||||
roomIdEmpty: '房间ID不能为空',
|
||||
roomIdInteger: '房间ID必须为正整数',
|
||||
|
||||
general: '常规',
|
||||
roomId: '房间ID',
|
||||
showDanmaku: '显示弹幕',
|
||||
showGift: '显示礼物和新舰长',
|
||||
mergeSimilarDanmaku: '合并相似弹幕',
|
||||
minGiftPrice: '最低显示礼物价格(元)',
|
||||
maxSpeed: '弹幕最大速度(条/秒,0为无限制)',
|
||||
maxNumber: '最大弹幕数',
|
||||
|
||||
block: '屏蔽',
|
||||
giftDanmaku: '屏蔽礼物弹幕',
|
||||
blockLevel: '屏蔽用户等级低于',
|
||||
informalUser: '屏蔽非正式会员',
|
||||
unverifiedUser: '屏蔽未绑定手机用户',
|
||||
blockKeywords: '屏蔽关键词',
|
||||
onePerLine: '一行一个',
|
||||
blockUsers: '屏蔽用户',
|
||||
blockMedalLevel: '屏蔽当前直播间勋章等级低于',
|
||||
|
||||
style: '样式',
|
||||
|
||||
roomUrl: '房间URL',
|
||||
copy: '复制',
|
||||
saveConfig: '保存配置',
|
||||
enterRoom: '进入房间',
|
||||
exportConfig: '导出配置',
|
||||
importConfig: '导入配置',
|
||||
|
||||
failedToSave: '保存失败:',
|
||||
successfullySaved: '保存成功',
|
||||
failedToParseConfig: '配置解析失败:'
|
||||
},
|
||||
stylegen: {
|
||||
outlines: '描边',
|
||||
showOutlines: '显示描边',
|
||||
outlineSize: '描边尺寸',
|
||||
outlineColor: '描边颜色',
|
||||
|
||||
avatars: '头像',
|
||||
showAvatars: '显示头像',
|
||||
avatarSize: '头像尺寸',
|
||||
|
||||
userNames: '用户名',
|
||||
font: '字体',
|
||||
fontSize: '字体尺寸',
|
||||
lineHeight: '行高(0为默认)',
|
||||
normalColor: '普通颜色',
|
||||
ownerColor: '主播颜色',
|
||||
moderatorColor: '房管颜色',
|
||||
memberColor: '舰长颜色',
|
||||
showBadges: '显示勋章',
|
||||
showColon: '用户名后显示冒号',
|
||||
|
||||
messages: '消息',
|
||||
color: '颜色',
|
||||
onNewLine: '在新行显示',
|
||||
|
||||
time: '时间',
|
||||
showTime: '显示时间',
|
||||
|
||||
backgrounds: '背景',
|
||||
bgColor: '背景色',
|
||||
useBarsInsteadOfBg: '用条代替背景',
|
||||
messageBgColor: '消息背景色',
|
||||
ownerMessageBgColor: '主播消息背景色',
|
||||
moderatorMessageBgColor: '房管消息背景色',
|
||||
memberMessageBgColor: '舰长消息背景色',
|
||||
|
||||
scAndNewMember: '礼物、舰长',
|
||||
firstLineFont: '第一行字体',
|
||||
firstLineFontSize: '第一行字体尺寸',
|
||||
firstLineLineHeight: '第一行行高(0为默认)',
|
||||
firstLineColor: '第一行颜色',
|
||||
secondLineFont: '第二行字体',
|
||||
secondLineFontSize: '第二行字体尺寸',
|
||||
secondLineLineHeight: '第二行行高(0为默认)',
|
||||
secondLineColor: '第二行颜色',
|
||||
scContentLineFont: 'Super Chat内容字体',
|
||||
scContentLineFontSize: 'Super Chat内容字体尺寸',
|
||||
scContentLineLineHeight: 'Super Chat内容行高(0为默认)',
|
||||
scContentLineColor: 'Super Chat内容颜色',
|
||||
showNewMemberBg: '显示新舰长背景',
|
||||
showScTicker: '显示Super Chat贴纸',
|
||||
showOtherThings: '显示Super Chat贴纸之外的内容',
|
||||
|
||||
animation: '动画',
|
||||
animateIn: '进入动画',
|
||||
fadeInTime: '淡入时间(毫秒)',
|
||||
animateOut: '移除旧消息',
|
||||
animateOutWaitTime: '等待时间(秒)',
|
||||
fadeOutTime: '淡出时间(毫秒)',
|
||||
slide: '滑动',
|
||||
reverseSlide: '反向滑动',
|
||||
playAnimation: '播放动画',
|
||||
|
||||
result: '结果',
|
||||
copy: '复制',
|
||||
resetConfig: '恢复默认设置'
|
||||
}
|
||||
}
|
||||
60
frontend/src/layout/Sidebar.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<el-scrollbar wrap-class="scrollbar-wrapper">
|
||||
<el-menu
|
||||
router
|
||||
background-color="#304156"
|
||||
text-color="#bfcbd9"
|
||||
active-text-color="rgb(64, 158, 255)"
|
||||
:default-active="$route.path"
|
||||
>
|
||||
<el-menu-item index="/">
|
||||
<i class="el-icon-s-home"></i>{{$t('sidebar.home')}}
|
||||
</el-menu-item>
|
||||
<el-menu-item :index="$router.resolve({name: 'stylegen'}).href">
|
||||
<i class="el-icon-brush"></i>{{$t('sidebar.stylegen')}}
|
||||
</el-menu-item>
|
||||
<a href="https://github.com/xfgryujk/blivechat" target="_blank">
|
||||
<el-menu-item>
|
||||
<i class="el-icon-share"></i>{{$t('sidebar.projectAddress')}}
|
||||
</el-menu-item>
|
||||
</a>
|
||||
<el-submenu index="null">
|
||||
<template slot="title">
|
||||
<i class="el-icon-chat-line-square"></i>Language
|
||||
</template>
|
||||
<el-menu-item v-for="{locale, name} in [
|
||||
{locale: 'zh', name: '中文'},
|
||||
{locale: 'ja', name: '日本語'},
|
||||
{locale: 'en', name: 'English'}
|
||||
]" :key="locale"
|
||||
@click="onSelectLanguage(locale)"
|
||||
>{{name}}</el-menu-item>
|
||||
</el-submenu>
|
||||
</el-menu>
|
||||
</el-scrollbar>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Sidebar',
|
||||
methods: {
|
||||
onSelectLanguage(locale) {
|
||||
window.localStorage.lang = this.$i18n.locale = locale
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.el-scrollbar {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.scrollbar-wrapper {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
.scrollbar-wrapper .el-menu {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
124
frontend/src/layout/index.vue
Normal file
@@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<el-container class="app-wrapper" :class="{mobile: isMobile}">
|
||||
<div v-show="isMobile && !hideSidebar" class="drawer-bg" @click="hideSidebar = true"></div>
|
||||
<el-aside width="210px" class="sidebar-container" :class="{'hide-sidebar': hideSidebar}">
|
||||
<div class="logo-container">
|
||||
<router-link to="/">
|
||||
<img src="@/assets/img/logo.png" class="sidebar-logo">
|
||||
<h1 class="sidebar-title">blivechat</h1>
|
||||
</router-link>
|
||||
</div>
|
||||
<sidebar></sidebar>
|
||||
</el-aside>
|
||||
<el-main>
|
||||
<el-button v-show="isMobile" class="menu-button" icon="el-icon-s-unfold" @click="hideSidebar = false"></el-button>
|
||||
<router-view></router-view>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Sidebar from './Sidebar.vue'
|
||||
|
||||
export default {
|
||||
name: 'Layout',
|
||||
components: {
|
||||
Sidebar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isMobile: false,
|
||||
hideSidebar: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
window.addEventListener('resize', this.onResize)
|
||||
this.onResize()
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.onResize)
|
||||
},
|
||||
methods: {
|
||||
onResize() {
|
||||
this.isMobile = document.body.clientWidth <= 992
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html {
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
}
|
||||
|
||||
html, body, #app, .app-wrapper, .sidebar-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a, a:focus, a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.drawer-bg {
|
||||
background: #000;
|
||||
opacity: 0.3;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
background-color: #304156;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-wrapper.mobile .sidebar-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transition-duration: 0.3s;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.app-wrapper.mobile .sidebar-container.hide-sidebar {
|
||||
pointer-events: none;
|
||||
transition-duration: 0.3s;
|
||||
transform: translate3d(-210px, 0, 0);
|
||||
}
|
||||
|
||||
.sidebar-container .logo-container {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
background: #2b2f3a;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar-container .logo-container .sidebar-logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
vertical-align: middle;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.sidebar-container .logo-container .sidebar-title {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
line-height: 50px;
|
||||
font-size: 14px;
|
||||
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.sidebar-container .is-horizontal {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -1,23 +1,71 @@
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import ElementUI from 'element-ui'
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
import VueI18n from 'vue-i18n'
|
||||
import axios from 'axios'
|
||||
|
||||
import App from './App.vue'
|
||||
import Home from './components/Home.vue'
|
||||
import Room from './components/Room.vue'
|
||||
import NotFound from './components/NotFound.vue'
|
||||
import Layout from './layout'
|
||||
import Home from './views/Home.vue'
|
||||
import StyleGenerator from './views/StyleGenerator'
|
||||
import Room from './views/Room.vue'
|
||||
import NotFound from './views/NotFound.vue'
|
||||
|
||||
import zh from './lang/zh'
|
||||
import ja from './lang/ja'
|
||||
import en from './lang/en'
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// 开发时使用localhost:12450
|
||||
axios.defaults.baseURL = 'http://localhost:12450'
|
||||
}
|
||||
|
||||
Vue.use(VueRouter)
|
||||
Vue.use(ElementUI)
|
||||
Vue.use(VueI18n)
|
||||
|
||||
Vue.config.ignoredElements = [
|
||||
/^yt-/
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
routes: [
|
||||
{path: '/', component: Home},
|
||||
{
|
||||
path: '/',
|
||||
component: Layout,
|
||||
children: [
|
||||
{path: '', component: Home},
|
||||
{path: 'stylegen', name: 'stylegen', component: StyleGenerator}
|
||||
]
|
||||
},
|
||||
{path: '/room/:roomId', name: 'room', component: Room},
|
||||
{path: '*', component: NotFound}
|
||||
]
|
||||
})
|
||||
|
||||
let locale = window.localStorage.lang
|
||||
if (!locale) {
|
||||
let lang = navigator.language
|
||||
if (lang.startsWith('zh')) {
|
||||
locale = 'zh'
|
||||
} else if (lang.startsWith('ja')) {
|
||||
locale = 'ja'
|
||||
} else {
|
||||
locale = 'en'
|
||||
}
|
||||
}
|
||||
const i18n = new VueI18n({
|
||||
locale,
|
||||
fallbackLocale: 'en',
|
||||
messages: {
|
||||
zh, ja, en
|
||||
}
|
||||
})
|
||||
|
||||
new Vue({
|
||||
render: h => h(App),
|
||||
router
|
||||
router,
|
||||
i18n
|
||||
}).$mount('#app')
|
||||
|
||||
25
frontend/src/utils.js
Normal file
@@ -0,0 +1,25 @@
|
||||
export function mergeConfig (config, defaultConfig) {
|
||||
let res = {}
|
||||
for (let i in defaultConfig) {
|
||||
res[i] = i in config ? config[i] : defaultConfig[i]
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
export function formatCurrency (price) {
|
||||
return new Intl.NumberFormat('zh-CN', {
|
||||
minimumFractionDigits: price < 100 ? 2 : 0
|
||||
}).format(price)
|
||||
}
|
||||
|
||||
export function getTimeTextMinSec (date) {
|
||||
let min = ('00' + date.getMinutes()).slice(-2)
|
||||
let sec = ('00' + date.getSeconds()).slice(-2)
|
||||
return `${min}:${sec}`
|
||||
}
|
||||
|
||||
export default {
|
||||
mergeConfig,
|
||||
formatCurrency,
|
||||
getTimeTextMinSec
|
||||
}
|
||||
165
frontend/src/views/Home.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<el-form :model="form" ref="form" label-width="150px" :rules="{
|
||||
roomId: [
|
||||
{required: true, message: $t('home.roomIdEmpty'), trigger: 'blur'},
|
||||
{type: 'integer', min: 1, message: $t('home.roomIdInteger'), trigger: 'blur'}
|
||||
]
|
||||
}">
|
||||
<el-tabs>
|
||||
<el-tab-pane :label="$t('home.general')">
|
||||
<el-form-item :label="$t('home.roomId')" required prop="roomId">
|
||||
<el-input v-model.number="form.roomId" type="number" min="1"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('home.showDanmaku')">
|
||||
<el-switch v-model="form.showDanmaku"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('home.showGift')">
|
||||
<el-switch v-model="form.showGift"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('home.mergeSimilarDanmaku')">
|
||||
<el-switch v-model="form.mergeSimilarDanmaku"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('home.minGiftPrice')">
|
||||
<el-input v-model.number="form.minGiftPrice" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('home.maxSpeed')">
|
||||
<el-input v-model.number="form.maxSpeed" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('home.maxNumber')">
|
||||
<el-input v-model.number="form.maxNumber" type="number" min="1"></el-input>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('home.block')">
|
||||
<el-form-item :label="$t('home.giftDanmaku')">
|
||||
<el-switch v-model="form.blockGiftDanmaku"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('home.blockLevel')">
|
||||
<el-slider v-model="form.blockLevel" show-input :min="0" :max="60"></el-slider>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('home.informalUser')">
|
||||
<el-switch v-model="form.blockNewbie"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('home.unverifiedUser')">
|
||||
<el-switch v-model="form.blockNotMobileVerified"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('home.blockKeywords')">
|
||||
<el-input v-model="form.blockKeywords" type="textarea" :rows="5" :placeholder="$t('home.onePerLine')"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('home.blockUsers')">
|
||||
<el-input v-model="form.blockUsers" type="textarea" :rows="5" :placeholder="$t('home.onePerLine')"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('home.blockMedalLevel')">
|
||||
<el-slider v-model="form.blockMedalLevel" show-input :min="0" :max="20"></el-slider>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('home.style')">
|
||||
<el-form-item label="CSS">
|
||||
<el-input v-model="form.css" type="textarea" :rows="20"></el-input>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<el-divider></el-divider>
|
||||
<el-form-item :label="$t('home.roomUrl')" v-show="roomUrl">
|
||||
<el-input ref="roomUrlInput" readonly :value="roomUrl" style="width: calc(100% - 6em); margin-right: 1em;"></el-input>
|
||||
<el-button type="primary" @click="copyUrl">{{$t('home.copy')}}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="saveConfig">{{$t('home.saveConfig')}}</el-button>
|
||||
<el-button type="primary" :disabled="!roomUrl" @click="enterRoom">{{$t('home.enterRoom')}}</el-button>
|
||||
<el-button type="primary" @click="exportConfig">{{$t('home.exportConfig')}}</el-button>
|
||||
<el-button type="primary" @click="importConfig">{{$t('home.importConfig')}}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import download from 'downloadjs'
|
||||
|
||||
import {mergeConfig} from '@/utils'
|
||||
import config from '@/api/config'
|
||||
|
||||
export default {
|
||||
name: 'Home',
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
roomId: parseInt(window.localStorage.roomId || '1'),
|
||||
...config.getLocalConfig()
|
||||
},
|
||||
roomUrl: ''
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
saveConfig() {
|
||||
this.$refs.form.validate(async valid => {
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
window.localStorage.roomId = this.form.roomId
|
||||
config.setLocalConfig(this.form)
|
||||
|
||||
try {
|
||||
if (window.localStorage.configId) {
|
||||
try {
|
||||
await config.setRemoteConfig(window.localStorage.configId, this.form)
|
||||
} catch (e) { // 404
|
||||
window.localStorage.configId = (await config.createRemoteConfig(this.form)).id
|
||||
}
|
||||
} else {
|
||||
window.localStorage.configId = (await config.createRemoteConfig(this.form)).id
|
||||
}
|
||||
} catch (e) {
|
||||
this.$message.error(this.$t('home.failedToSave') + e)
|
||||
return
|
||||
}
|
||||
this.$message({message: this.$t('home.successfullySaved'), type: 'success'})
|
||||
|
||||
let resolved = this.$router.resolve({name: 'room', params: {roomId: this.form.roomId},
|
||||
query: {config_id: window.localStorage.configId}})
|
||||
this.roomUrl = `http://${window.location.host}${resolved.href}`
|
||||
})
|
||||
},
|
||||
enterRoom() {
|
||||
window.open(this.roomUrl, `room ${this.form.roomId}`, 'menubar=0,location=0,scrollbars=0,toolbar=0,width=600,height=600')
|
||||
},
|
||||
copyUrl() {
|
||||
this.$refs.roomUrlInput.select()
|
||||
document.execCommand('Copy')
|
||||
},
|
||||
exportConfig() {
|
||||
let cfg = mergeConfig(this.form, config.DEFAULT_CONFIG)
|
||||
download(JSON.stringify(cfg, null, 2), 'blivechat.json', 'application/json')
|
||||
},
|
||||
importConfig() {
|
||||
let input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.accept = 'application/json'
|
||||
input.onchange = () => {
|
||||
let reader = new window.FileReader()
|
||||
reader.onload = () => {
|
||||
let cfg
|
||||
try {
|
||||
cfg = JSON.parse(reader.result)
|
||||
} catch (e) {
|
||||
this.$message.error(this.$t('home.failedToParseConfig') + e)
|
||||
return
|
||||
}
|
||||
cfg = mergeConfig(cfg, config.DEFAULT_CONFIG)
|
||||
this.form = {roomId: this.form.roomId, ...cfg}
|
||||
}
|
||||
reader.readAsText(input.files[0])
|
||||
}
|
||||
input.click()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-form {
|
||||
max-width: 800px;
|
||||
}
|
||||
</style>
|
||||
256
frontend/src/views/Room.vue
Normal file
@@ -0,0 +1,256 @@
|
||||
<template>
|
||||
<chat-renderer :paidMessages="paidMessages" :messages="messages" :css="config.css"></chat-renderer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from '@/api/config'
|
||||
import ChatRenderer from '@/components/ChatRenderer'
|
||||
import * as constants from '@/components/ChatRenderer/constants'
|
||||
|
||||
// const COMMAND_HEARTBEAT = 0
|
||||
const COMMAND_JOIN_ROOM = 1
|
||||
const COMMAND_ADD_TEXT = 2
|
||||
const COMMAND_ADD_GIFT = 3
|
||||
const COMMAND_ADD_MEMBER = 4
|
||||
const COMMAND_ADD_SUPER_CHAT = 5
|
||||
const COMMAND_DEL_SUPER_CHAT = 6
|
||||
|
||||
export default {
|
||||
name: 'Room',
|
||||
components: {
|
||||
ChatRenderer
|
||||
},
|
||||
data() {
|
||||
let cfg = {...config.DEFAULT_CONFIG}
|
||||
cfg.blockKeywords = cfg.blockKeywords.split('\n').filter(val => val)
|
||||
cfg.blockUsers = cfg.blockUsers.split('\n').filter(val => val)
|
||||
cfg.maxSpeed = 0
|
||||
return {
|
||||
config: cfg,
|
||||
websocket: null,
|
||||
messagesBufferTimerId: null,
|
||||
nextId: 0,
|
||||
messagesBuffer: [], // 暂时不显示的消息,可能会丢弃
|
||||
messages: [], // 正在显示的消息
|
||||
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
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.messagesBufferTimerId) {
|
||||
window.clearInterval(this.messagesBufferTimerId)
|
||||
}
|
||||
this.websocket.close()
|
||||
},
|
||||
watch: {
|
||||
config(val) {
|
||||
if (this.messagesBufferTimerId) {
|
||||
window.clearInterval(this.messagesBufferTimerId)
|
||||
this.messagesBufferTimerId = null
|
||||
}
|
||||
if (val.maxSpeed > 0) {
|
||||
this.messagesBufferTimerId = window.setInterval(this.handleMessagesBuffer.bind(this), 1000 / val.maxSpeed)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onWsOpen() {
|
||||
this.websocket.send(JSON.stringify({
|
||||
cmd: COMMAND_JOIN_ROOM,
|
||||
data: {
|
||||
roomId: parseInt(this.$route.params.roomId)
|
||||
}
|
||||
}))
|
||||
},
|
||||
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) {
|
||||
case COMMAND_ADD_TEXT:
|
||||
if (!this.config.showDanmaku || !this.filterTextMessage(data) || this.mergeSimilar(data.content)) {
|
||||
break
|
||||
}
|
||||
message = {
|
||||
type: constants.MESSAGE_TYPE_TEXT,
|
||||
avatarUrl: data.avatarUrl,
|
||||
time: time,
|
||||
authorName: data.authorName,
|
||||
authorType: data.authorType,
|
||||
content: data.content,
|
||||
privilegeType: data.privilegeType,
|
||||
repeated: 1
|
||||
}
|
||||
break
|
||||
case COMMAND_ADD_GIFT: {
|
||||
if (!this.config.showGift) {
|
||||
break
|
||||
}
|
||||
let price = data.totalCoin / 1000
|
||||
if (price < this.config.minGiftPrice) { // 丢人
|
||||
break
|
||||
}
|
||||
message = {
|
||||
type: constants.MESSAGE_TYPE_SUPER_CHAT,
|
||||
avatarUrl: data.avatarUrl,
|
||||
authorName: data.authorName,
|
||||
price: price,
|
||||
time: time,
|
||||
content: '' // 有了SC,礼物不需要内容了
|
||||
}
|
||||
break
|
||||
}
|
||||
case COMMAND_ADD_MEMBER:
|
||||
if (!this.config.showGift || !this.filterSuperChatMessage(data)) {
|
||||
break
|
||||
}
|
||||
message = {
|
||||
type: constants.MESSAGE_TYPE_MEMBER,
|
||||
avatarUrl: data.avatarUrl,
|
||||
time: time,
|
||||
authorName: data.authorName,
|
||||
title: 'NEW MEMBER!',
|
||||
content: `Welcome ${data.authorName}!`
|
||||
}
|
||||
break
|
||||
case COMMAND_ADD_SUPER_CHAT:
|
||||
if (!this.config.showGift) {
|
||||
break
|
||||
}
|
||||
if (data.price < this.config.minGiftPrice) { // 丢人
|
||||
break
|
||||
}
|
||||
message = {
|
||||
type: constants.MESSAGE_TYPE_SUPER_CHAT,
|
||||
avatarUrl: data.avatarUrl,
|
||||
authorName: data.authorName,
|
||||
price: data.price,
|
||||
time: time,
|
||||
content: data.content
|
||||
}
|
||||
break
|
||||
case COMMAND_DEL_SUPER_CHAT:
|
||||
// TODO 删除SC
|
||||
break
|
||||
}
|
||||
if (message) {
|
||||
this.addMessageBuffer(message)
|
||||
}
|
||||
},
|
||||
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
|
||||
} else if (this.config.blockMedalLevel > 0 && data.medalLevel < this.config.blockMedalLevel) {
|
||||
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
|
||||
},
|
||||
filterSuperChatMessage(data) {
|
||||
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
|
||||
},
|
||||
addMessageBuffer(message) {
|
||||
if (this.config.maxSpeed > 0 && message.type === 0) {
|
||||
message.addTime = new Date()
|
||||
this.messagesBuffer.push(message)
|
||||
} else {
|
||||
// 无速度限制或者是礼物
|
||||
this.addMessageShow(message)
|
||||
}
|
||||
},
|
||||
addMessageShow(message) {
|
||||
message.id = this.nextId++
|
||||
message.addTime = new Date()
|
||||
this.messages.push(message)
|
||||
if (message.type !== constants.MESSAGE_TYPE_TEXT) {
|
||||
this.paidMessages.unshift(message)
|
||||
}
|
||||
// 防止同时添加和删除项目时所有的项目重新渲染 https://github.com/vuejs/vue/issues/6857
|
||||
this.$nextTick(() => {
|
||||
if (this.messages.length > this.config.maxNumber) {
|
||||
this.messages.splice(0, this.messages.length - this.config.maxNumber)
|
||||
}
|
||||
})
|
||||
},
|
||||
handleMessagesBuffer() {
|
||||
// 3秒内未发出则丢弃
|
||||
let i
|
||||
let curTime = new Date()
|
||||
for (i = 0; i < this.messagesBuffer.length; i++) {
|
||||
if (curTime - this.messagesBuffer[i].addTime <= 3 * 1000) {
|
||||
break
|
||||
}
|
||||
}
|
||||
this.messagesBuffer.splice(0, i)
|
||||
if (this.messagesBuffer.length <= 0) {
|
||||
return
|
||||
}
|
||||
let message = this.messagesBuffer.shift()
|
||||
// 防止短时间发送多条时不能合并
|
||||
if (this.mergeSimilar(message.content)) {
|
||||
this.handleMessagesBuffer()
|
||||
return
|
||||
}
|
||||
this.addMessageShow(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
14
frontend/src/views/StyleGenerator/fonts.js
Normal file
400
frontend/src/views/StyleGenerator/index.vue
Normal file
@@ -0,0 +1,400 @@
|
||||
<template>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form label-width="150px" size="mini">
|
||||
<h3>{{$t('stylegen.outlines')}}</h3>
|
||||
<el-form-item :label="$t('stylegen.showOutlines')">
|
||||
<el-switch v-model="form.showOutlines"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.outlineSize')">
|
||||
<el-input v-model.number="form.outlineSize" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.outlineColor')">
|
||||
<el-color-picker v-model="form.outlineColor"></el-color-picker>
|
||||
</el-form-item>
|
||||
|
||||
<h3>{{$t('stylegen.avatars')}}</h3>
|
||||
<el-form-item :label="$t('stylegen.showAvatars')">
|
||||
<el-switch v-model="form.showAvatars"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.avatarSize')">
|
||||
<el-input v-model.number="form.avatarSize" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<h3>{{$t('stylegen.userNames')}}</h3>
|
||||
<el-form-item :label="$t('stylegen.font')">
|
||||
<el-autocomplete v-model="form.userNameFont" :fetch-suggestions="getFontSuggestions"></el-autocomplete>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.fontSize')">
|
||||
<el-input v-model.number="form.userNameFontSize" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.lineHeight')">
|
||||
<el-input v-model.number="form.userNameLineHeight" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.normalColor')">
|
||||
<el-color-picker v-model="form.userNameColor"></el-color-picker>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.ownerColor')">
|
||||
<el-color-picker v-model="form.ownerUserNameColor"></el-color-picker>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.moderatorColor')">
|
||||
<el-color-picker v-model="form.moderatorUserNameColor"></el-color-picker>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.memberColor')">
|
||||
<el-color-picker v-model="form.memberUserNameColor"></el-color-picker>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.showBadges')">
|
||||
<el-switch v-model="form.showBadges"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.showColon')">
|
||||
<el-switch v-model="form.showColon"></el-switch>
|
||||
</el-form-item>
|
||||
|
||||
<h3>{{$t('stylegen.messages')}}</h3>
|
||||
<el-form-item :label="$t('stylegen.font')">
|
||||
<el-autocomplete v-model="form.messageFont" :fetch-suggestions="getFontSuggestions"></el-autocomplete>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.fontSize')">
|
||||
<el-input v-model.number="form.messageFontSize" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.lineHeight')">
|
||||
<el-input v-model.number="form.messageLineHeight" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.color')">
|
||||
<el-color-picker v-model="form.messageColor"></el-color-picker>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.onNewLine')">
|
||||
<el-switch v-model="form.messageOnNewLine"></el-switch>
|
||||
</el-form-item>
|
||||
|
||||
<h3>{{$t('stylegen.time')}}</h3>
|
||||
<el-form-item :label="$t('stylegen.showTime')">
|
||||
<el-switch v-model="form.showTime"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.font')">
|
||||
<el-autocomplete v-model="form.timeFont" :fetch-suggestions="getFontSuggestions"></el-autocomplete>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.fontSize')">
|
||||
<el-input v-model.number="form.timeFontSize" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.lineHeight')">
|
||||
<el-input v-model.number="form.timeLineHeight" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.color')">
|
||||
<el-color-picker v-model="form.timeColor"></el-color-picker>
|
||||
</el-form-item>
|
||||
|
||||
<h3>{{$t('stylegen.backgrounds')}}</h3>
|
||||
<el-form-item :label="$t('stylegen.bgColor')">
|
||||
<el-color-picker v-model="form.bgColor" show-alpha></el-color-picker>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.useBarsInsteadOfBg')">
|
||||
<el-switch v-model="form.useBarsInsteadOfBg"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.messageBgColor')">
|
||||
<el-color-picker v-model="form.messageBgColor" show-alpha></el-color-picker>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.ownerMessageBgColor')">
|
||||
<el-color-picker v-model="form.ownerMessageBgColor" show-alpha></el-color-picker>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.moderatorMessageBgColor')">
|
||||
<el-color-picker v-model="form.moderatorMessageBgColor" show-alpha></el-color-picker>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.memberMessageBgColor')">
|
||||
<el-color-picker v-model="form.memberMessageBgColor" show-alpha></el-color-picker>
|
||||
</el-form-item>
|
||||
|
||||
<h3>{{$t('stylegen.scAndNewMember')}}</h3>
|
||||
<el-form-item :label="$t('stylegen.firstLineFont')">
|
||||
<el-autocomplete v-model="form.firstLineFont" :fetch-suggestions="getFontSuggestions"></el-autocomplete>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.firstLineFontSize')">
|
||||
<el-input v-model.number="form.firstLineFontSize" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.firstLineLineHeight')">
|
||||
<el-input v-model.number="form.firstLineLineHeight" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.firstLineColor')">
|
||||
<el-color-picker v-model="form.firstLineColor"></el-color-picker>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.secondLineFont')">
|
||||
<el-autocomplete v-model="form.secondLineFont" :fetch-suggestions="getFontSuggestions"></el-autocomplete>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.secondLineFontSize')">
|
||||
<el-input v-model.number="form.secondLineFontSize" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.secondLineLineHeight')">
|
||||
<el-input v-model.number="form.secondLineLineHeight" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.secondLineColor')">
|
||||
<el-color-picker v-model="form.secondLineColor"></el-color-picker>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.scContentLineFont')">
|
||||
<el-autocomplete v-model="form.scContentFont" :fetch-suggestions="getFontSuggestions"></el-autocomplete>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.scContentLineFontSize')">
|
||||
<el-input v-model.number="form.scContentFontSize" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.scContentLineLineHeight')">
|
||||
<el-input v-model.number="form.scContentLineHeight" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.scContentLineColor')">
|
||||
<el-color-picker v-model="form.scContentColor"></el-color-picker>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.showNewMemberBg')">
|
||||
<el-switch v-model="form.showNewMemberBg"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.showScTicker')">
|
||||
<el-switch v-model="form.showScTicker"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.showOtherThings')">
|
||||
<el-switch v-model="form.showOtherThings"></el-switch>
|
||||
</el-form-item>
|
||||
|
||||
<h3>{{$t('stylegen.animation')}}</h3>
|
||||
<el-form-item :label="$t('stylegen.animateIn')">
|
||||
<el-switch v-model="form.animateIn"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.fadeInTime')">
|
||||
<el-input v-model.number="form.fadeInTime" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.animateOut')">
|
||||
<el-switch v-model="form.animateOut"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.animateOutWaitTime')">
|
||||
<el-input v-model.number="form.animateOutWaitTime" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.fadeOutTime')">
|
||||
<el-input v-model.number="form.fadeOutTime" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.slide')">
|
||||
<el-switch v-model="form.slide"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('stylegen.reverseSlide')">
|
||||
<el-switch v-model="form.reverseSlide"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="playAnimation">{{$t('stylegen.playAnimation')}}</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<h3>{{$t('stylegen.result')}}</h3>
|
||||
<el-form-item label="CSS">
|
||||
<el-input v-model="result" ref="result" type="textarea" :rows="20"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="copyResult">{{$t('stylegen.copy')}}</el-button>
|
||||
<el-button @click="resetConfig">{{$t('stylegen.resetConfig')}}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<div id="example-container" ref="exampleContainer">
|
||||
<div id="fakebody">
|
||||
<chat-renderer :paidMessages="examplePaidMessages" :messages="messages" :css="exampleCss" ref="example"></chat-renderer>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
|
||||
import stylegen from './stylegen'
|
||||
import fonts from './fonts'
|
||||
import ChatRenderer from '@/components/ChatRenderer'
|
||||
import * as constants from '@/components/ChatRenderer/constants'
|
||||
|
||||
let time = new Date()
|
||||
let textMessageTemplate = {
|
||||
id: 0,
|
||||
addTime: time,
|
||||
type: constants.MESSAGE_TYPE_TEXT,
|
||||
avatarUrl: 'https://static.hdslb.com/images/member/noface.gif',
|
||||
time: time,
|
||||
authorName: '',
|
||||
authorType: constants.AUTHRO_TYPE_NORMAL,
|
||||
content: '',
|
||||
privilegeType: 0,
|
||||
repeated: 1
|
||||
}
|
||||
let legacyPaidMessageTemplate = {
|
||||
id: 0,
|
||||
addTime: time,
|
||||
type: constants.MESSAGE_TYPE_MEMBER,
|
||||
avatarUrl: 'https://static.hdslb.com/images/member/noface.gif',
|
||||
time: time,
|
||||
authorName: '',
|
||||
title: 'NEW MEMBER!',
|
||||
content: ''
|
||||
}
|
||||
let paidMessageTemplate = {
|
||||
id: 0,
|
||||
addTime: time,
|
||||
type: constants.MESSAGE_TYPE_SUPER_CHAT,
|
||||
avatarUrl: 'https://static.hdslb.com/images/member/noface.gif',
|
||||
authorName: '',
|
||||
price: 0,
|
||||
time: time,
|
||||
content: ''
|
||||
}
|
||||
let nextId = 0
|
||||
const EXAMPLE_MESSAGES = [
|
||||
{
|
||||
...textMessageTemplate,
|
||||
id: nextId++,
|
||||
authorName: 'mob路人',
|
||||
content: '8888888888',
|
||||
repeated: 12
|
||||
}, {
|
||||
...textMessageTemplate,
|
||||
id: nextId++,
|
||||
authorName: 'member舰长',
|
||||
authorType: constants.AUTHRO_TYPE_MEMBER,
|
||||
content: '草',
|
||||
privilegeType: 3,
|
||||
repeated: 3
|
||||
}, {
|
||||
...textMessageTemplate,
|
||||
id: nextId++,
|
||||
authorName: 'admin房管',
|
||||
authorType: constants.AUTHRO_TYPE_ADMIN,
|
||||
content: 'kksk'
|
||||
}, {
|
||||
...legacyPaidMessageTemplate,
|
||||
id: nextId++,
|
||||
authorName: '吾乐KANA',
|
||||
content: 'Welcome 吾乐KANA!'
|
||||
}, {
|
||||
...paidMessageTemplate,
|
||||
id: nextId++,
|
||||
authorName: '无火的残渣',
|
||||
price: 66600,
|
||||
content: 'Sent 小电视飞船x100'
|
||||
}, {
|
||||
...textMessageTemplate,
|
||||
id: nextId++,
|
||||
authorName: 'streamer主播',
|
||||
authorType: constants.AUTHRO_TYPE_OWNER,
|
||||
content: '感谢石油佬送的小电视'
|
||||
}, {
|
||||
...paidMessageTemplate,
|
||||
id: nextId++,
|
||||
authorName: '夏色祭保護協会会長',
|
||||
price: 28,
|
||||
content: 'Sent 礼花x1'
|
||||
}
|
||||
]
|
||||
let examplePaidMessages = []
|
||||
for (let message of EXAMPLE_MESSAGES) {
|
||||
if (message.type !== constants.MESSAGE_TYPE_TEXT) {
|
||||
examplePaidMessages.push(message)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'StyleGenerator',
|
||||
components: {
|
||||
ChatRenderer
|
||||
},
|
||||
data() {
|
||||
let config = stylegen.getLocalConfig()
|
||||
let result = stylegen.getStyle(config)
|
||||
return {
|
||||
FONTS: [...fonts.LOCAL_FONTS, ...fonts.NETWORK_FONTS],
|
||||
form: {...config},
|
||||
result,
|
||||
exampleCss: result.replace(/^body\b/gm, '#fakebody'),
|
||||
messages: EXAMPLE_MESSAGES,
|
||||
examplePaidMessages
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
computedResult() {
|
||||
return stylegen.getStyle(this.form)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
let observer = new MutationObserver(() => this.$refs.example.scrollToBottom())
|
||||
observer.observe(this.$refs.exampleContainer, {attributes: true})
|
||||
},
|
||||
methods: {
|
||||
getFontSuggestions(query, callback) {
|
||||
let res = this.FONTS.map(font => {return {value: font}})
|
||||
if (query) {
|
||||
query = query.toLowerCase()
|
||||
res = res.filter(
|
||||
font => font.value.toLowerCase().indexOf(query) !== -1
|
||||
)
|
||||
}
|
||||
callback(res)
|
||||
},
|
||||
playAnimation() {
|
||||
this.messages = []
|
||||
this.$nextTick(() => this.messages = EXAMPLE_MESSAGES)
|
||||
},
|
||||
copyResult() {
|
||||
this.$refs.result.select()
|
||||
document.execCommand('Copy')
|
||||
},
|
||||
resetConfig() {
|
||||
this.form = {...stylegen.DEFAULT_CONFIG}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
computedResult: _.debounce(function(val) {
|
||||
this.result = val
|
||||
stylegen.setLocalConfig(this.form)
|
||||
}, 500),
|
||||
result(val) {
|
||||
this.exampleCss = val.replace(/^body\b/gm, '#fakebody')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.el-form {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
#example-container {
|
||||
position: fixed;
|
||||
top: 30px;
|
||||
left: calc(210px + 40px + (100vw - 210px - 40px) / 2);
|
||||
width: calc((100vw - 210px - 40px) / 2 - 40px - 30px);
|
||||
height: calc(100vh - 110px);
|
||||
|
||||
background-color: #444;
|
||||
background-image:
|
||||
-moz-linear-gradient(45deg, #333 25%, transparent 25%),
|
||||
-moz-linear-gradient(-45deg, #333 25%, transparent 25%),
|
||||
-moz-linear-gradient(45deg, transparent 75%, #333 75%),
|
||||
-moz-linear-gradient(-45deg, transparent 75%, #333 75%);
|
||||
background-image:
|
||||
-webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, #333), color-stop(.25, transparent)),
|
||||
-webkit-gradient(linear, 0 0, 100% 100%, color-stop(.25, #333), color-stop(.25, transparent)),
|
||||
-webkit-gradient(linear, 0 100%, 100% 0, color-stop(.75, transparent), color-stop(.75, #333)),
|
||||
-webkit-gradient(linear, 0 0, 100% 100%, color-stop(.75, transparent), color-stop(.75, #333));
|
||||
|
||||
-moz-background-size:32px 32px;
|
||||
background-size:32px 32px;
|
||||
-webkit-background-size:32px 32px;
|
||||
|
||||
background-position:0 0, 16px 0, 16px -16px, 0px 16px;
|
||||
|
||||
padding: 25px;
|
||||
|
||||
resize: both;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.app-wrapper.mobile #example-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#fakebody {
|
||||
outline: 1px #999 dashed;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
413
frontend/src/views/StyleGenerator/stylegen.js
Normal file
@@ -0,0 +1,413 @@
|
||||
import {mergeConfig} from '@/utils'
|
||||
import fonts from './fonts'
|
||||
|
||||
export const DEFAULT_CONFIG = {
|
||||
showOutlines: true,
|
||||
outlineSize: 2,
|
||||
outlineColor: '#000000',
|
||||
|
||||
showAvatars: true,
|
||||
avatarSize: 24,
|
||||
|
||||
userNameFont: 'Changa One',
|
||||
userNameFontSize: 20,
|
||||
userNameLineHeight: 0,
|
||||
userNameColor: '#cccccc',
|
||||
ownerUserNameColor: '#ffd600',
|
||||
moderatorUserNameColor: '#5e84f1',
|
||||
memberUserNameColor: '#0f9d58',
|
||||
showBadges: false,
|
||||
showColon: true,
|
||||
|
||||
messageFont: 'Imprima',
|
||||
messageFontSize: 18,
|
||||
messageLineHeight: 0,
|
||||
messageColor: '#ffffff',
|
||||
messageOnNewLine: false,
|
||||
|
||||
showTime: false,
|
||||
timeFont: 'Imprima',
|
||||
timeFontSize: 16,
|
||||
timeLineHeight: 0,
|
||||
timeColor: '#999999',
|
||||
|
||||
bgColor: 'rgba(0, 0, 0, 0)',
|
||||
useBarsInsteadOfBg: false,
|
||||
messageBgColor: 'rgba(204, 204, 204, 0)',
|
||||
ownerMessageBgColor: 'rgba(255, 214, 0, 0)',
|
||||
moderatorMessageBgColor: 'rgba(94, 132, 241, 0)',
|
||||
memberMessageBgColor: 'rgba(15, 157, 88, 0)',
|
||||
|
||||
firstLineFont: 'Changa One',
|
||||
firstLineFontSize: 20,
|
||||
firstLineLineHeight: 0,
|
||||
firstLineColor: '#ffffff',
|
||||
secondLineFont: 'Imprima',
|
||||
secondLineFontSize: 18,
|
||||
secondLineLineHeight: 0,
|
||||
secondLineColor: '#ffffff',
|
||||
scContentFont: 'Imprima',
|
||||
scContentFontSize: 18,
|
||||
scContentLineHeight: 0,
|
||||
scContentColor: '#ffffff',
|
||||
showNewMemberBg: true,
|
||||
showScTicker: false,
|
||||
showOtherThings: true,
|
||||
|
||||
animateIn: false,
|
||||
fadeInTime: 200, // ms
|
||||
animateOut: false,
|
||||
animateOutWaitTime: 30, // s
|
||||
fadeOutTime: 200, // ms
|
||||
slide: false,
|
||||
reverseSlide: false
|
||||
}
|
||||
|
||||
const FALLBACK_FONTS = ', "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "\\5FAE\\8F6F\\96C5\\9ED1", SimHei, Arial, sans-serif'
|
||||
|
||||
export function setLocalConfig (config) {
|
||||
config = mergeConfig(config, DEFAULT_CONFIG)
|
||||
window.localStorage.stylegenConfig = JSON.stringify(config)
|
||||
}
|
||||
|
||||
export function getLocalConfig () {
|
||||
if (!window.localStorage.stylegenConfig) {
|
||||
return DEFAULT_CONFIG
|
||||
}
|
||||
return mergeConfig(JSON.parse(window.localStorage.stylegenConfig), DEFAULT_CONFIG)
|
||||
}
|
||||
|
||||
export function getStyle (config) {
|
||||
config = mergeConfig(config, DEFAULT_CONFIG)
|
||||
return `${getImports(config)}
|
||||
|
||||
/* Background colors */
|
||||
body {
|
||||
overflow: hidden;
|
||||
${config.bgColor ? `background-color: ${config.bgColor};` : ''}
|
||||
}
|
||||
|
||||
/* Transparent background. */
|
||||
yt-live-chat-renderer {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
${getMessageColorStyle('', config.messageBgColor, config.useBarsInsteadOfBg)}
|
||||
|
||||
${getMessageColorStyle('owner', config.ownerMessageBgColor, config.useBarsInsteadOfBg)}
|
||||
|
||||
${getMessageColorStyle('moderator', config.moderatorMessageBgColor, config.useBarsInsteadOfBg)}
|
||||
|
||||
${getMessageColorStyle('member', config.memberMessageBgColor, config.useBarsInsteadOfBg)}
|
||||
|
||||
yt-live-chat-author-chip #author-name {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
/* Outlines */
|
||||
yt-live-chat-renderer * {
|
||||
${getShowOutlinesStyle(config)}
|
||||
font-family: "${config.messageFont}"${FALLBACK_FONTS};
|
||||
font-size: ${config.messageFontSize}px !important;
|
||||
line-height: ${config.messageLineHeight}px !important;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer #content,
|
||||
yt-live-chat-legacy-paid-message-renderer #content {
|
||||
overflow: initial !important;
|
||||
}
|
||||
|
||||
/* Hide scrollbar. */
|
||||
yt-live-chat-item-list-renderer #items {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
yt-live-chat-item-list-renderer #item-scroller {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* Hide header and input. */
|
||||
yt-live-chat-header-renderer,
|
||||
yt-live-chat-message-input-renderer {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Reduce side padding. */
|
||||
yt-live-chat-text-message-renderer,
|
||||
yt-live-chat-legacy-paid-message-renderer {
|
||||
${getPaddingStyle(config)}
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer #header {
|
||||
${getPaddingStyle(config)}
|
||||
}
|
||||
|
||||
/* Avatars. */
|
||||
yt-live-chat-text-message-renderer #author-photo,
|
||||
yt-live-chat-text-message-renderer #author-photo img,
|
||||
yt-live-chat-paid-message-renderer #author-photo,
|
||||
yt-live-chat-paid-message-renderer #author-photo img,
|
||||
yt-live-chat-legacy-paid-message-renderer #author-photo,
|
||||
yt-live-chat-legacy-paid-message-renderer #author-photo img {
|
||||
${config.showAvatars ? '' : 'display: none !important;'}
|
||||
width: ${config.avatarSize}px !important;
|
||||
height: ${config.avatarSize}px !important;
|
||||
border-radius: ${config.avatarSize}px !important;
|
||||
margin-right: ${config.avatarSize / 4}px !important;
|
||||
}
|
||||
|
||||
/* Hide badges. */
|
||||
yt-live-chat-text-message-renderer #chat-badges {
|
||||
${config.showBadges ? '' : 'display: none !important;'}
|
||||
vertical-align: text-top !important;
|
||||
}
|
||||
|
||||
/* Timestamps. */
|
||||
yt-live-chat-text-message-renderer #timestamp {
|
||||
display: ${config.showTime ? 'inline' : 'none'} !important;
|
||||
${config.timeColor ? `color: ${config.timeColor} !important;` : ''}
|
||||
font-family: "${config.timeFont}"${FALLBACK_FONTS};
|
||||
font-size: ${config.timeFontSize}px !important;
|
||||
line-height: ${config.timeLineHeight || config.timeFontSize}px !important;
|
||||
}
|
||||
|
||||
/* Badges. */
|
||||
yt-live-chat-text-message-renderer #author-name[type="owner"],
|
||||
yt-live-chat-text-message-renderer yt-live-chat-author-badge-renderer[type="owner"] {
|
||||
${config.ownerUserNameColor ? `color: ${config.ownerUserNameColor} !important;` : ''}
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer #author-name[type="moderator"],
|
||||
yt-live-chat-text-message-renderer yt-live-chat-author-badge-renderer[type="moderator"] {
|
||||
${config.moderatorUserNameColor ? `color: ${config.moderatorUserNameColor} !important;` : ''}
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer #author-name[type="member"],
|
||||
yt-live-chat-text-message-renderer yt-live-chat-author-badge-renderer[type="member"] {
|
||||
${config.memberUserNameColor ? `color: ${config.memberUserNameColor} !important;` : ''}
|
||||
}
|
||||
|
||||
/* Channel names. */
|
||||
yt-live-chat-text-message-renderer #author-name {
|
||||
${config.userNameColor ? `color: ${config.userNameColor} !important;` : ''}
|
||||
font-family: "${config.userNameFont}"${FALLBACK_FONTS};
|
||||
font-size: ${config.userNameFontSize}px !important;
|
||||
line-height: ${config.userNameLineHeight || config.userNameFontSize}px !important;
|
||||
}
|
||||
|
||||
${getShowColonStyle(config)}
|
||||
|
||||
/* Messages. */
|
||||
yt-live-chat-text-message-renderer #message,
|
||||
yt-live-chat-text-message-renderer #message * {
|
||||
${config.messageColor ? `color: ${config.messageColor} !important;` : ''}
|
||||
font-family: "${config.messageFont}"${FALLBACK_FONTS};
|
||||
font-size: ${config.messageFontSize}px !important;
|
||||
line-height: ${config.messageLineHeight || config.messageFontSize}px !important;
|
||||
}
|
||||
|
||||
${!config.messageOnNewLine ? '' : `yt-live-chat-text-message-renderer #message {
|
||||
display: block !important;
|
||||
}`}
|
||||
|
||||
/* SuperChat/Fan Funding Messages. */
|
||||
yt-live-chat-paid-message-renderer #author-name,
|
||||
yt-live-chat-paid-message-renderer #author-name *,
|
||||
yt-live-chat-legacy-paid-message-renderer #event-text,
|
||||
yt-live-chat-legacy-paid-message-renderer #event-text * {
|
||||
${config.firstLineColor ? `color: ${config.firstLineColor} !important;` : ''}
|
||||
font-family: "${config.firstLineFont}"${FALLBACK_FONTS};
|
||||
font-size: ${config.firstLineFontSize}px !important;
|
||||
line-height: ${config.firstLineLineHeight || config.firstLineFontSize}px !important;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer #purchase-amount,
|
||||
yt-live-chat-paid-message-renderer #purchase-amount *,
|
||||
yt-live-chat-legacy-paid-message-renderer #detail-text,
|
||||
yt-live-chat-legacy-paid-message-renderer #detail-text * {
|
||||
${config.secondLineColor ? `color: ${config.secondLineColor} !important;` : ''}
|
||||
font-family: "${config.secondLineFont}"${FALLBACK_FONTS};
|
||||
font-size: ${config.secondLineFontSize}px !important;
|
||||
line-height: ${config.secondLineLineHeight || config.secondLineFontSize}px !important;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer #content,
|
||||
yt-live-chat-paid-message-renderer #content * {
|
||||
${config.scContentColor ? `color: ${config.scContentColor} !important;` : ''}
|
||||
font-family: "${config.scContentFont}"${FALLBACK_FONTS};
|
||||
font-size: ${config.scContentFontSize}px !important;
|
||||
line-height: ${config.scContentLineHeight || config.scContentFontSize}px !important;
|
||||
}
|
||||
|
||||
yt-live-chat-paid-message-renderer {
|
||||
margin: 4px 0 !important;
|
||||
}
|
||||
|
||||
yt-live-chat-legacy-paid-message-renderer #card {
|
||||
${getShowNewMemberBgStyle(config)}
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer a,
|
||||
yt-live-chat-legacy-paid-message-renderer a {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer[is-deleted],
|
||||
yt-live-chat-legacy-paid-message-renderer[is-deleted] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
yt-live-chat-ticker-renderer {
|
||||
background-color: transparent !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
${config.showScTicker ? '' : `yt-live-chat-ticker-renderer {
|
||||
display: none !important;
|
||||
}`}
|
||||
|
||||
${config.showOtherThings ? '' : `yt-live-chat-item-list-renderer {
|
||||
display: none !important;
|
||||
}`}
|
||||
|
||||
yt-live-chat-ticker-paid-message-item-renderer,
|
||||
yt-live-chat-ticker-paid-message-item-renderer *,
|
||||
yt-live-chat-ticker-sponsor-item-renderer,
|
||||
yt-live-chat-ticker-sponsor-item-renderer * {
|
||||
${config.secondLineColor ? `color: ${config.secondLineColor} !important;` : ''}
|
||||
font-family: "${config.secondLineFont}"${FALLBACK_FONTS};
|
||||
}
|
||||
|
||||
yt-live-chat-mode-change-message-renderer,
|
||||
yt-live-chat-viewer-engagement-message-renderer,
|
||||
yt-live-chat-restricted-participation-renderer {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
${getAnimationStyle(config)}
|
||||
`
|
||||
}
|
||||
|
||||
function getImports (config) {
|
||||
let fontsNeedToImport = new Set()
|
||||
for (let name of ['userNameFont', 'messageFont', 'timeFont', 'firstLineFont', 'secondLineFont', 'scContentFont']) {
|
||||
let font = config[name]
|
||||
if (fonts.NETWORK_FONTS.indexOf(font) !== -1) {
|
||||
fontsNeedToImport.add(font)
|
||||
}
|
||||
}
|
||||
let res = []
|
||||
for (let font of fontsNeedToImport) {
|
||||
res.push(`@import url("https://fonts.googleapis.com/css?family=${encodeURIComponent(font)}");`)
|
||||
}
|
||||
return res.join('\n')
|
||||
}
|
||||
|
||||
function getMessageColorStyle (authorType, color, useBarsInsteadOfBg) {
|
||||
let typeSelector = authorType ? `[author-type="${authorType}"]` : ''
|
||||
if (!useBarsInsteadOfBg) {
|
||||
return `yt-live-chat-text-message-renderer${typeSelector},
|
||||
yt-live-chat-text-message-renderer${typeSelector}[is-highlighted] {
|
||||
${color ? `background-color: ${color} !important;` : ''}
|
||||
}`
|
||||
} else {
|
||||
return `yt-live-chat-text-message-renderer${typeSelector}::after {
|
||||
${color ? `border: 2px solid ${color};` : ''}
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: block;
|
||||
left: 8px;
|
||||
top: 4px;
|
||||
bottom: 4px;
|
||||
width: 1px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 2px;
|
||||
}`
|
||||
}
|
||||
}
|
||||
|
||||
function getShowOutlinesStyle (config) {
|
||||
if (!config.showOutlines || !config.outlineSize) {
|
||||
return ''
|
||||
}
|
||||
let shadow = []
|
||||
for (var x = -config.outlineSize; x <= config.outlineSize; x += Math.ceil(config.outlineSize / 4)) {
|
||||
for (var y = -config.outlineSize; y <= config.outlineSize; y += Math.ceil(config.outlineSize / 4)) {
|
||||
shadow.push(`${x}px ${y}px ${config.outlineColor}`)
|
||||
}
|
||||
}
|
||||
return `text-shadow: ${shadow.join(', ')};`
|
||||
}
|
||||
|
||||
function getPaddingStyle (config) {
|
||||
return `padding-left: ${config.useBarsInsteadOfBg ? 20 : 4}px !important;
|
||||
padding-right: 4px !important;`
|
||||
}
|
||||
|
||||
function getShowColonStyle (config) {
|
||||
if (!config.showColon) {
|
||||
return ''
|
||||
}
|
||||
return `yt-live-chat-text-message-renderer #author-name::after {
|
||||
content: ":";
|
||||
margin-left: ${config.outlineSize}px;
|
||||
}`
|
||||
}
|
||||
|
||||
function getShowNewMemberBgStyle (config) {
|
||||
if (config.showNewMemberBg) {
|
||||
return `background-color: ${config.memberUserNameColor} !important;
|
||||
margin: 4px 0 !important;`
|
||||
} else {
|
||||
return `background-color: transparent !important;
|
||||
box-shadow: none !important;
|
||||
margin: 0 !important;`
|
||||
}
|
||||
}
|
||||
|
||||
function getAnimationStyle (config) {
|
||||
if (!config.animateIn && !config.animateOut) {
|
||||
return ''
|
||||
}
|
||||
let totalTime = 0
|
||||
if (config.animateIn) {
|
||||
totalTime += config.fadeInTime
|
||||
}
|
||||
if (config.animateOut) {
|
||||
totalTime += config.animateOutWaitTime * 1000
|
||||
totalTime += config.fadeOutTime
|
||||
}
|
||||
let keyframes = []
|
||||
let curTime = 0
|
||||
if (config.animateIn) {
|
||||
keyframes.push(` 0% { opacity: 0;${!config.slide ? ''
|
||||
: ` transform: translateX(${config.reverseSlide ? 16 : -16}px);`
|
||||
} }`)
|
||||
curTime += config.fadeInTime
|
||||
keyframes.push(` ${(curTime / totalTime) * 100}% { opacity: 1; transform: none; }`)
|
||||
}
|
||||
if (config.animateOut) {
|
||||
curTime += config.animateOutWaitTime * 1000
|
||||
keyframes.push(` ${(curTime / totalTime) * 100}% { opacity: 1; transform: none; }`)
|
||||
curTime += config.fadeOutTime
|
||||
keyframes.push(` ${(curTime / totalTime) * 100}% { opacity: 0;${!config.slide ? ''
|
||||
: ` transform: translateX(${config.reverseSlide ? -16 : 16}px);`
|
||||
} }`)
|
||||
}
|
||||
return `@keyframes anim {
|
||||
${keyframes.join('\n')}
|
||||
}
|
||||
|
||||
yt-live-chat-text-message-renderer,
|
||||
yt-live-chat-legacy-paid-message-renderer {
|
||||
animation: anim ${totalTime}ms;
|
||||
animation-fill-mode: both;
|
||||
}`
|
||||
}
|
||||
|
||||
export default {
|
||||
DEFAULT_CONFIG,
|
||||
setLocalConfig,
|
||||
getLocalConfig,
|
||||
getStyle
|
||||
}
|
||||
38
main.py
@@ -1,30 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import webbrowser
|
||||
|
||||
import tornado.ioloop
|
||||
import tornado.web
|
||||
|
||||
import chat
|
||||
import update
|
||||
import views.chat
|
||||
import views.config
|
||||
import views.main
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
WEB_ROOT = os.path.join(os.path.dirname(__file__), 'frontend', 'dist')
|
||||
|
||||
|
||||
# noinspection PyAbstractClass
|
||||
class MainHandler(tornado.web.StaticFileHandler):
|
||||
"""为了使用Vue Router的history模式,把所有请求转发到index.html"""
|
||||
async def get(self, *args, **kwargs):
|
||||
await super().get('index.html', *args, **kwargs)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='用于OBS的仿YouTube风格的bilibili直播聊天层')
|
||||
parser.add_argument('--host', help='服务器host,默认为127.0.0.1', default='127.0.0.1')
|
||||
parser.add_argument('--port', help='服务器端口,默认为80', type=int, default=80)
|
||||
parser.add_argument('--port', help='服务器端口,默认为12450', type=int, default=12450)
|
||||
parser.add_argument('--debug', help='调试模式', action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -35,19 +33,31 @@ def main():
|
||||
level=logging.INFO if not args.debug else logging.DEBUG
|
||||
)
|
||||
|
||||
asyncio.ensure_future(update.check_update())
|
||||
|
||||
app = tornado.web.Application(
|
||||
[
|
||||
(r'/chat', chat.ChatHandler),
|
||||
(r'/((css|img|js)/.*)', tornado.web.StaticFileHandler, {'path': WEB_ROOT}),
|
||||
(r'/chat', views.chat.ChatHandler),
|
||||
(r'/config', views.config.ConfigsHandler),
|
||||
(r'/config/(.+)', views.config.ConfigHandler),
|
||||
|
||||
(r'/((css|fonts|img|js|static)/.*)', tornado.web.StaticFileHandler, {'path': WEB_ROOT}),
|
||||
(r'/(favicon\.ico)', tornado.web.StaticFileHandler, {'path': WEB_ROOT}),
|
||||
(r'/.*', MainHandler, {'path': WEB_ROOT})
|
||||
(r'/.*', views.main.MainHandler, {'path': WEB_ROOT})
|
||||
],
|
||||
websocket_ping_interval=30,
|
||||
debug=args.debug,
|
||||
autoreload=False
|
||||
)
|
||||
app.listen(args.port, args.host)
|
||||
logger.info('服务器启动:%s:%d', args.host, args.port)
|
||||
try:
|
||||
app.listen(args.port, args.host)
|
||||
except OSError:
|
||||
logger.warning('Address is used %s:%d', args.host, args.port)
|
||||
return
|
||||
finally:
|
||||
url = 'http://localhost' if args.port == 80 else f'http://localhost:{args.port}'
|
||||
webbrowser.open(url)
|
||||
logger.info('Server started: %s:%d', args.host, args.port)
|
||||
tornado.ioloop.IOLoop.current().start()
|
||||
|
||||
|
||||
|
||||
BIN
screenshot.png
|
Before Width: | Height: | Size: 305 KiB |
BIN
screenshots/chrome.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
screenshots/obs.png
Normal file
|
After Width: | Height: | Size: 288 KiB |
BIN
screenshots/stylegen.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
15
update.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import aiohttp
|
||||
|
||||
VERSION = 'v1.2.0'
|
||||
|
||||
|
||||
async def check_update():
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get('https://api.github.com/repos/xfgryujk/blivechat/releases/latest') as r:
|
||||
data = await r.json()
|
||||
if data['name'] != VERSION:
|
||||
print('New version available:', data['name'])
|
||||
print(data['body'])
|
||||
print('Download:', data['html_url'])
|
||||
28
views/base.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
|
||||
import tornado.web
|
||||
|
||||
|
||||
# noinspection PyAbstractClass
|
||||
class ApiHandler(tornado.web.RequestHandler):
|
||||
def set_default_headers(self):
|
||||
# 跨域测试用
|
||||
if not self.application.settings['debug']:
|
||||
return
|
||||
self.set_header('Access-Control-Allow-Origin', '*')
|
||||
self.set_header('Access-Control-Allow-Methods', 'OPTIONS, PUT, POST, GET, DELETE')
|
||||
if 'Access-Control-Request-Headers' in self.request.headers:
|
||||
self.set_header('Access-Control-Allow-Headers',
|
||||
self.request.headers['Access-Control-Request-Headers'])
|
||||
|
||||
def prepare(self):
|
||||
if self.request.headers.get('Content-Type', '').startswith('application/json'):
|
||||
self.json_args = json.loads(self.request.body)
|
||||
else:
|
||||
self.json_args = None
|
||||
|
||||
async def options(self, *_args, **_kwargs):
|
||||
# 跨域测试用
|
||||
self.set_status(204 if self.application.settings['debug'] else 405)
|
||||
282
views/chat.py
Normal file
@@ -0,0 +1,282 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import asyncio
|
||||
import datetime
|
||||
import enum
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from typing import *
|
||||
|
||||
import aiohttp
|
||||
import tornado.websocket
|
||||
|
||||
import blivedm.blivedm as blivedm
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(enum.IntEnum):
|
||||
HEARTBEAT = 0
|
||||
JOIN_ROOM = 1
|
||||
ADD_TEXT = 2
|
||||
ADD_GIFT = 3
|
||||
ADD_MEMBER = 4
|
||||
ADD_SUPER_CHAT = 5
|
||||
DEL_SUPER_CHAT = 6
|
||||
|
||||
|
||||
DEFAULT_AVATAR_URL = 'https://static.hdslb.com/images/member/noface.gif'
|
||||
|
||||
_http_session = aiohttp.ClientSession()
|
||||
_avatar_url_cache: Dict[int, str] = {}
|
||||
_last_fetch_avatar_time = datetime.datetime.now()
|
||||
_last_avatar_failed_time = None
|
||||
_uids_to_fetch_avatar = asyncio.Queue(15)
|
||||
|
||||
|
||||
async def get_avatar_url(user_id):
|
||||
if user_id in _avatar_url_cache:
|
||||
return _avatar_url_cache[user_id]
|
||||
|
||||
global _last_avatar_failed_time, _last_fetch_avatar_time
|
||||
cur_time = datetime.datetime.now()
|
||||
# 防止获取头像频率太高被ban
|
||||
if (cur_time - _last_fetch_avatar_time).total_seconds() < 0.2:
|
||||
# 由_fetch_avatar_loop过一段时间再获取并缓存
|
||||
try:
|
||||
_uids_to_fetch_avatar.put_nowait(user_id)
|
||||
except asyncio.QueueFull:
|
||||
pass
|
||||
return DEFAULT_AVATAR_URL
|
||||
|
||||
if _last_avatar_failed_time is not None:
|
||||
if (cur_time - _last_avatar_failed_time).total_seconds() < 3 * 60 + 3:
|
||||
# 3分钟以内被ban,解封大约要15分钟
|
||||
return DEFAULT_AVATAR_URL
|
||||
else:
|
||||
_last_avatar_failed_time = None
|
||||
|
||||
_last_fetch_avatar_time = cur_time
|
||||
try:
|
||||
async with _http_session.get('https://api.bilibili.com/x/space/acc/info',
|
||||
params={'mid': user_id}) as r:
|
||||
if r.status != 200: # 可能会被B站ban
|
||||
logger.warning('Failed to fetch avatar: status=%d %s uid=%d', r.status, r.reason, user_id)
|
||||
_last_avatar_failed_time = cur_time
|
||||
return DEFAULT_AVATAR_URL
|
||||
data = await r.json()
|
||||
except aiohttp.ClientConnectionError:
|
||||
return DEFAULT_AVATAR_URL
|
||||
url = data['data']['face']
|
||||
if not url.endswith('noface.gif'):
|
||||
url += '@48w_48h'
|
||||
_avatar_url_cache[user_id] = url
|
||||
|
||||
if len(_avatar_url_cache) > 50000:
|
||||
for _, key in zip(range(100), _avatar_url_cache):
|
||||
del _avatar_url_cache[key]
|
||||
|
||||
return url
|
||||
|
||||
|
||||
async def _fetch_avatar_loop():
|
||||
while True:
|
||||
try:
|
||||
user_id = await _uids_to_fetch_avatar.get()
|
||||
if user_id in _avatar_url_cache:
|
||||
continue
|
||||
# 延时长一些使实时弹幕有机会获取头像
|
||||
await asyncio.sleep(0.4 - (datetime.datetime.now() - _last_fetch_avatar_time).total_seconds())
|
||||
asyncio.ensure_future(get_avatar_url(user_id))
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
asyncio.ensure_future(_fetch_avatar_loop())
|
||||
|
||||
|
||||
class Room(blivedm.BLiveClient):
|
||||
def __init__(self, room_id):
|
||||
super().__init__(room_id, session=_http_session, heartbeat_interval=10)
|
||||
self.clients: List['ChatHandler'] = []
|
||||
|
||||
def stop_and_close(self):
|
||||
if self.is_running:
|
||||
future = self.stop()
|
||||
future.add_done_callback(lambda _future: asyncio.ensure_future(self.close()))
|
||||
else:
|
||||
asyncio.ensure_future(self.close())
|
||||
|
||||
def send_message(self, cmd, data):
|
||||
body = json.dumps({'cmd': cmd, 'data': data})
|
||||
for client in self.clients:
|
||||
client.write_message(body)
|
||||
|
||||
async def _on_receive_danmaku(self, danmaku: blivedm.DanmakuMessage):
|
||||
asyncio.ensure_future(self.__on_receive_danmaku(danmaku))
|
||||
|
||||
async def __on_receive_danmaku(self, danmaku: blivedm.DanmakuMessage):
|
||||
if danmaku.uid == self.room_owner_uid:
|
||||
author_type = 3 # 主播
|
||||
elif danmaku.admin:
|
||||
author_type = 2 # 房管
|
||||
elif danmaku.privilege_type != 0: # 1总督,2提督,3舰长
|
||||
author_type = 1 # 舰队
|
||||
else:
|
||||
author_type = 0
|
||||
self.send_message(Command.ADD_TEXT, {
|
||||
'avatarUrl': await get_avatar_url(danmaku.uid),
|
||||
'timestamp': danmaku.timestamp,
|
||||
'authorName': danmaku.uname,
|
||||
'authorType': author_type,
|
||||
'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),
|
||||
'medalLevel': 0 if danmaku.room_id != self.room_id else danmaku.medal_level
|
||||
})
|
||||
|
||||
async def _on_receive_gift(self, gift: blivedm.GiftMessage):
|
||||
if gift.coin_type != 'gold': # 丢人
|
||||
return
|
||||
self.send_message(Command.ADD_GIFT, {
|
||||
'avatarUrl': gift.face,
|
||||
'timestamp': gift.timestamp,
|
||||
'authorName': gift.uname,
|
||||
'giftName': gift.gift_name,
|
||||
'giftNum': gift.num,
|
||||
'totalCoin': gift.total_coin
|
||||
})
|
||||
|
||||
async def _on_buy_guard(self, message: blivedm.GuardBuyMessage):
|
||||
asyncio.ensure_future(self.__on_buy_guard(message))
|
||||
|
||||
async def __on_buy_guard(self, message: blivedm.GuardBuyMessage):
|
||||
self.send_message(Command.ADD_MEMBER, {
|
||||
'avatarUrl': await get_avatar_url(message.uid),
|
||||
'timestamp': message.start_time,
|
||||
'authorName': message.username
|
||||
})
|
||||
|
||||
async def _on_super_chat(self, message: blivedm.SuperChatMessage):
|
||||
self.send_message(Command.ADD_SUPER_CHAT, {
|
||||
'avatarUrl': message.face,
|
||||
'timestamp': message.start_time,
|
||||
'authorName': message.uname,
|
||||
'price': message.price,
|
||||
'content': message.message,
|
||||
'id': message.id
|
||||
})
|
||||
|
||||
async def _on_super_chat_delete(self, message: blivedm.SuperChatDeleteMessage):
|
||||
self.send_message(Command.ADD_SUPER_CHAT, {
|
||||
'ids': message.ids
|
||||
})
|
||||
|
||||
|
||||
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()
|
||||
room.clients.append(client)
|
||||
|
||||
if client.application.settings['debug']:
|
||||
self.__send_test_message(room)
|
||||
|
||||
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)
|
||||
if not room.clients:
|
||||
logger.info('Removing room %d', room_id)
|
||||
room.stop_and_close()
|
||||
del self._rooms[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)
|
||||
|
||||
|
||||
room_manager = RoomManager()
|
||||
|
||||
|
||||
# noinspection PyAbstractClass
|
||||
class ChatHandler(tornado.websocket.WebSocketHandler):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.room_id = None
|
||||
|
||||
def open(self):
|
||||
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:
|
||||
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)
|
||||
else:
|
||||
logger.warning('Unknown cmd: %s data: %s', body['cmd'], body['data'])
|
||||
|
||||
def on_close(self):
|
||||
logger.info('Websocket disconnected %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)
|
||||
|
||||
# 跨域测试用
|
||||
def check_origin(self, origin):
|
||||
if self.application.settings['debug']:
|
||||
return True
|
||||
return super().check_origin(origin)
|
||||
50
views/config.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import uuid
|
||||
|
||||
import views.base
|
||||
from typing import *
|
||||
|
||||
configs: Dict[str, dict] = {}
|
||||
|
||||
ALLOWED_FIELDS = (
|
||||
'showDanmaku', 'showGift', 'mergeSimilarDanmaku', 'minGiftPrice', 'maxSpeed',
|
||||
'maxNumber', 'blockGiftDanmaku', 'blockLevel', 'blockNewbie', 'blockNotMobileVerified',
|
||||
'blockKeywords', 'blockUsers', 'blockMedalLevel', 'css'
|
||||
)
|
||||
|
||||
|
||||
# noinspection PyAbstractClass
|
||||
class ConfigsHandler(views.base.ApiHandler):
|
||||
async def post(self):
|
||||
config_id = str(uuid.uuid4())
|
||||
config = {
|
||||
name: self.json_args[name] for name in ALLOWED_FIELDS
|
||||
}
|
||||
config['id'] = config_id
|
||||
configs[config_id] = config
|
||||
self.set_status(201)
|
||||
self.write(config)
|
||||
|
||||
if len(configs) > 10000:
|
||||
for _, key in zip(range(100), configs):
|
||||
del configs[key]
|
||||
|
||||
|
||||
# noinspection PyAbstractClass
|
||||
class ConfigHandler(views.base.ApiHandler):
|
||||
async def put(self, config_id):
|
||||
config = configs.get(config_id, None)
|
||||
if config is None:
|
||||
self.set_status(404)
|
||||
return
|
||||
for name in ALLOWED_FIELDS:
|
||||
config[name] = self.json_args[name]
|
||||
self.write(config)
|
||||
|
||||
async def get(self, config_id):
|
||||
config = configs.get(config_id, None)
|
||||
if config is None:
|
||||
self.set_status(404)
|
||||
return
|
||||
self.write(config)
|
||||
10
views/main.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import tornado.web
|
||||
|
||||
|
||||
# noinspection PyAbstractClass
|
||||
class MainHandler(tornado.web.StaticFileHandler):
|
||||
"""为了使用Vue Router的history模式,把所有请求转发到index.html"""
|
||||
async def get(self, *args, **kwargs):
|
||||
await super().get('index.html', *args, **kwargs)
|
||||