Compare commits

..

68 Commits

Author SHA1 Message Date
John Smith
e7d8fd0ff6 Merge branch 'dev' 2020-10-11 14:51:55 +08:00
John Smith
cce51a6990 更新版本号v1.5.0 2020-10-11 14:37:30 +08:00
John Smith
adeba8f552 优化SC固定栏 2020-10-11 14:36:23 +08:00
John Smith
0e06c4ba24 前端头像加载失败则使用默认头像 2020-10-11 13:14:10 +08:00
John Smith
0e060e61fa 修改一些说明 2020-10-11 13:00:57 +08:00
John Smith
8cf4310152 init_room支持降级 2020-10-11 12:52:15 +08:00
John Smith
d6897ca4b4 添加通过服务器转发消息配置 2020-09-14 21:50:40 +08:00
John Smith
6e2f3b1a0c 完成房间信息和头像接口 2020-09-13 21:24:20 +08:00
John Smith
6a64f3c795 添加前端直连B站弹幕服务器 2020-09-13 16:29:49 +08:00
John Smith
2dbf1769e0 分离房间和弹幕API 2020-09-12 17:37:52 +08:00
John Smith
54aac237ee Merge pull request #30 from xfgryujk/dependabot/npm_and_yarn/frontend/http-proxy-1.18.1
Bump http-proxy from 1.18.0 to 1.18.1 in /frontend
2020-09-12 15:57:13 +08:00
John Smith
2ec6096d4d 把YouTube的CSS移到单独文件 2020-09-12 15:50:16 +08:00
John Smith
3808609963 数据库头像URL类型改成VARCHAR 2020-09-12 11:33:11 +08:00
John Smith
c66a47c3e5 git忽略日志 2020-09-12 10:51:29 +08:00
dependabot[bot]
435ee43c98 Bump http-proxy from 1.18.0 to 1.18.1 in /frontend
Bumps [http-proxy](https://github.com/http-party/node-http-proxy) from 1.18.0 to 1.18.1.
- [Release notes](https://github.com/http-party/node-http-proxy/releases)
- [Changelog](https://github.com/http-party/node-http-proxy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/http-party/node-http-proxy/compare/1.18.0...1.18.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-09 00:12:58 +00:00
John Smith
cb150effbb Merge branch 'dev' 2020-09-05 12:08:50 +08:00
John Smith
47d7528b61 更新版本号v1.4.5 2020-09-05 12:08:17 +08:00
John Smith
152db9f00a 优化消息平滑 2020-09-05 12:05:01 +08:00
John Smith
7a244757d2 修复OBS 25.0.8不支持中文字体名的问题 2020-09-05 09:44:40 +08:00
John Smith
626099f835 docker暴露一些目录 2020-09-05 08:45:19 +08:00
John Smith
ca55f9917d 日志保存到文件 2020-09-03 20:01:54 +08:00
John Smith
4d58245ad9 添加加载器URL配置 2020-08-30 17:46:04 +08:00
John Smith
28b04f27ee 改一些默认配置 2020-08-29 14:27:15 +08:00
John Smith
1252e1942d 添加tornado xheaders配置 2020-08-22 18:38:52 +08:00
John Smith
38774252cc 更新blivedm 2020-08-19 23:17:29 +08:00
John Smith
7cfa94c68c 改API路径 2020-08-19 23:16:01 +08:00
John Smith
4e7cc7a782 更新nginx配置 2020-08-19 22:55:54 +08:00
John Smith
bfa6054899 显示的房间URL改成加载器URL 2020-08-18 21:48:33 +08:00
John Smith
d25a32beaf 增加牌子等级上限 2020-08-16 12:15:01 +08:00
John Smith
127344802d 添加允许自动翻译的房间配置 2020-08-16 12:13:53 +08:00
John Smith
96070ac473 更新使用方法 2020-08-16 10:10:49 +08:00
John Smith
583deb4588 Merge pull request #25 from xfgryujk/dependabot/npm_and_yarn/frontend/elliptic-6.5.3
Bump elliptic from 6.5.2 to 6.5.3 in /frontend
2020-07-30 23:43:30 +08:00
dependabot[bot]
b555c6ac2d Bump elliptic from 6.5.2 to 6.5.3 in /frontend
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.2 to 6.5.3.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.2...v6.5.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-30 15:40:11 +00:00
John Smith
602a9f2a44 更新初始化房间API 2020-07-19 22:19:05 +08:00
John Smith
a89b64b454 修改样式 2020-07-19 21:33:26 +08:00
John Smith
49d58a9a60 删除BiliSC链接 2020-07-19 16:31:49 +08:00
John Smith
cb7ecc066c Merge pull request #23 from xfgryujk/dependabot/npm_and_yarn/frontend/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19 in /frontend
2020-07-17 21:33:12 +08:00
dependabot[bot]
57aeada21e Bump lodash from 4.17.15 to 4.17.19 in /frontend
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-17 02:05:38 +00:00
John Smith
c2de73a1a5 Merge pull request #22 from xfgryujk/dependabot/npm_and_yarn/frontend/websocket-extensions-0.1.4
Bump websocket-extensions from 0.1.3 to 0.1.4 in /frontend
2020-06-07 01:46:39 +08:00
dependabot[bot]
71f02c0269 Bump websocket-extensions from 0.1.3 to 0.1.4 in /frontend
Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/faye/websocket-extensions-node/releases)
- [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-06-06 16:14:56 +00:00
John Smith
9b8a581a83 更新版本号v1.4.4 2020-06-06 11:41:55 +08:00
John Smith
f554c8b6d5 兼容B站弹幕升级(使用旧接口获取token会连不上某些服务器) 2020-05-20 21:26:00 +08:00
John Smith
b862a0906e 修复弹幕时间不正确的问题 2020-05-20 21:20:08 +08:00
John Smith
61b13808fb 更新版本号v1.4.3 2020-05-10 14:14:14 +08:00
John Smith
7f25c219d8 前端优化 2020-05-09 23:17:49 +08:00
John Smith
32ba4187aa 修改一些文本 2020-05-09 22:46:24 +08:00
John Smith
06c0dfa3f2 修复冒号两边空白太宽的问题、消息颜色影响重复标记颜色的问题 2020-05-09 22:15:34 +08:00
John Smith
4c7bf69275 (真)修复启动长时间后自动翻译失效的问题 2020-05-09 21:38:21 +08:00
John Smith
63644f5499 修复qtv、qtk反了 2020-03-21 19:13:06 +08:00
John Smith
8a57304790 更新版本号v1.4.2 2020-03-21 19:05:53 +08:00
John Smith
d7df071595 修复启动长时间后自动翻译失效的问题 2020-03-21 18:42:27 +08:00
John Smith
6e8b04ef4f 修复删缓存时的错误、提高容错性 2020-03-21 15:23:31 +08:00
John Smith
b73a4e3748 更新版本号v1.4.1 2020-03-01 14:22:12 +08:00
John Smith
102746d0b0 添加显示礼物名、只合并同类礼物 2020-03-01 14:14:19 +08:00
John Smith
2d237af6fd 提示用户已加载页面 2020-02-29 19:20:29 +08:00
John Smith
4030ff43cd 减小编译后文件尺寸 2020-02-22 17:21:47 +08:00
John Smith
092cc8d9ab 更新帮助 2020-02-06 20:31:43 +08:00
John Smith
2d7106ae93 更新版本号v1.4.0 2020-02-06 19:53:33 +08:00
John Smith
d5b885aab4 添加前端获取后端配置 2020-02-06 19:51:03 +08:00
John Smith
34f0effb6a 修复翻译导致弹幕无法滚动到底部的问题 2020-02-06 18:31:05 +08:00
John Smith
d3e9300fa4 添加自动翻译前端 2020-02-06 17:39:56 +08:00
John Smith
0c9560f8ca 添加自动翻译后端 2020-02-06 12:48:20 +08:00
John Smith
1c5df01fbc websocket消息优化 2020-02-05 17:28:10 +08:00
John Smith
49cdc56cff 防止队列中出现相同uid时重复获取头像 2020-02-05 12:58:26 +08:00
John Smith
00e1f31f84 优化前端防卡代码 2020-02-04 20:33:29 +08:00
John Smith
b7e06652b4 防止前端弹幕卡住 2020-02-04 20:13:11 +08:00
John Smith
0cf49ee042 防止更新版本后浏览器加载缓存 2020-02-04 16:26:28 +08:00
John Smith
7d610ecfdc 修复获取头像future没有设置结果的问题、部分头像URL没有处理的问题 2020-02-04 16:12:16 +08:00
61 changed files with 8899 additions and 13692 deletions

View File

@@ -7,10 +7,15 @@ build/
**/node_modules/
# IDEs and editors
/.idea
.idea/
# misc
**/.git*
*.spec
screenshots/
README.md
# runtime data
data/*
!data/config.ini
log/*

1
.gitignore vendored
View File

@@ -106,3 +106,4 @@ venv.bak/
.idea/
data/database.db
*.log*

View File

@@ -14,20 +14,22 @@ RUN wget https://nodejs.org/dist/v10.16.0/node-v10.16.0-linux-x64.tar.xz \
&& 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
COPY requirements.txt ./
RUN pip3 install --no-cache-dir -i https://pypi.tuna.tsinghua.edu.cn/simple -r requirements.txt
# 前端依赖
WORKDIR /blivechat/frontend
COPY frontend/package*.json ./
WORKDIR ./frontend
COPY frontend/package.json frontend/package-lock.json ./
RUN npm i --registry=https://registry.npm.taobao.org
# 编译
COPY . /blivechat
# 编译前端
COPY . ../
RUN npm run build
# 运行
WORKDIR /blivechat
WORKDIR ..
VOLUME /blivechat/data /blivechat/log /blivechat/frontend/dist
EXPOSE 12450
ENTRYPOINT ["python3", "main.py"]
CMD ["--host", "0.0.0.0", "--port", "12450"]

View File

@@ -1,7 +1,7 @@
# blivechat
用于OBS的仿YouTube风格的bilibili直播评论栏
最近喜欢看VTuber想为此写些程序于是有了这个东西。~~写到一半发现有类似项目了:[bilibili-live-chat](https://github.com/Tsuk1ko/bilibili-live-chat)、[BiliChat](https://github.com/3Shain/BiliChat)~~ 本项目就当做练手吧而且对YouTube的模仿程度更高
最近喜欢看VTuber想为此写些程序于是有了这个东西。~~写到一半发现有类似项目了:[bilibili-live-chat](https://github.com/Tsuk1ko/bilibili-live-chat)、[BiliChat](https://github.com/3Shain/BiliChat)~~
![OBS截图](https://github.com/xfgryujk/blivechat/blob/master/screenshots/obs.png)
![Chrome截图](https://github.com/xfgryujk/blivechat/blob/master/screenshots/chrome.png)
@@ -13,26 +13,40 @@
* 高亮舰队、房管、主播的用户名
* 支持屏蔽弹幕、合并相似弹幕等设置
* 自带样式生成器
* 支持自动翻译弹幕、醒目留言到日语
## 使用方法
### 本地使用
### 一、本地使用
1. 下载[发布版](https://github.com/xfgryujk/blivechat/releases)仅提供x64 Windows版
2. 双击`blivechat.exe`运行服务器或者用命令行可以指定host和端口号
```bat
blivechat.exe --host 127.0.0.1 --port 12450
```
3. 用浏览器打开[http://localhost:12450](http://localhost:12450)输入房间ID保存配置,复制房间URL
3. 用浏览器打开[http://localhost:12450](http://localhost:12450)输入房间ID复制房间URL
4. 用样式生成器生成样式复制CSS
5. 在OBS中添加浏览器源输入URL和自定义CSS
### 公共服务器
请优先在本地使用,使用公共服务器会有更大的弹幕延迟,而且服务器故障时可能出现直播事故
**注意事项:**
* [第三方公共服务器](http://chat.bilisc.com/)
* 本地使用时不要关闭blivechat.exe那个黑框否则不能继续获取弹幕
* 样式生成器没有列出所有本地字体,但是可以手动输入本地字体
### 二、公共服务器
请优先在本地使用,使用公共服务器会有更大的弹幕延迟,而且服务器故障时可能发生直播事故
* [公共服务器](http://chat.bilisc.com/)
* [仅样式生成器](https://style.vtbs.moe/)
### 源代码版
1. 编译前端需要安装Node.js和npm
### 三、源代码版自建服务器或在Windows以外平台
0. 由于使用了git子模块clone时需要加上`--recursive`参数
```sh
git clone --recursive https://github.com/xfgryujk/blivechat.git
```
如果已经clone拉子模块的方法
```sh
git submodule update --init --recursive
```
1. 编译前端需要安装Node.js
```sh
cd frontend
npm i
@@ -49,8 +63,73 @@
```
3. 用浏览器打开[http://localhost:12450](http://localhost:12450),以下略
### Docker
### 四、Docker(自建服务器)
1. ```sh
docker run -d -p 12450:12450 xfgryujk/blivechat:latest
docker run --name blivechat -d -p 12450:12450 \
--mount source=blc-data,target=/blivechat/data \
--mount source=blc-log,target=/blivechat/log \
--mount source=blc-frontend,target=/blivechat/frontend/dist \
xfgryujk/blivechat:latest
```
2. 用浏览器打开[http://localhost:12450](http://localhost:12450),以下略
### nginx配置可选
自建服务器时使用,`sudo vim /etc/nginx/sites-enabled/blivechat.conf`
```conf
upstream blivechat {
keepalive 8;
# blivechat地址
server 127.0.0.1:12450;
}
# 强制HTTPS
server {
listen 80;
listen [::]:80;
server_name YOUR.DOMAIN.NAME;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name YOUR.DOMAIN.NAME;
# SSL
ssl_certificate /PATH/TO/CERT.crt;
ssl_certificate_key /PATH/TO/CERT_KEY.key;
# 代理header
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Connection "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 静态文件
location / {
root /PATH/TO/BLIVECHAT/frontend/dist;
# 如果文件不存在,交给前端路由
try_files $uri $uri/ /index.html;
}
# 动态API
location /api {
proxy_pass http://blivechat;
}
# websocket
location = /api/chat {
proxy_pass http://blivechat;
# 代理websocket必须设置
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
# 由于这个块有proxy_set_header这些不会自动继承
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
```

View File

@@ -4,14 +4,19 @@ import asyncio
import enum
import json
import logging
import random
import time
import uuid
from typing import *
import aiohttp
import tornado.websocket
import api.base
import blivedm.blivedm as blivedm
import config
import models.avatar
import models.translate
logger = logging.getLogger(__name__)
@@ -24,6 +29,7 @@ class Command(enum.IntEnum):
ADD_MEMBER = 4
ADD_SUPER_CHAT = 5
DEL_SUPER_CHAT = 6
UPDATE_TRANSLATION = 7
_http_session = aiohttp.ClientSession()
@@ -66,7 +72,7 @@ class Room(blivedm.BLiveClient):
def __parse_buy_guard(self, command):
data = command['data']
return self._on_buy_guard(blivedm.GuardBuyMessage(
data['uid'], data['username'], None, None, None,
data['uid'], data['username'], data['guard_level'], None, None,
None, None, data['start_time'], None
))
@@ -93,6 +99,11 @@ class Room(blivedm.BLiveClient):
def __init__(self, room_id):
super().__init__(room_id, session=_http_session, heartbeat_interval=10)
self.clients: List['ChatHandler'] = []
self.auto_translate_count = 0
async def init_room(self):
await super().init_room()
return True
def stop_and_close(self):
if self.is_running:
@@ -107,7 +118,15 @@ class Room(blivedm.BLiveClient):
try:
client.write_message(body)
except tornado.websocket.WebSocketClosedError:
pass
room_manager.del_client(self.room_id, client)
def send_message_if(self, can_send_func: Callable[['ChatHandler'], bool], cmd, data):
body = json.dumps({'cmd': cmd, 'data': data})
for client in filter(can_send_func, self.clients):
try:
client.write_message(body)
except tornado.websocket.WebSocketClosedError:
room_manager.del_client(self.room_id, client)
async def _on_receive_danmaku(self, danmaku: blivedm.DanmakuMessage):
asyncio.ensure_future(self.__on_receive_danmaku(danmaku))
@@ -121,61 +140,165 @@ class Room(blivedm.BLiveClient):
author_type = 1 # 舰队
else:
author_type = 0
self.send_message(Command.ADD_TEXT, {
'avatarUrl': await models.avatar.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
})
need_translate = self._need_translate(danmaku.msg)
if need_translate:
translation = models.translate.get_translation_from_cache(danmaku.msg)
if translation is None:
# 没有缓存,需要后面异步翻译后通知
translation = ''
else:
need_translate = False
else:
translation = ''
id_ = uuid.uuid4().hex
# 为了节省带宽用list而不是dict
self.send_message(Command.ADD_TEXT, make_text_message(
await models.avatar.get_avatar_url(danmaku.uid),
int(danmaku.timestamp / 1000),
danmaku.uname,
author_type,
danmaku.msg,
danmaku.privilege_type,
danmaku.msg_type,
danmaku.user_level,
danmaku.urank < 10000,
danmaku.mobile_verify,
0 if danmaku.room_id != self.room_id else danmaku.medal_level,
id_,
translation
))
if need_translate:
await self._translate_and_response(danmaku.msg, id_)
async def _on_receive_gift(self, gift: blivedm.GiftMessage):
avatar_url = gift.face.replace('http:', '').replace('https:', '')
avatar_url = models.avatar.process_avatar_url(gift.face)
models.avatar.update_avatar_cache(gift.uid, avatar_url)
if gift.coin_type != 'gold': # 丢人
return
id_ = uuid.uuid4().hex
self.send_message(Command.ADD_GIFT, {
'id': id_,
'avatarUrl': avatar_url,
'timestamp': gift.timestamp,
'authorName': gift.uname,
'totalCoin': gift.total_coin,
'giftName': gift.gift_name,
'giftNum': gift.num,
'totalCoin': gift.total_coin
'num': gift.num
})
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):
id_ = uuid.uuid4().hex
self.send_message(Command.ADD_MEMBER, {
'avatarUrl': await models.avatar.get_avatar_url(message.uid),
'id': id_,
'avatarUrl': await models.avatar.get_avatar_url(message.uid),
'timestamp': message.start_time,
'authorName': message.username
'authorName': message.username,
'privilegeType': message.guard_level
})
async def _on_super_chat(self, message: blivedm.SuperChatMessage):
avatar_url = message.face.replace('http:', '').replace('https:', '')
avatar_url = models.avatar.process_avatar_url(message.face)
models.avatar.update_avatar_cache(message.uid, avatar_url)
need_translate = self._need_translate(message.message)
if need_translate:
translation = models.translate.get_translation_from_cache(message.message)
if translation is None:
# 没有缓存,需要后面异步翻译后通知
translation = ''
else:
need_translate = False
else:
translation = ''
id_ = str(message.id)
self.send_message(Command.ADD_SUPER_CHAT, {
'id': id_,
'avatarUrl': avatar_url,
'timestamp': message.start_time,
'authorName': message.uname,
'price': message.price,
'content': message.message,
'id': message.id
'translation': translation
})
if need_translate:
asyncio.ensure_future(self._translate_and_response(message.message, id_))
async def _on_super_chat_delete(self, message: blivedm.SuperChatDeleteMessage):
self.send_message(Command.ADD_SUPER_CHAT, {
'ids': message.ids
'ids': list(map(str, message.ids))
})
def _need_translate(self, text):
cfg = config.get_config()
return (
cfg.enable_translate
and (not cfg.allow_translate_rooms or self.room_id in cfg.allow_translate_rooms)
and self.auto_translate_count > 0
and models.translate.need_translate(text)
)
async def _translate_and_response(self, text, msg_id):
translation = await models.translate.translate(text)
if translation is None:
return
self.send_message_if(
lambda client: client.auto_translate,
Command.UPDATE_TRANSLATION, make_translation_message(
msg_id,
translation
)
)
def make_text_message(avatar_url, timestamp, author_name, author_type, content, privilege_type,
is_gift_danmaku, author_level, is_newbie, is_mobile_verified, medal_level,
id_, translation):
return [
# 0: avatarUrl
avatar_url,
# 1: timestamp
timestamp,
# 2: authorName
author_name,
# 3: authorType
author_type,
# 4: content
content,
# 5: privilegeType
privilege_type,
# 6: isGiftDanmaku
1 if is_gift_danmaku else 0,
# 7: authorLevel
author_level,
# 8: isNewbie
1 if is_newbie else 0,
# 9: isMobileVerified
1 if is_mobile_verified else 0,
# 10: medalLevel
medal_level,
# 11: id
id_,
# 12: translation
translation
]
def make_translation_message(msg_id, translation):
return [
# 0: id
msg_id,
# 1: translation
translation
]
class RoomManager:
def __init__(self):
@@ -186,19 +309,32 @@ class RoomManager:
if not await self._add_room(room_id):
client.close()
return
room = self._rooms[room_id]
room = self._rooms.get(room_id, None)
if room is None:
return
room.clients.append(client)
logger.info('%d clients in room %s', len(room.clients), room_id)
if client.auto_translate:
room.auto_translate_count += 1
if client.application.settings['debug']:
client.send_test_message()
await client.on_join_room()
def del_client(self, room_id, client: 'ChatHandler'):
if room_id not in self._rooms:
room = self._rooms.get(room_id, None)
if room is None:
return
room = self._rooms[room_id]
room.clients.remove(client)
logger.info('%d clients in room %s', len(room.clients), room_id)
try:
room.clients.remove(client)
except ValueError:
# _add_room未完成没有执行到room.clients.append
pass
else:
logger.info('%d clients in room %s', len(room.clients), room_id)
if client.auto_translate:
room.auto_translate_count = max(0, room.auto_translate_count - 1)
if not room.clients:
self._del_room(room_id)
@@ -206,53 +342,80 @@ class RoomManager:
if room_id in self._rooms:
return True
logger.info('Creating room %d', room_id)
room = Room(room_id)
self._rooms[room_id] = room
self._rooms[room_id] = room = Room(room_id)
if await room.init_room():
room.start()
logger.info('%d rooms', len(self._rooms))
return True
else:
self._del_room(room_id)
return False
def _del_room(self, room_id):
if room_id not in self._rooms:
room = self._rooms.get(room_id, None)
if room is None:
return
logger.info('Removing room %d', room_id)
room = self._rooms[room_id]
for client in room.clients:
client.close()
room.stop_and_close()
del self._rooms[room_id]
self._rooms.pop(room_id, None)
logger.info('%d rooms', len(self._rooms))
# noinspection PyAbstractClass
class ChatHandler(tornado.websocket.WebSocketHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._close_on_timeout_future = None
self.room_id = None
self.auto_translate = False
def open(self):
logger.info('Websocket connected %s', self.request.remote_ip)
self._close_on_timeout_future = asyncio.ensure_future(self._close_on_timeout())
async def _close_on_timeout(self):
try:
# 超过一定时间还没加入房间则断开
await asyncio.sleep(10)
logger.warning('Client %s joining room timed out', self.request.remote_ip)
self.close()
except (asyncio.CancelledError, tornado.websocket.WebSocketClosedError):
pass
def on_message(self, message):
body = json.loads(message)
cmd = body['cmd']
if cmd == Command.HEARTBEAT:
pass
elif cmd == Command.JOIN_ROOM:
if self.room_id is not None:
try:
body = json.loads(message)
cmd = body['cmd']
if cmd == Command.HEARTBEAT:
return
self.room_id = int(body['data']['roomId'])
logger.info('Client %s is joining room %d', self.request.remote_ip, self.room_id)
asyncio.ensure_future(room_manager.add_client(self.room_id, self))
else:
logger.warning('Unknown cmd: %s body: %s', cmd, body)
elif cmd == Command.JOIN_ROOM:
if self.has_joined_room:
return
self.room_id = int(body['data']['roomId'])
logger.info('Client %s is joining room %d', self.request.remote_ip, self.room_id)
try:
cfg = body['data']['config']
self.auto_translate = cfg['autoTranslate']
except KeyError:
pass
asyncio.ensure_future(room_manager.add_client(self.room_id, self))
self._close_on_timeout_future.cancel()
self._close_on_timeout_future = None
else:
logger.warning('Unknown cmd, client: %s, cmd: %d, body: %s', self.request.remote_ip, cmd, body)
except:
logger.exception('on_message error, client: %s, message: %s', self.request.remote_ip, message)
def on_close(self):
logger.info('Websocket disconnected %s room: %s', self.request.remote_ip, str(self.room_id))
if self.room_id is not None:
if self.has_joined_room:
room_manager.del_client(self.room_id, self)
if self._close_on_timeout_future is not None:
self._close_on_timeout_future.cancel()
self._close_on_timeout_future = None
# 跨域测试用
def check_origin(self, origin):
@@ -260,56 +423,186 @@ class ChatHandler(tornado.websocket.WebSocketHandler):
return True
return super().check_origin(origin)
# 测试用
def send_test_message(self):
base_data = {
'avatarUrl': '//i0.hdslb.com/bfs/face/29b6be8aa611e70a3d3ac219cdaf5e72b604f2de.jpg@48w_48h',
'timestamp': time.time(),
'authorName': 'xfgryujk',
}
text_data = {
**base_data,
'authorType': 0,
'content': '我能吞下玻璃而不伤身体',
'privilegeType': 0,
'isGiftDanmaku': False,
'authorLevel': 20,
'isNewbie': False,
'isMobileVerified': True
}
member_data = base_data
gift_data = {
**base_data,
'giftName': '摩天大楼',
'giftNum': 1,
'totalCoin': 450000
}
sc_data = {
**base_data,
'price': 30,
'content': 'The quick brown fox jumps over the lazy dog',
'id': 1
}
self.send_message(Command.ADD_TEXT, text_data)
text_data['authorName'] = '主播'
text_data['authorType'] = 3
text_data['content'] = "I can eat glass, it doesn't hurt me."
self.send_message(Command.ADD_TEXT, text_data)
self.send_message(Command.ADD_MEMBER, member_data)
self.send_message(Command.ADD_SUPER_CHAT, sc_data)
sc_data['price'] = 100
sc_data['content'] = '敏捷的棕色狐狸跳过了懒狗'
sc_data['id'] = 2
self.send_message(Command.ADD_SUPER_CHAT, sc_data)
# self.send_message(Command.DEL_SUPER_CHAT, {'ids': [1, 2]})
self.send_message(Command.ADD_GIFT, gift_data)
gift_data['giftName'] = '小电视飞船'
gift_data['totalCoin'] = 1245000
self.send_message(Command.ADD_GIFT, gift_data)
@property
def has_joined_room(self):
return self.room_id is not None
def send_message(self, cmd, data):
body = json.dumps({'cmd': cmd, 'data': data})
try:
self.write_message(body)
except tornado.websocket.WebSocketClosedError:
pass
self.on_close()
async def on_join_room(self):
if self.application.settings['debug']:
await self.send_test_message()
# 不允许自动翻译的提示
if self.auto_translate:
cfg = config.get_config()
if cfg.allow_translate_rooms and self.room_id not in cfg.allow_translate_rooms:
self.send_message(Command.ADD_TEXT, make_text_message(
models.avatar.DEFAULT_AVATAR_URL,
int(time.time()),
'blivechat',
2,
'Translation is not allowed in this room. Please download to use translation',
0,
False,
60,
False,
True,
0,
uuid.uuid4().hex,
''
))
# 测试用
async def send_test_message(self):
base_data = {
'avatarUrl': await models.avatar.get_avatar_url(300474),
'timestamp': int(time.time()),
'authorName': 'xfgryujk',
}
text_data = make_text_message(
base_data['avatarUrl'],
base_data['timestamp'],
base_data['authorName'],
0,
'我能吞下玻璃而不伤身体',
0,
False,
20,
False,
True,
0,
uuid.uuid4().hex,
''
)
member_data = {
**base_data,
'id': uuid.uuid4().hex,
'privilegeType': 3
}
gift_data = {
**base_data,
'id': uuid.uuid4().hex,
'totalCoin': 450000,
'giftName': '摩天大楼',
'num': 1
}
sc_data = {
**base_data,
'id': str(random.randint(1, 65535)),
'price': 30,
'content': 'The quick brown fox jumps over the lazy dog',
'translation': ''
}
self.send_message(Command.ADD_TEXT, text_data)
text_data[2] = '主播'
text_data[3] = 3
text_data[4] = "I can eat glass, it doesn't hurt me."
text_data[11] = uuid.uuid4().hex
self.send_message(Command.ADD_TEXT, text_data)
self.send_message(Command.ADD_MEMBER, member_data)
self.send_message(Command.ADD_SUPER_CHAT, sc_data)
sc_data['id'] = str(random.randint(1, 65535))
sc_data['price'] = 100
sc_data['content'] = '敏捷的棕色狐狸跳过了懒狗'
self.send_message(Command.ADD_SUPER_CHAT, sc_data)
# self.send_message(Command.DEL_SUPER_CHAT, {'ids': [sc_data['id']]})
self.send_message(Command.ADD_GIFT, gift_data)
gift_data['id'] = uuid.uuid4().hex
gift_data['totalCoin'] = 1245000
gift_data['giftName'] = '小电视飞船'
self.send_message(Command.ADD_GIFT, gift_data)
# noinspection PyAbstractClass
class RoomInfoHandler(api.base.ApiHandler):
_host_server_list_cache = blivedm.DEFAULT_DANMAKU_SERVER_LIST
async def get(self):
room_id = int(self.get_query_argument('roomId'))
logger.info('Client %s is getting room info %d', self.request.remote_ip, room_id)
room_id, owner_uid = await self._get_room_info(room_id)
host_server_list = await self._get_server_host_list(room_id)
if owner_uid == 0:
# 缓存3分钟
self.set_header('Cache-Control', 'private, max-age=180')
else:
# 缓存1天
self.set_header('Cache-Control', 'private, max-age=86400')
self.write({
'roomId': room_id,
'ownerUid': owner_uid,
'hostServerList': host_server_list
})
@staticmethod
async def _get_room_info(room_id):
try:
async with _http_session.get(blivedm.ROOM_INIT_URL, params={'room_id': room_id}
) as res:
if res.status != 200:
logger.warning('room %d _get_room_info failed: %d %s', room_id,
res.status, res.reason)
return room_id, 0
data = await res.json()
except aiohttp.ClientConnectionError:
logger.exception('room %d _get_room_info failed', room_id)
return room_id, 0
if data['code'] != 0:
logger.warning('room %d _get_room_info failed: %s', room_id, data['msg'])
return room_id, 0
room_info = data['data']['room_info']
return room_info['room_id'], room_info['uid']
@classmethod
async def _get_server_host_list(cls, _room_id):
return cls._host_server_list_cache
# 连接其他host必须要key
# try:
# async with _http_session.get(blivedm.DANMAKU_SERVER_CONF_URL, params={'id': room_id, 'type': 0}
# ) as res:
# if res.status != 200:
# logger.warning('room %d _get_server_host_list failed: %d %s', room_id,
# res.status, res.reason)
# return cls._host_server_list_cache
# data = await res.json()
# except aiohttp.ClientConnectionError:
# logger.exception('room %d _get_server_host_list failed', room_id)
# return cls._host_server_list_cache
#
# if data['code'] != 0:
# logger.warning('room %d _get_server_host_list failed: %s', room_id, data['msg'])
# return cls._host_server_list_cache
#
# host_server_list = data['data']['host_list']
# if not host_server_list:
# logger.warning('room %d _get_server_host_list failed: host_server_list is empty')
# return cls._host_server_list_cache
#
# cls._host_server_list_cache = host_server_list
# return host_server_list
# noinspection PyAbstractClass
class AvatarHandler(api.base.ApiHandler):
async def get(self):
uid = int(self.get_query_argument('uid'))
avatar_url = await models.avatar.get_avatar_url_or_none(uid)
if avatar_url is None:
avatar_url = models.avatar.DEFAULT_AVATAR_URL
# 缓存3分钟
self.set_header('Cache-Control', 'private, max-age=180')
else:
# 缓存1天
self.set_header('Cache-Control', 'private, max-age=86400')
self.write({
'avatarUrl': avatar_url
})

View File

@@ -2,9 +2,32 @@
import tornado.web
import api.base
import config
import update
# 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)
"""为了使用Vue Router的history模式不存在的文件请求转发到index.html"""
async def get(self, path, include_body=True):
try:
await super().get(path, include_body)
except tornado.web.HTTPError as e:
if e.status_code != 404:
raise
# 不存在的文件请求转发到index.html交给前端路由
await super().get('index.html', include_body)
# noinspection PyAbstractClass
class ServerInfoHandler(api.base.ApiHandler):
async def get(self):
cfg = config.get_config()
self.write({
'version': update.VERSION,
'config': {
'enableTranslate': cfg.enable_translate,
'loaderUrl': cfg.loader_url
}
})

Submodule blivedm updated: 15669a2084...3156142011

View File

@@ -13,14 +13,20 @@ _config: Optional['AppConfig'] = None
def init():
reload()
if reload():
return
logger.warning('Using default config')
global _config
_config = AppConfig()
def reload():
config = AppConfig()
if config.load(CONFIG_PATH):
global _config
_config = config
if not config.load(CONFIG_PATH):
return False
global _config
_config = config
return True
def get_config():
@@ -30,13 +36,30 @@ def get_config():
class AppConfig:
def __init__(self):
self.database_url = 'sqlite:///data/database.db'
self.enable_translate = True
self.allow_translate_rooms = {}
self.tornado_xheaders = False
self.loader_url = ''
def load(self, path):
config = configparser.ConfigParser()
config.read(path)
try:
config = configparser.ConfigParser()
config.read(path)
app_section = config['app']
self.database_url = app_section['database_url']
self.enable_translate = app_section.getboolean('enable_translate')
allow_translate_rooms = app_section['allow_translate_rooms']
if allow_translate_rooms == '':
self.allow_translate_rooms = {}
else:
allow_translate_rooms = allow_translate_rooms.split(',')
self.allow_translate_rooms = set(map(lambda id_: int(id_.strip()), allow_translate_rooms))
self.tornado_xheaders = app_section.getboolean('tornado_xheaders')
self.loader_url = app_section['loader_url']
except (KeyError, ValueError):
logger.exception('Failed to load config:')
return False

View File

@@ -2,7 +2,24 @@
# See https://docs.sqlalchemy.org/en/13/core/engines.html#database-urls
database_url = sqlite:///data/database.db
# Enable auto translate to Japanese
enable_translate = true
# Comma separated room IDs in which translation are not allowed. If empty, all are allowed
# Example: allow_translate_rooms = 4895312,22347054,21693691
allow_translate_rooms =
# Set to true if you are using a reverse proxy server such as nginx
tornado_xheaders = false
# Use a loader so that you can run OBS before blivechat. If empty, no loader is used
loader_url = https://xfgryujk.sinacloud.net/blivechat/loader.html
# DON'T modify this section
[DEFAULT]
database_url = sqlite:///data/database.db
enable_translate = true
allow_translate_rooms =
tornado_xheaders = false
loader_url =

View File

@@ -1,5 +1,13 @@
module.exports = {
presets: [
'@vue/app'
],
plugins: [
[
'component', {
'libraryName': 'element-ui',
'styleLibraryName': 'theme-chalk'
}
]
]
}

15946
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,8 @@
"core-js": "^2.6.5",
"downloadjs": "^1.4.7",
"element-ui": "^2.9.1",
"lodash": "^4.17.13",
"lodash": "^4.17.19",
"pako": "^1.0.11",
"vue": "^2.6.10",
"vue-i18n": "^8.11.2",
"vue-router": "^3.0.6"
@@ -20,8 +21,9 @@
"devDependencies": {
"@vue/cli-plugin-babel": "^3.7.0",
"@vue/cli-plugin-eslint": "^3.7.0",
"@vue/cli-service": "^3.7.0",
"@vue/cli-service": "^4.2.2",
"babel-eslint": "^10.0.1",
"babel-plugin-component": "^1.1.1",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"vue-template-compiler": "^2.5.21"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View File

@@ -0,0 +1,343 @@
import axios from 'axios'
import {inflate} from 'pako'
import {getUuid4Hex} from '@/utils'
import * as avatar from './avatar'
const HEADER_SIZE = 16
// const WS_BODY_PROTOCOL_VERSION_NORMAL = 0
// const WS_BODY_PROTOCOL_VERSION_INT = 1 // 用于心跳包
const WS_BODY_PROTOCOL_VERSION_DEFLATE = 2
// const OP_HANDSHAKE = 0
// const OP_HANDSHAKE_REPLY = 1
const OP_HEARTBEAT = 2
const OP_HEARTBEAT_REPLY = 3
// const OP_SEND_MSG = 4
const OP_SEND_MSG_REPLY = 5
// const OP_DISCONNECT_REPLY = 6
const OP_AUTH = 7
const OP_AUTH_REPLY = 8
// const OP_RAW = 9
// const OP_PROTO_READY = 10
// const OP_PROTO_FINISH = 11
// const OP_CHANGE_ROOM = 12
// const OP_CHANGE_ROOM_REPLY = 13
// const OP_REGISTER = 14
// const OP_REGISTER_REPLY = 15
// const OP_UNREGISTER = 16
// const OP_UNREGISTER_REPLY = 17
// B站业务自定义OP
// const MinBusinessOp = 1000
// const MaxBusinessOp = 10000
let textEncoder = new TextEncoder()
let textDecoder = new TextDecoder()
export default class ChatClientDirect {
constructor (roomId) {
// 调用initRoom后初始化如果失败使用这里的默认值
this.roomId = roomId
this.roomOwnerUid = 0
this.hostServerList = [
{host: "broadcastlv.chat.bilibili.com", port: 2243, wss_port: 443, ws_port: 2244}
]
this.onAddText = null
this.onAddGift = null
this.onAddMember = null
this.onAddSuperChat = null
this.onDelSuperChat = null
this.onUpdateTranslation = null
this.websocket = null
this.retryCount = 0
this.isDestroying = false
this.heartbeatTimerId = null
}
async start () {
await this.initRoom()
this.wsConnect()
}
stop () {
this.isDestroying = true
if (this.websocket) {
this.websocket.close()
}
}
async initRoom () {
let res
try {
res = (await axios.get('/api/room_info', {params: {
roomId: this.roomId
}})).data
} catch {
return
}
this.roomId = res.roomId
this.roomOwnerUid = res.ownerUid
if (res.hostServerList.length !== 0) {
this.hostServerList = res.hostServerList
}
}
makePacket (data, operation) {
let body = textEncoder.encode(JSON.stringify(data))
let header = new ArrayBuffer(HEADER_SIZE)
let headerView = new DataView(header)
headerView.setUint32(0, HEADER_SIZE + body.byteLength) // pack_len
headerView.setUint16(4, HEADER_SIZE) // raw_header_size
headerView.setUint16(6, 1) // ver
headerView.setUint32(8, operation) // operation
headerView.setUint32(12, 1) // seq_id
return new Blob([header, body])
}
sendAuth () {
let authParams = {
uid: 0,
roomid: this.roomId,
protover: 2,
platform: 'web',
clientver: '1.14.3',
type: 2
}
this.websocket.send(this.makePacket(authParams, OP_AUTH))
}
wsConnect () {
if (this.isDestroying) {
return
}
let hostServer = this.hostServerList[this.retryCount % this.hostServerList.length]
const url = `wss://${hostServer.host}:${hostServer.wss_port}/sub`
this.websocket = new WebSocket(url)
this.websocket.binaryType = 'arraybuffer'
this.websocket.onopen = this.onWsOpen.bind(this)
this.websocket.onclose = this.onWsClose.bind(this)
this.websocket.onmessage = this.onWsMessage.bind(this)
this.heartbeatTimerId = window.setInterval(this.sendHeartbeat.bind(this), 10 * 1000)
}
sendHeartbeat () {
this.websocket.send(this.makePacket({}, OP_HEARTBEAT))
}
onWsOpen () {
this.sendAuth()
}
onWsClose () {
this.websocket = null
if (this.heartbeatTimerId) {
window.clearInterval(this.heartbeatTimerId)
this.heartbeatTimerId = null
}
if (this.isDestroying) {
return
}
window.console.log(`掉线重连中${++this.retryCount}`)
window.setTimeout(this.wsConnect.bind(this), 1000)
}
onWsMessage (event) {
this.retryCount = 0
if (!(event.data instanceof ArrayBuffer)) {
window.console.warn('未知的websocket消息', event.data)
return
}
let data = new Uint8Array(event.data)
this.handlerMessage(data)
}
handlerMessage (data) {
let offset = 0
while (offset < data.byteLength) {
let dataView = new DataView(data.buffer, offset)
let packLen = dataView.getUint32(0)
// let rawHeaderSize = dataView.getUint16(4)
let ver = dataView.getUint16(6)
let operation = dataView.getUint32(8)
// let seqId = dataView.getUint32(12)
switch (operation) {
case OP_HEARTBEAT_REPLY: {
// 人气值没用
break
}
case OP_SEND_MSG_REPLY: {
let body = new Uint8Array(data.buffer, offset + HEADER_SIZE, packLen - HEADER_SIZE)
if (ver == WS_BODY_PROTOCOL_VERSION_DEFLATE) {
body = inflate(body)
this.handlerMessage(body)
} else {
try {
body = JSON.parse(textDecoder.decode(body))
this.handlerCommand(body)
} catch (e) {
window.console.warn('body:', body)
throw e
}
}
break
}
case OP_AUTH_REPLY: {
this.sendHeartbeat()
break
}
default: {
let body = new Uint8Array(data.buffer, offset + HEADER_SIZE, packLen - HEADER_SIZE)
window.console.warn('未知包类型operation=', operation, body)
break
}
}
offset += packLen
}
}
handlerCommand (command) {
if (command instanceof Array) {
for (let oneCommand of command) {
this.handlerCommand(oneCommand)
}
return
}
let cmd = command.cmd || ''
let pos = cmd.indexOf(':')
if (pos != -1) {
cmd = cmd.substr(0, pos)
}
let handler = COMMAND_HANDLERS[cmd]
if (handler) {
handler.call(this, command)
}
}
async onReceiveDanmaku (command) {
if (!this.onAddText) {
return
}
let info = command.info
let roomId, medalLevel
if (info[3]) {
roomId = info[3][3]
medalLevel = info[3][0]
} else {
roomId = medalLevel = 0
}
let uid = info[2][0]
let isAdmin = info[2][2]
let privilegeType = info[7]
let authorType
if (uid === this.roomOwnerUid) {
authorType = 3
} else if (isAdmin) {
authorType = 2
} else if (privilegeType !== 0) {
authorType = 1
} else {
authorType = 0
}
let urank = info[2][5]
let data = {
avatarUrl: await avatar.getAvatarUrl(uid),
timestamp: info[0][4] / 1000,
authorName: info[2][1],
authorType: authorType,
content: info[1],
privilegeType: privilegeType,
isGiftDanmaku: !!info[0][9],
authorLevel: info[4][0],
isNewbie: urank < 10000,
isMobileVerified: !!info[2][6],
medalLevel: roomId === this.roomId ? medalLevel : 0,
id: getUuid4Hex(),
translation: ''
}
this.onAddText(data)
}
onReceiveGift (command) {
if (!this.onAddGift) {
return
}
let data = command.data
if (data.coin_type !== 'gold') { // 丢人
return
}
data = {
id: getUuid4Hex(),
avatarUrl: avatar.processAvatarUrl(data.face),
timestamp: data.timestamp,
authorName: data.uname,
totalCoin: data.total_coin,
giftName: data.giftName,
num: data.num
}
this.onAddGift(data)
}
async onBuyGuard (command) {
if (!this.onAddMember) {
return
}
let data = command.data
data = {
id: getUuid4Hex(),
avatarUrl: await avatar.getAvatarUrl(data.uid),
timestamp: data.start_time,
authorName: data.username,
privilegeType: data.guard_level
}
this.onAddMember(data)
}
onSuperChat (command) {
if (!this.onAddSuperChat) {
return
}
let data = command.data
data = {
id: data.id.toString(),
avatarUrl: avatar.processAvatarUrl(data.user_info.face),
timestamp: data.start_time,
authorName: data.user_info.uname,
price: data.price,
content: data.message,
translation: ''
}
this.onAddSuperChat(data)
}
onSuperChatDelete (command) {
if (!this.onDelSuperChat) {
return
}
let ids = []
for (let id of command.data.ids) {
ids.push(id.toString())
}
this.onDelSuperChat({ids})
}
}
const COMMAND_HANDLERS = {
DANMU_MSG: ChatClientDirect.prototype.onReceiveDanmaku,
SEND_GIFT: ChatClientDirect.prototype.onReceiveGift,
GUARD_BUY: ChatClientDirect.prototype.onBuyGuard,
SUPER_CHAT_MESSAGE: ChatClientDirect.prototype.onSuperChat,
SUPER_CHAT_MESSAGE_DELETE: ChatClientDirect.prototype.onSuperChatDelete
}

View File

@@ -0,0 +1,148 @@
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
const COMMAND_UPDATE_TRANSLATION = 7
export default class ChatClientRelay {
constructor (roomId, autoTranslate) {
this.roomId = roomId
this.autoTranslate = autoTranslate
this.onAddText = null
this.onAddGift = null
this.onAddMember = null
this.onAddSuperChat = null
this.onDelSuperChat = null
this.onUpdateTranslation = null
this.websocket = null
this.retryCount = 0
this.isDestroying = false
this.heartbeatTimerId = null
}
start () {
this.wsConnect()
}
stop () {
this.isDestroying = true
if (this.websocket) {
this.websocket.close()
}
}
wsConnect () {
if (this.isDestroying) {
return
}
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'
// 开发时使用localhost:12450
const host = process.env.NODE_ENV === 'development' ? 'localhost:12450' : window.location.host
const url = `${protocol}://${host}/api/chat`
this.websocket = new WebSocket(url)
this.websocket.onopen = this.onWsOpen.bind(this)
this.websocket.onclose = this.onWsClose.bind(this)
this.websocket.onmessage = this.onWsMessage.bind(this)
this.heartbeatTimerId = window.setInterval(this.sendHeartbeat.bind(this), 10 * 1000)
}
sendHeartbeat () {
this.websocket.send(JSON.stringify({
cmd: COMMAND_HEARTBEAT
}))
}
onWsOpen () {
this.retryCount = 0
this.websocket.send(JSON.stringify({
cmd: COMMAND_JOIN_ROOM,
data: {
roomId: this.roomId,
config: {
autoTranslate: this.autoTranslate
}
}
}))
}
onWsClose () {
this.websocket = null
if (this.heartbeatTimerId) {
window.clearInterval(this.heartbeatTimerId)
this.heartbeatTimerId = null
}
if (this.isDestroying) {
return
}
window.console.log(`掉线重连中${++this.retryCount}`)
window.setTimeout(this.wsConnect.bind(this), 1000)
}
onWsMessage (event) {
let {cmd, data} = JSON.parse(event.data)
switch (cmd) {
case COMMAND_ADD_TEXT: {
if (!this.onAddText) {
break
}
data = {
avatarUrl: data[0],
timestamp: data[1],
authorName: data[2],
authorType: data[3],
content: data[4],
privilegeType: data[5],
isGiftDanmaku: !!data[6],
authorLevel: data[7],
isNewbie: !!data[8],
isMobileVerified: !!data[9],
medalLevel: data[10],
id: data[11],
translation: data[12]
}
this.onAddText(data)
break
}
case COMMAND_ADD_GIFT: {
if (this.onAddGift) {
this.onAddGift(data)
}
break
}
case COMMAND_ADD_MEMBER: {
if (this.onAddMember) {
this.onAddMember(data)
}
break
}
case COMMAND_ADD_SUPER_CHAT: {
if (this.onAddSuperChat) {
this.onAddSuperChat(data)
}
break
}
case COMMAND_DEL_SUPER_CHAT: {
if (this.onDelSuperChat) {
this.onDelSuperChat(data)
}
break
}
case COMMAND_UPDATE_TRANSLATION: {
if (!this.onUpdateTranslation) {
break
}
data = {
id: data[0],
translation: data[1]
}
this.onUpdateTranslation(data)
break
}
}
}
}

View File

@@ -0,0 +1,28 @@
import axios from 'axios'
export const DEFAULT_AVATAR_URL = '//static.hdslb.com/images/member/noface.gif'
export function processAvatarUrl (avatarUrl) {
// 去掉协议兼容HTTP、HTTPS
let m = avatarUrl.match(/(?:https?:)?(.*)/)
if (m) {
avatarUrl = m[1]
}
// 缩小图片加快传输
if (!avatarUrl.endsWith('noface.gif')) {
avatarUrl += '@48w_48h'
}
return avatarUrl
}
export async function getAvatarUrl (uid) {
let res
try {
res = (await axios.get('/api/avatar_url', {params: {
uid: uid
}})).data
} catch {
return DEFAULT_AVATAR_URL
}
return res.avatarUrl
}

View File

@@ -2,9 +2,11 @@ import {mergeConfig} from '@/utils'
export const DEFAULT_CONFIG = {
minGiftPrice: 7, // $1
mergeSimilarDanmaku: true,
showDanmaku: true,
showGift: true,
showGiftName: false,
mergeSimilarDanmaku: false,
mergeGift: true,
maxNumber: 60,
blockGiftDanmaku: true,
@@ -13,7 +15,10 @@ export const DEFAULT_CONFIG = {
blockNotMobileVerified: true,
blockKeywords: '',
blockUsers: '',
blockMedalLevel: 0
blockMedalLevel: 0,
relayMessagesByServer: false,
autoTranslate: false
}
export function setLocalConfig (config) {

View File

@@ -0,0 +1,362 @@
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;
}
html:not(.style-scope) {
--layout_-_display: flex;
;
--layout-inline_-_display: inline-flex;
;
--layout-horizontal_-_display: var(--layout_-_display);
--layout-horizontal_-_-ms-flex-direction: row;
--layout-horizontal_-_-webkit-flex-direction: row;
--layout-horizontal_-_flex-direction: row;
;
--layout-horizontal-reverse_-_display: var(--layout_-_display);
--layout-horizontal-reverse_-_-ms-flex-direction: row-reverse;
--layout-horizontal-reverse_-_-webkit-flex-direction: row-reverse;
--layout-horizontal-reverse_-_flex-direction: row-reverse;
;
--layout-vertical_-_display: var(--layout_-_display);
--layout-vertical_-_-ms-flex-direction: column;
--layout-vertical_-_-webkit-flex-direction: column;
--layout-vertical_-_flex-direction: column;
;
--layout-vertical-reverse_-_display: var(--layout_-_display);
--layout-vertical-reverse_-_-ms-flex-direction: column-reverse;
--layout-vertical-reverse_-_-webkit-flex-direction: column-reverse;
--layout-vertical-reverse_-_flex-direction: column-reverse;
;
--layout-wrap_-_-ms-flex-wrap: wrap;
--layout-wrap_-_-webkit-flex-wrap: wrap;
--layout-wrap_-_flex-wrap: wrap;
;
--layout-wrap-reverse_-_-ms-flex-wrap: wrap-reverse;
--layout-wrap-reverse_-_-webkit-flex-wrap: wrap-reverse;
--layout-wrap-reverse_-_flex-wrap: wrap-reverse;
;
--layout-flex-auto_-_-ms-flex: 1 1 auto;
--layout-flex-auto_-_-webkit-flex: 1 1 auto;
--layout-flex-auto_-_flex: 1 1 auto;
;
--layout-flex-none_-_-ms-flex: none;
--layout-flex-none_-_-webkit-flex: none;
--layout-flex-none_-_flex: none;
;
--layout-flex_-_-ms-flex: 1 1 0.000000001px;
--layout-flex_-_-webkit-flex: 1;
--layout-flex_-_flex: 1;
--layout-flex_-_-webkit-flex-basis: 0.000000001px;
--layout-flex_-_flex-basis: 0.000000001px;
;
--layout-flex-2_-_-ms-flex: 2;
--layout-flex-2_-_-webkit-flex: 2;
--layout-flex-2_-_flex: 2;
;
--layout-flex-3_-_-ms-flex: 3;
--layout-flex-3_-_-webkit-flex: 3;
--layout-flex-3_-_flex: 3;
;
--layout-flex-4_-_-ms-flex: 4;
--layout-flex-4_-_-webkit-flex: 4;
--layout-flex-4_-_flex: 4;
;
--layout-flex-5_-_-ms-flex: 5;
--layout-flex-5_-_-webkit-flex: 5;
--layout-flex-5_-_flex: 5;
;
--layout-flex-6_-_-ms-flex: 6;
--layout-flex-6_-_-webkit-flex: 6;
--layout-flex-6_-_flex: 6;
;
--layout-flex-7_-_-ms-flex: 7;
--layout-flex-7_-_-webkit-flex: 7;
--layout-flex-7_-_flex: 7;
;
--layout-flex-8_-_-ms-flex: 8;
--layout-flex-8_-_-webkit-flex: 8;
--layout-flex-8_-_flex: 8;
;
--layout-flex-9_-_-ms-flex: 9;
--layout-flex-9_-_-webkit-flex: 9;
--layout-flex-9_-_flex: 9;
;
--layout-flex-10_-_-ms-flex: 10;
--layout-flex-10_-_-webkit-flex: 10;
--layout-flex-10_-_flex: 10;
;
--layout-flex-11_-_-ms-flex: 11;
--layout-flex-11_-_-webkit-flex: 11;
--layout-flex-11_-_flex: 11;
;
--layout-flex-12_-_-ms-flex: 12;
--layout-flex-12_-_-webkit-flex: 12;
--layout-flex-12_-_flex: 12;
;
--layout-start_-_-ms-flex-align: start;
--layout-start_-_-webkit-align-items: flex-start;
--layout-start_-_align-items: flex-start;
;
--layout-center_-_-ms-flex-align: center;
--layout-center_-_-webkit-align-items: center;
--layout-center_-_align-items: center;
;
--layout-end_-_-ms-flex-align: end;
--layout-end_-_-webkit-align-items: flex-end;
--layout-end_-_align-items: flex-end;
;
--layout-baseline_-_-ms-flex-align: baseline;
--layout-baseline_-_-webkit-align-items: baseline;
--layout-baseline_-_align-items: baseline;
;
--layout-start-justified_-_-ms-flex-pack: start;
--layout-start-justified_-_-webkit-justify-content: flex-start;
--layout-start-justified_-_justify-content: flex-start;
;
--layout-center-justified_-_-ms-flex-pack: center;
--layout-center-justified_-_-webkit-justify-content: center;
--layout-center-justified_-_justify-content: center;
;
--layout-end-justified_-_-ms-flex-pack: end;
--layout-end-justified_-_-webkit-justify-content: flex-end;
--layout-end-justified_-_justify-content: flex-end;
;
--layout-around-justified_-_-ms-flex-pack: distribute;
--layout-around-justified_-_-webkit-justify-content: space-around;
--layout-around-justified_-_justify-content: space-around;
;
--layout-justified_-_-ms-flex-pack: justify;
--layout-justified_-_-webkit-justify-content: space-between;
--layout-justified_-_justify-content: space-between;
;
--layout-center-center_-_-ms-flex-align: var(--layout-center_-_-ms-flex-align);
--layout-center-center_-_-webkit-align-items: var(--layout-center_-_-webkit-align-items);
--layout-center-center_-_align-items: var(--layout-center_-_align-items);
--layout-center-center_-_-ms-flex-pack: var(--layout-center-justified_-_-ms-flex-pack);
--layout-center-center_-_-webkit-justify-content: var(--layout-center-justified_-_-webkit-justify-content);
--layout-center-center_-_justify-content: var(--layout-center-justified_-_justify-content);
;
--layout-self-start_-_-ms-align-self: flex-start;
--layout-self-start_-_-webkit-align-self: flex-start;
--layout-self-start_-_align-self: flex-start;
;
--layout-self-center_-_-ms-align-self: center;
--layout-self-center_-_-webkit-align-self: center;
--layout-self-center_-_align-self: center;
;
--layout-self-end_-_-ms-align-self: flex-end;
--layout-self-end_-_-webkit-align-self: flex-end;
--layout-self-end_-_align-self: flex-end;
;
--layout-self-stretch_-_-ms-align-self: stretch;
--layout-self-stretch_-_-webkit-align-self: stretch;
--layout-self-stretch_-_align-self: stretch;
;
--layout-self-baseline_-_-ms-align-self: baseline;
--layout-self-baseline_-_-webkit-align-self: baseline;
--layout-self-baseline_-_align-self: baseline;
;
--layout-start-aligned_-_-ms-flex-line-pack: start;
--layout-start-aligned_-_-ms-align-content: flex-start;
--layout-start-aligned_-_-webkit-align-content: flex-start;
--layout-start-aligned_-_align-content: flex-start;
;
--layout-end-aligned_-_-ms-flex-line-pack: end;
--layout-end-aligned_-_-ms-align-content: flex-end;
--layout-end-aligned_-_-webkit-align-content: flex-end;
--layout-end-aligned_-_align-content: flex-end;
;
--layout-center-aligned_-_-ms-flex-line-pack: center;
--layout-center-aligned_-_-ms-align-content: center;
--layout-center-aligned_-_-webkit-align-content: center;
--layout-center-aligned_-_align-content: center;
;
--layout-between-aligned_-_-ms-flex-line-pack: justify;
--layout-between-aligned_-_-ms-align-content: space-between;
--layout-between-aligned_-_-webkit-align-content: space-between;
--layout-between-aligned_-_align-content: space-between;
;
--layout-around-aligned_-_-ms-flex-line-pack: distribute;
--layout-around-aligned_-_-ms-align-content: space-around;
--layout-around-aligned_-_-webkit-align-content: space-around;
--layout-around-aligned_-_align-content: space-around;
;
--layout-block_-_display: block;
;
--layout-invisible_-_visibility: hidden !important;
;
--layout-relative_-_position: relative;
;
--layout-fit_-_position: absolute;
--layout-fit_-_top: 0;
--layout-fit_-_right: 0;
--layout-fit_-_bottom: 0;
--layout-fit_-_left: 0;
;
--layout-scroll_-_-webkit-overflow-scrolling: touch;
--layout-scroll_-_overflow: auto;
;
--layout-fullbleed_-_margin: 0;
--layout-fullbleed_-_height: 100vh;
;
--layout-fixed-top_-_position: fixed;
--layout-fixed-top_-_top: 0;
--layout-fixed-top_-_left: 0;
--layout-fixed-top_-_right: 0;
;
--layout-fixed-right_-_position: fixed;
--layout-fixed-right_-_top: 0;
--layout-fixed-right_-_right: 0;
--layout-fixed-right_-_bottom: 0;
;
--layout-fixed-bottom_-_position: fixed;
--layout-fixed-bottom_-_right: 0;
--layout-fixed-bottom_-_bottom: 0;
--layout-fixed-bottom_-_left: 0;
;
--layout-fixed-left_-_position: fixed;
--layout-fixed-left_-_top: 0;
--layout-fixed-left_-_bottom: 0;
--layout-fixed-left_-_left: 0;
;
}

View File

@@ -0,0 +1,30 @@
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;
}

View File

@@ -0,0 +1,68 @@
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;
}

View File

@@ -0,0 +1,36 @@
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;
}

View File

@@ -0,0 +1,64 @@
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;
}

View File

@@ -0,0 +1,140 @@
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;
}

View File

@@ -0,0 +1,360 @@
#timestamp.yt-live-chat-membership-item-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-membership-item-renderer {
display: block;
margin-right: 16px;
overflow: hidden;
border-radius: 50%;
-ms-flex: var(--layout-flex-none_-_-ms-flex);
-webkit-flex: var(--layout-flex-none_-_-webkit-flex);
flex: var(--layout-flex-none_-_flex);
}
#menu-button.yt-live-chat-membership-item-renderer {
width: var(--yt-live-chat-32px-icon-button_-_width);
height: var(--yt-live-chat-32px-icon-button_-_height);
padding: var(--yt-live-chat-32px-icon-button_-_padding);
}
#menu.yt-live-chat-membership-item-renderer {
position: absolute;
top: 0;
bottom: 0;
right: 0;
transform: translateX(100px);
}
yt-live-chat-membership-item-renderer:hover #menu.yt-live-chat-membership-item-renderer,
yt-live-chat-membership-item-renderer[menu-visible] #menu.yt-live-chat-membership-item-renderer {
transform: none;
}
yt-live-chat-membership-item-renderer:focus-within #menu.yt-live-chat-membership-item-renderer {
transform: none;
}
#inline-action-button-container.yt-live-chat-membership-item-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: var(--layout-center-center_-_-ms-flex-align);
-webkit-align-items: var(--layout-center-center_-_-webkit-align-items);
align-items: var(--layout-center-center_-_align-items);
-ms-flex-pack: var(--layout-center-center_-_-ms-flex-pack);
-webkit-justify-content: var(--layout-center-center_-_-webkit-justify-content);
justify-content: var(--layout-center-center_-_justify-content);
}
yt-live-chat-membership-item-renderer[has-inline-action-buttons]:hover #inline-action-button-container.yt-live-chat-membership-item-renderer {
display: var(--layout-horizontal_-_display);
-ms-flex-direction: var(--layout-horizontal_-_-ms-flex-direction);
-webkit-flex-direction: var(--layout-horizontal_-_-webkit-flex-direction);
flex-direction: var(--layout-horizontal_-_flex-direction);
display: var(--yt-live-chat-inline-action-button-container-display, none);
}
yt-live-chat-membership-item-renderer[has-inline-action-buttons][hide-inline-action-buttons]:hover #inline-action-button-container.yt-live-chat-membership-item-renderer {
display: none;
}
yt-live-chat-membership-item-renderer[has-inline-action-buttons]:hover #menu.yt-live-chat-membership-item-renderer {
display: var(--yt-live-chat-item-with-inline-actions-context-menu-display, block);
}
#inline-action-buttons.yt-live-chat-membership-item-renderer>*.yt-live-chat-membership-item-renderer,
#additional-inline-action-buttons.yt-live-chat-membership-item-renderer>*.yt-live-chat-membership-item-renderer {
--yt-button-icon-size: 36px;
--yt-button-icon-padding: 6px;
color: var(--yt-white);
border-radius: 2px;
}
#inline-action-buttons.yt-live-chat-membership-item-renderer>*.yt-live-chat-membership-item-renderer {
background: var(--yt-luna-black-opacity-lighten-1);
}
#inline-action-buttons.yt-live-chat-membership-item-renderer>.yt-live-chat-membership-item-renderer:hover {
background: var(--yt-luna-black);
}
#additional-inline-action-buttons.yt-live-chat-membership-item-renderer>*.yt-live-chat-membership-item-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-membership-item-renderer>.yt-live-chat-membership-item-renderer:hover {
background: var(--yt-live-chat-additional-inline-action-button-background-color-hover);
}
#additional-inline-action-buttons.yt-live-chat-membership-item-renderer:not(:empty) {
margin-left: 32px;
}
#inline-action-buttons.yt-live-chat-membership-item-renderer>*.yt-live-chat-membership-item-renderer:not(:first-child),
#additional-inline-action-buttons.yt-live-chat-membership-item-renderer>*.yt-live-chat-membership-item-renderer:not(:first-child) {
margin-left: 8px;
}
yt-live-chat-membership-item-renderer {
position: relative;
display: block;
--yt-live-chat-sponsor-header-color: #0a8043;
--yt-live-chat-sponsor-color: #0f9d58;
--yt-live-chat-sponsor-text-color: #fff;
--yt-live-chat-item-timestamp-display: var(--yt-live-chat-paid-message-timestamp-display, none);
padding: 4px 24px;
}
yt-live-chat-membership-item-renderer[dashboard-money-feed] {
padding: 0;
--yt-live-chat-item-timestamp-display: block;
}
#card.yt-live-chat-membership-item-renderer {
overflow: hidden;
font-size: 14px;
border-radius: 4px;
display: var(--layout-vertical_-_display);
-ms-flex-direction: var(--layout-vertical_-_-ms-flex-direction);
-webkit-flex-direction: var(--layout-vertical_-_-webkit-flex-direction);
flex-direction: var(--layout-vertical_-_flex-direction);
box-shadow: var(--shadow-elevation-2dp_-_box-shadow);
}
yt-live-chat-membership-item-renderer[dashboard-money-feed] #card.yt-live-chat-membership-item-renderer {
border-radius: 0;
box-shadow: none;
}
#header.yt-live-chat-membership-item-renderer {
position: relative;
background-color: var(--yt-live-chat-sponsor-header-color);
padding: 8px 16px;
color: #fff;
min-height: 20px;
display: var(--layout-horizontal_-_display);
-ms-flex-direction: var(--layout-horizontal_-_-ms-flex-direction);
-webkit-flex-direction: var(--layout-horizontal_-_-webkit-flex-direction);
flex-direction: var(--layout-horizontal_-_flex-direction);
-ms-flex-align: var(--layout-center_-_-ms-flex-align);
-webkit-align-items: var(--layout-center_-_-webkit-align-items);
align-items: var(--layout-center_-_align-items);
}
yt-live-chat-membership-item-renderer[show-only-header] #header.yt-live-chat-membership-item-renderer {
background-color: var(--yt-live-chat-sponsor-color);
}
yt-live-chat-membership-item-renderer[dashboard-money-feed] #header.yt-live-chat-membership-item-renderer {
color: var(--yt-live-chat-secondary-text-color);
background-color: var(--yt-live-chat-background-color);
-ms-flex-align: var(--layout-start_-_-ms-flex-align);
-webkit-align-items: var(--layout-start_-_-webkit-align-items);
align-items: var(--layout-start_-_align-items);
}
#header-content.yt-live-chat-membership-item-renderer {
display: var(--layout-horizontal_-_display);
-ms-flex-direction: var(--layout-horizontal_-_-ms-flex-direction);
-webkit-flex-direction: var(--layout-horizontal_-_-webkit-flex-direction);
flex-direction: var(--layout-horizontal_-_flex-direction);
-ms-flex-pack: var(--layout-justified_-_-ms-flex-pack);
-webkit-justify-content: var(--layout-justified_-_-webkit-justify-content);
justify-content: var(--layout-justified_-_justify-content);
-ms-flex: var(--layout-flex_-_-ms-flex);
-webkit-flex: var(--layout-flex_-_-webkit-flex);
flex: var(--layout-flex_-_flex);
-webkit-flex-basis: var(--layout-flex_-_-webkit-flex-basis);
flex-basis: var(--layout-flex_-_flex-basis);
-ms-flex-align: var(--layout-baseline_-_-ms-flex-align);
-webkit-align-items: var(--layout-baseline_-_-webkit-align-items);
align-items: var(--layout-baseline_-_align-items);
}
yt-live-chat-membership-item-renderer[dashboard-money-feed] #header-content.yt-live-chat-membership-item-renderer {
display: var(--layout-vertical_-_display);
-ms-flex-direction: var(--layout-vertical_-_-ms-flex-direction);
-webkit-flex-direction: var(--layout-vertical_-_-webkit-flex-direction);
flex-direction: var(--layout-vertical_-_flex-direction);
}
yt-live-chat-membership-item-renderer[dashboard-money-feed] #header-content-inner-column.yt-live-chat-membership-item-renderer {
margin-bottom: 4px;
display: var(--layout-horizontal_-_display);
-ms-flex-direction: var(--layout-horizontal_-_-ms-flex-direction);
-webkit-flex-direction: var(--layout-horizontal_-_-webkit-flex-direction);
flex-direction: var(--layout-horizontal_-_flex-direction);
-ms-flex-align: var(--layout-center_-_-ms-flex-align);
-webkit-align-items: var(--layout-center_-_-webkit-align-items);
align-items: var(--layout-center_-_align-items);
-ms-flex: var(--layout-flex-none_-_-ms-flex);
-webkit-flex: var(--layout-flex-none_-_-webkit-flex);
flex: var(--layout-flex-none_-_flex);
}
#author-photo.yt-live-chat-membership-item-renderer {
width: 40px;
height: 40px;
}
yt-icon#author-photo.yt-live-chat-membership-item-renderer {
display: none;
}
yt-live-chat-membership-item-renderer[dashboard-money-feed] yt-icon#author-photo.yt-live-chat-membership-item-renderer {
display: block;
}
yt-live-chat-membership-item-renderer:not([dashboard-money-feed]) yt-live-chat-author-chip.yt-live-chat-membership-item-renderer {
--yt-live-chat-sponsor-color: var(--yt-live-chat-sponsor-text-color);
--yt-live-chat-secondary-text-color: var(--yt-live-chat-sponsor-text-color);
}
yt-live-chat-membership-item-renderer[dashboard-money-feed] yt-live-chat-author-chip.yt-live-chat-membership-item-renderer {
margin-right: 8px;
font-weight: 500;
--yt-live-chat-sponsor-color: var(--yt-live-chat-secondary-text-color);
}
#header-subtext.yt-live-chat-membership-item-renderer {
margin-top: 2px;
color: rgba(255, 255, 255, 0.7);
font-weight: 500;
font-size: 15px;
}
#header-subtext.yt-live-chat-membership-item-renderer:empty {
display: none;
}
yt-live-chat-membership-item-renderer[dashboard-money-feed] #header-subtext.yt-live-chat-membership-item-renderer {
margin: 4px 0 13px;
font-size: 11px;
font-weight: normal;
color: var(--yt-live-chat-secondary-text-color);
}
#header-primary-text.yt-live-chat-membership-item-renderer {
word-wrap: break-word;
word-break: break-word;
font-weight: 500;
color: rgba(255, 255, 255, 1);
}
#header-primary-text.yt-live-chat-membership-item-renderer:empty {
display: none;
}
yt-live-chat-membership-item-renderer[has-primary-header-text]:not([dashboard-money-feed]) yt-live-chat-author-chip.yt-live-chat-membership-item-renderer,
yt-live-chat-membership-item-renderer[has-primary-header-text]:not([dashboard-money-feed]) #header-subtext.yt-live-chat-membership-item-renderer {
font-size: 12px;
}
yt-live-chat-membership-item-renderer[dashboard-money-feed] #header-primary-text.yt-live-chat-membership-item-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: var(--yt-white);
display: var(--layout-inline_-_display, inline);
-ms-flex-align: var(--layout-center-center_-_-ms-flex-align);
-webkit-align-items: var(--layout-center-center_-_-webkit-align-items);
align-items: var(--layout-center-center_-_align-items);
-ms-flex-pack: var(--layout-center-center_-_-ms-flex-pack);
-webkit-justify-content: var(--layout-center-center_-_-webkit-justify-content);
justify-content: var(--layout-center-center_-_justify-content);
font-size: var(--ytd-badge_-_font-size);
font-weight: var(--ytd-badge_-_font-weight);
line-height: var(--ytd-badge_-_line-height);
}
#content.yt-live-chat-membership-item-renderer {
background-color: var(--yt-live-chat-sponsor-color);
color: var(--yt-live-chat-sponsor-text-color);
padding: 8px 16px;
word-wrap: break-word;
word-break: break-word;
font-size: 15px;
line-height: 20px;
}
yt-live-chat-membership-item-renderer[dashboard-money-feed] #content.yt-live-chat-membership-item-renderer {
background-color: unset;
font-size: unset;
color: var(--yt-live-chat-secondary-text-color);
padding: 0 0 16px 72px;
}
#content.yt-live-chat-membership-item-renderer img.yt-live-chat-membership-item-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-membership-item-renderer[show-only-header] #content.yt-live-chat-membership-item-renderer,
#deleted-state.yt-live-chat-membership-item-renderer:empty {
display: none;
}
#deleted-state.yt-live-chat-membership-item-renderer {
display: block;
font-style: italic;
opacity: 0.7;
}
a.yt-live-chat-membership-item-renderer {
display: inline;
text-decoration: underline;
}
#message.yt-live-chat-membership-item-renderer a.yt-live-chat-membership-item-renderer {
word-break: break-all;
}
#message.yt-live-chat-membership-item-renderer a.yt-live-chat-membership-item-renderer .mention.yt-live-chat-membership-item-renderer {
text-decoration: underline;
}
#menu.yt-live-chat-membership-item-renderer {
background: linear-gradient(to right, transparent, var(--yt-live-chat-sponsor-header-color) 100%);
border-radius: 0 4px 4px 0;
}
yt-live-chat-membership-item-renderer[show-only-header] #menu.yt-live-chat-membership-item-renderer {
background: linear-gradient(to right, transparent, var(--yt-live-chat-sponsor-color) 100%);
}
yt-live-chat-membership-item-renderer[dashboard-money-feed] #menu.yt-live-chat-membership-item-renderer {
margin-top: 8px;
background: linear-gradient(to right, transparent, var(--yt-live-chat-background-color) 40%);
}

View File

@@ -0,0 +1,378 @@
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;
}

View File

@@ -0,0 +1,252 @@
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;
}
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);
}
}

View File

@@ -0,0 +1,230 @@
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);
}

View File

@@ -0,0 +1,80 @@
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;
}

View File

@@ -0,0 +1,65 @@
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;
}

View File

@@ -46,76 +46,5 @@ export default {
}
</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>
<style src="@/assets/css/youtube/yt-live-chat-author-badge-renderer.css"></style>
<style src="@/assets/css/youtube/yt-icon.css"></style>

View File

@@ -1,88 +1,37 @@
<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">
<img id="img" class="style-scope yt-img-shadow" alt="" :height="height" :width="width" :src="showImgUrl" @error="onLoadError">
</yt-img-shadow>
</template>
<script>
import {DEFAULT_AVATAR_URL} from '@/api/chat/avatar'
export default {
name: 'ImgShadow',
props: {
imgUrl: String,
height: String,
width: String
},
data() {
return {
showImgUrl: this.imgUrl
}
},
watch: {
imgUrl(val) {
this.showImgUrl = val
}
},
methods: {
onLoadError() {
if (this.showImgUrl !== DEFAULT_AVATAR_URL) {
this.showImgUrl = DEFAULT_AVATAR_URL
}
}
}
}
</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>
<style src="@/assets/css/youtube/yt-img-shadow.css"></style>

View File

@@ -1,306 +0,0 @@
<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 * as 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>

View File

@@ -0,0 +1,59 @@
<template>
<yt-live-chat-membership-item-renderer class="style-scope yt-live-chat-item-list-renderer" show-only-header>
<div id="card" class="style-scope yt-live-chat-membership-item-renderer">
<div id="header" class="style-scope yt-live-chat-membership-item-renderer">
<img-shadow id="author-photo" height="40" width="40" class="style-scope yt-live-chat-membership-item-renderer"
:imgUrl="avatarUrl"
></img-shadow>
<div id="header-content" class="style-scope yt-live-chat-membership-item-renderer">
<div id="header-content-primary-column" class="style-scope yt-live-chat-membership-item-renderer">
<div id="header-content-inner-column" class="style-scope yt-live-chat-membership-item-renderer">
<yt-live-chat-author-chip class="style-scope yt-live-chat-membership-item-renderer">
<span id="author-name" dir="auto" class="member style-scope yt-live-chat-author-chip">{{
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="false" :privilegeType="privilegeType"
></author-badge>
</span>
</yt-live-chat-author-chip>
</div>
<div id="header-subtext" class="style-scope yt-live-chat-membership-item-renderer">{{title}}</div>
</div>
<div id="timestamp" class="style-scope yt-live-chat-membership-item-renderer">{{timeText}}</div>
</div>
</div>
</div>
</yt-live-chat-membership-item-renderer>
</template>
<script>
import ImgShadow from './ImgShadow.vue'
import AuthorBadge from './AuthorBadge.vue'
import * as utils from '@/utils'
export default {
name: 'MembershipItem',
components: {
ImgShadow,
AuthorBadge
},
props: {
avatarUrl: String,
authorName: String,
privilegeType: Number,
title: String,
time: Date
},
computed: {
timeText() {
return utils.getTimeTextHourMin(this.time)
}
}
}
</script>
<style src="@/assets/css/youtube/yt-live-chat-membership-item-renderer.css"></style>

View File

@@ -23,9 +23,9 @@
</div>
</div>
<div id="content" class="style-scope yt-live-chat-paid-message-renderer">
<div id="message" dir="auto" class="style-scope yt-live-chat-paid-message-renderer">
{{content}}
</div>
<div id="message" dir="auto" class="style-scope yt-live-chat-paid-message-renderer">{{
content
}}</div>
</div>
</div>
</yt-live-chat-paid-message-renderer>
@@ -56,390 +56,10 @@ export default {
return 'CN¥' + utils.formatCurrency(this.price)
},
timeText() {
return utils.getTimeTextMinSec(this.time)
return utils.getTimeTextHourMin(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>
<style src="@/assets/css/youtube/yt-live-chat-paid-message-renderer.css"></style>

View File

@@ -6,9 +6,9 @@
<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="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">
@@ -17,9 +17,9 @@
></author-badge>
</span>
</yt-live-chat-author-chip>
<span id="message" class="style-scope yt-live-chat-text-message-renderer">
{{content}}
<el-badge :value="repeated" :max="99" v-show="repeated > 1" class="style-scope yt-live-chat-text-message-renderer"
<span id="message" class="style-scope yt-live-chat-text-message-renderer">{{
content
}}<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>
</span>
@@ -54,7 +54,7 @@ export default {
},
computed: {
timeText() {
return utils.getTimeTextMinSec(this.time)
return utils.getTimeTextHourMin(this.time)
},
authorTypeText() {
return constants.AUTHOR_TYPE_TO_TEXT[this.authorType]
@@ -80,7 +80,7 @@ export default {
<style>
yt-live-chat-text-message-renderer>#content>#message>.el-badge {
margin-left: 5px;
margin-left: 10px;
}
yt-live-chat-text-message-renderer>#content>#message>.el-badge .el-badge__content {
@@ -88,309 +88,11 @@ yt-live-chat-text-message-renderer>#content>#message>.el-badge .el-badge__conten
line-height: 18px !important;
text-shadow: none !important;
font-family: sans-serif !important;
color: #FFF !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>
<style src="@/assets/css/youtube/yt-live-chat-text-message-renderer.css"></style>
<style src="@/assets/css/youtube/yt-live-chat-author-chip.css"></style>

View File

@@ -1,97 +1,119 @@
<template>
<yt-live-chat-ticker-renderer>
<yt-live-chat-ticker-renderer :hidden="showMessages.length === 0">
<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>
<yt-live-chat-ticker-paid-message-item-renderer v-for="message in showMessages" :key="message.raw.id"
tabindex="0" class="style-scope yt-live-chat-ticker-renderer" style="overflow: hidden;"
@click="onItemClick(message.raw)"
>
<div id="container" dir="ltr" class="style-scope yt-live-chat-ticker-paid-message-item-renderer" :style="{
background: message.bgColor,
}">
<div id="content" class="style-scope yt-live-chat-ticker-paid-message-item-renderer" :style="{
color: message.color
}">
<img-shadow id="author-photo" height="24" width="24" class="style-scope yt-live-chat-ticker-paid-message-item-renderer"
:imgUrl="message.raw.avatarUrl"
></img-shadow>
<span id="text" dir="ltr" class="style-scope yt-live-chat-ticker-paid-message-item-renderer">{{message.text}}</span>
</div>
</yt-live-chat-ticker-paid-message-item-renderer>
</template>
</div>
</yt-live-chat-ticker-paid-message-item-renderer>
</div>
</div>
<template v-if="pinnedMessage">
<legacy-paid-message :key="pinnedMessage.id" v-if="pinnedMessage.type === MESSAGE_TYPE_MEMBER"
<membership-item :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>
:avatarUrl="pinnedMessage.avatarUrl" :authorName="pinnedMessage.authorName" :privilegeType="pinnedMessage.privilegeType"
:title="pinnedMessage.title" :time="pinnedMessage.time"
></membership-item>
<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"
:time="pinnedMessage.time" :content="pinnedMessageShowContent"
></paid-message>
</template>
</yt-live-chat-ticker-renderer>
</template>
<script>
import * as chatConfig from '@/api/chatConfig'
import {formatCurrency} from '@/utils'
import ImgShadow from './ImgShadow.vue'
import LegacyPaidMessage from './LegacyPaidMessage.vue'
import MembershipItem from './MembershipItem.vue'
import PaidMessage from './PaidMessage.vue'
import * as constants from './constants'
import {formatCurrency} from '@/utils'
export default {
name: 'Ticker',
components: {
ImgShadow,
LegacyPaidMessage,
MembershipItem,
PaidMessage
},
props: {
messages: Array
messages: Array,
showGiftName: {
type: Boolean,
default: chatConfig.DEFAULT_CONFIG.showGiftName
}
},
data() {
return {
MESSAGE_TYPE_MEMBER: constants.MESSAGE_TYPE_MEMBER,
updateTimerId: window.setInterval(this.updateProgress.bind(this), 1000),
curTime: new Date(),
updateTimerId: window.setInterval(this.updateProgress, 1000),
pinnedMessage: null
}
},
computed: {
showMessages() {
let res = []
for (let message of this.messages) {
if (!this.needToShow(message)) {
continue
}
res.push({
raw: message,
bgColor: this.getBgColor(message),
color: this.getColor(message),
text: this.getText(message)
})
}
return res
},
pinnedMessageShowContent() {
if (!this.pinnedMessage) {
return ''
}
if (this.pinnedMessage.type === constants.MESSAGE_TYPE_GIFT) {
return constants.getGiftShowContent(this.pinnedMessage, this.showGiftName)
} else {
return constants.getShowContent(this.pinnedMessage)
}
}
},
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
}
let pinTime = this.getPinTime(message)
return (new Date() - message.addTime) / (60 * 1000) < pinTime
},
getBgColor(message) {
let color1, color2, pinTime
let color1, color2
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)
let config = constants.getPriceConfig(message.price)
color1 = config.colors.contentBg
color2 = config.colors.headerBg
pinTime = config.pinTime
}
let progress = (1 - (new Date() - message.addTime) / (60 * 1000) / pinTime) * 100
let pinTime = this.getPinTime(message)
let progress = (1 - (this.curTime - message.addTime) / (60 * 1000) / pinTime) * 100
if (progress < 0) {
progress = 0
} else if (progress > 100) {
@@ -111,18 +133,19 @@ export default {
}
return 'CN¥' + formatCurrency(message.price)
},
getPinTime(message) {
if (message.type === constants.MESSAGE_TYPE_MEMBER) {
return 2
}
return constants.getPriceConfig(message.price).pinTime
},
updateProgress() {
let curTime = new Date()
this.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]) {
let message = this.messages[i]
let pinTime = this.getPinTime(message)
if ((this.curTime - message.addTime) / (60 * 1000) >= pinTime) {
if (this.pinnedMessage == message) {
this.pinnedMessage = null
}
this.messages.splice(i, 1)
@@ -130,8 +153,6 @@ export default {
i++
}
}
this.$forceUpdate()
},
onItemClick(message) {
if (this.pinnedMessage == message) {
@@ -144,155 +165,5 @@ export default {
}
</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>
<style src="@/assets/css/youtube/yt-live-chat-ticker-renderer.css"></style>
<style src="@/assets/css/youtube/yt-live-chat-ticker-paid-message-item-renderer.css"></style>

View File

@@ -18,9 +18,11 @@ 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
export const MESSAGE_TYPE_DEL = 3
export const MESSAGE_TYPE_GIFT = 1
export const MESSAGE_TYPE_MEMBER = 2
export const MESSAGE_TYPE_SUPER_CHAT = 3
export const MESSAGE_TYPE_DEL = 4
export const MESSAGE_TYPE_UPDATE = 5
// 美元 -> 人民币 汇率
const EXCHANGE_RATE = 7
@@ -119,3 +121,17 @@ export function getPriceConfig (price) {
}
return PRICE_CONFIGS[PRICE_CONFIGS.length - 1]
}
export function getShowContent(message) {
if (message.translation) {
return `${message.content}${message.translation}`
}
return message.content
}
export function getGiftShowContent(message, showGiftName) {
if (!showGiftName) {
return ''
}
return `Sent ${message.giftName}x${message.num}`
}

View File

@@ -1,8 +1,10 @@
<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-renderer class="style-scope yt-live-chat-app" style="--scrollbar-width:11px;" hide-timestamps
@mousemove="refreshCantScrollStartTime"
>
<ticker class="style-scope yt-live-chat-renderer" :messages="paidMessages" :showGiftName="showGiftName"></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 ref="scroller" id="item-scroller" class="style-scope yt-live-chat-item-list-renderer animated" @scroll="onScroll">
<div ref="itemOffset" id="item-offset" class="style-scope yt-live-chat-item-list-renderer" style="height: 0px;">
<div ref="items" id="items" class="style-scope yt-live-chat-item-list-renderer" style="overflow: hidden"
:style="{transform: `translateY(${Math.floor(scrollPixelsRemaining)}px)`}"
@@ -11,18 +13,23 @@
<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"
:authorType="message.authorType" :content="getShowContent(message)" :privilegeType="message.privilegeType"
:repeated="message.repeated"
></text-message>
<legacy-paid-message :key="message.id" v-else-if="message.type === MESSAGE_TYPE_MEMBER"
<paid-message :key="message.id" v-else-if="message.type === MESSAGE_TYPE_GIFT"
class="style-scope yt-live-chat-item-list-renderer"
:avatarUrl="message.avatarUrl" :title="message.title" :content="message.content"
:time="message.time"
></legacy-paid-message>
:price="message.price" :avatarUrl="message.avatarUrl" :authorName="message.authorName"
:time="message.time" :content="getGiftShowContent(message)"
></paid-message>
<membership-item :key="message.id" v-else-if="message.type === MESSAGE_TYPE_MEMBER"
class="style-scope yt-live-chat-item-list-renderer"
:avatarUrl="message.avatarUrl" :authorName="message.authorName" :privilegeType="message.privilegeType"
:title="message.title" :time="message.time"
></membership-item>
<paid-message :key="message.id" v-else-if="message.type === MESSAGE_TYPE_SUPER_CHAT"
class="style-scope yt-live-chat-item-list-renderer"
:price="message.price" :avatarUrl="message.avatarUrl" :authorName="message.authorName"
:time="message.time" :content="message.content"
:time="message.time" :content="getShowContent(message)"
></paid-message>
</template>
</div>
@@ -33,10 +40,10 @@
</template>
<script>
import * as config from '@/api/config'
import * as chatConfig from '@/api/chatConfig'
import Ticker from './Ticker.vue'
import TextMessage from './TextMessage.vue'
import LegacyPaidMessage from './LegacyPaidMessage.vue'
import MembershipItem from './MembershipItem.vue'
import PaidMessage from './PaidMessage.vue'
import * as constants from './constants'
@@ -48,14 +55,18 @@ export default {
components: {
Ticker,
TextMessage,
LegacyPaidMessage,
MembershipItem,
PaidMessage
},
props: {
css: String,
maxNumber: {
type: Number,
default: config.DEFAULT_CONFIG.maxNumber
default: chatConfig.DEFAULT_CONFIG.maxNumber
},
showGiftName: {
type: Boolean,
default: chatConfig.DEFAULT_CONFIG.showGiftName
}
},
data() {
@@ -63,6 +74,7 @@ export default {
document.head.appendChild(styleElement)
return {
MESSAGE_TYPE_TEXT: constants.MESSAGE_TYPE_TEXT,
MESSAGE_TYPE_GIFT: constants.MESSAGE_TYPE_GIFT,
MESSAGE_TYPE_MEMBER: constants.MESSAGE_TYPE_MEMBER,
MESSAGE_TYPE_SUPER_CHAT: constants.MESSAGE_TYPE_SUPER_CHAT,
@@ -86,7 +98,8 @@ export default {
smoothScrollRafHandle: null, // 平滑滚动requestAnimationFrame句柄
lastSmoothScrollUpdate: null, // 平滑滚动上一帧时间
atBottom: true // 滚动到底部,用来判断能否自动滚动
atBottom: true, // 滚动到底部,用来判断能否自动滚动
cantScrollStartTime: null // 开始不能自动滚动的时间,用来防止卡住
}
},
computed: {
@@ -94,6 +107,14 @@ export default {
return this.atBottom/* || this.allowScroll*/
}
},
watch: {
css(val) {
this.styleElement.innerText = val
},
canScrollToBottom(val) {
this.cantScrollStartTime = val ? null : new Date()
}
},
mounted() {
this.styleElement.innerText = this.css
this.scrollToBottom()
@@ -106,12 +127,12 @@ export default {
}
this.clearMessages()
},
watch: {
css(val) {
this.styleElement.innerText = val
}
},
methods: {
getGiftShowContent(message) {
return constants.getGiftShowContent(message, this.showGiftName)
},
getShowContent: constants.getShowContent,
addMessage(message) {
this.addMessages([message])
},
@@ -137,6 +158,7 @@ export default {
if (longer.indexOf(shorter) !== -1 // 长的包含短的
&& longer.length - shorter.length < shorter.length // 长度差较小
) {
// 其实有小概率导致弹幕卡住
message.repeated++
res = true
return false
@@ -145,14 +167,15 @@ export default {
})
return res
},
mergeSimilarGift(authorName, price) {
mergeSimilarGift(authorName, price, giftName, num) {
let res = false
this.forEachRecentMessage(5, message => {
if (message.type === constants.MESSAGE_TYPE_SUPER_CHAT
&& message.content === ''
if (message.type === constants.MESSAGE_TYPE_GIFT
&& message.authorName === authorName
&& message.giftName === giftName
) {
message.price += price
message.num += num
res = true
return false
}
@@ -184,8 +207,8 @@ export default {
delMessages(ids) {
this.enqueueMessages(ids.map(id => {
return {
id: id,
type: constants.MESSAGE_TYPE_DEL
type: constants.MESSAGE_TYPE_DEL,
id
}
}))
},
@@ -206,6 +229,13 @@ export default {
this.scrollToBottom()
}
},
updateMessage(id, newValuesObj) {
this.enqueueMessages([{
type: constants.MESSAGE_TYPE_UPDATE,
id,
newValuesObj
}])
},
enqueueMessages(messages) {
if (this.lastEnqueueTime) {
@@ -225,7 +255,7 @@ export default {
let messageGroup = []
for (let message of messages) {
messageGroup.push(message)
if (message.type !== constants.MESSAGE_TYPE_DEL) {
if (message.type !== constants.MESSAGE_TYPE_DEL && message.type !== constants.MESSAGE_TYPE_UPDATE) {
this.smoothedMessageQueue.push(messageGroup)
messageGroup = []
}
@@ -249,16 +279,25 @@ export default {
if (this.estimatedEnqueueInterval) {
estimatedNextEnqueueRemainTime = Math.max(this.lastEnqueueTime - new Date() + this.estimatedEnqueueInterval, 1)
}
// 最快80ms/条,计算发送的消息数,保证在下次进队列之前消费队列
// 最快80ms/条,计算发送的消息数,保证在下次进队列之前消费队列到最多剩3条消息不消费完是为了防止消息速度变慢时突然停顿
const MIN_SLEEP_TIME = 80
const MAX_SLEEP_TIME = 1000
const MAX_REMAIN_GROUP_NUM = 3
// 下次进队列之前应该发多少条消息
let shouldEmitGroupNum = Math.max(this.smoothedMessageQueue.length - MAX_REMAIN_GROUP_NUM, 0)
// 下次进队列之前最多能发多少次
let maxCanEmitCount = estimatedNextEnqueueRemainTime / MIN_SLEEP_TIME
// 这次发多少条消息
let groupNumToEmit
if (this.smoothedMessageQueue.length < estimatedNextEnqueueRemainTime / 80) {
// 队列中消息数很少每次发1条也能发
if (shouldEmitGroupNum < maxCanEmitCount) {
// 队列中消息数很少每次发1条也能发到最多剩3条
groupNumToEmit = 1
} else {
// 每次发1条以上保证按最快速度能发
groupNumToEmit = Math.ceil(this.smoothedMessageQueue.length / (estimatedNextEnqueueRemainTime / 80))
// 每次发1条以上保证按最快速度能发到最多剩3条
groupNumToEmit = Math.ceil(shouldEmitGroupNum / maxCanEmitCount)
}
// 发消息
let messageGroups = this.smoothedMessageQueue.splice(0, groupNumToEmit)
let mergedGroup = []
for (let messageGroup of messageGroups) {
@@ -271,19 +310,20 @@ export default {
if (this.smoothedMessageQueue.length <= 0) {
return
}
// 消息没发完,计算下次发消息时间
let sleepTime
if (groupNumToEmit == 1) {
// 队列中消息数很少,随便定个80-1000ms的时间
if (groupNumToEmit === 1) {
// 队列中消息数很少,随便定个[MIN_SLEEP_TIME, MAX_SLEEP_TIME]的时间
sleepTime = estimatedNextEnqueueRemainTime / this.smoothedMessageQueue.length
sleepTime *= 0.5 + Math.random()
if (sleepTime > 1000) {
sleepTime = 1000
} else if (sleepTime < 80) {
sleepTime = 80
if (sleepTime > MAX_SLEEP_TIME) {
sleepTime = MAX_SLEEP_TIME
} else if (sleepTime < MIN_SLEEP_TIME) {
sleepTime = MIN_SLEEP_TIME
}
} else {
// 按最快速度发
sleepTime = 80
sleepTime = MIN_SLEEP_TIME
}
this.emitSmoothedMessageTimerId = window.setTimeout(this.emitSmoothedMessages, sleepTime)
},
@@ -296,12 +336,16 @@ export default {
for (let message of messageGroup) {
switch (message.type) {
case constants.MESSAGE_TYPE_TEXT:
case constants.MESSAGE_TYPE_GIFT:
case constants.MESSAGE_TYPE_MEMBER:
case constants.MESSAGE_TYPE_SUPER_CHAT:
this.handleAddMessage(message)
break
case constants.MESSAGE_TYPE_DEL:
this.handleDelMessage(message.id)
this.handleDelMessage(message)
break
case constants.MESSAGE_TYPE_UPDATE:
this.handleUpdateMessage(message)
break
}
}
@@ -320,23 +364,46 @@ export default {
this.paidMessages.unshift(message)
}
},
handleDelMessage(message) {
let id = message.id
handleDelMessage({id}) {
for (let arr of [this.messages, this.paidMessages, this.messagesBuffer]) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].id === id) {
arr.splice(i, 1)
this.resetSmoothScroll()
break
}
}
}
},
handleUpdateMessage({id, newValuesObj}) {
// 遍历滚动的消息
this.forEachRecentMessage(999999999, message => {
if (message.id !== id) {
return true
}
for (let name in newValuesObj) {
message[name] = newValuesObj[name]
}
return false
})
// 遍历固定的消息
for (let message of this.paidMessages) {
if (message.id !== id) {
continue
}
for (let name in newValuesObj) {
message[name] = newValuesObj[name]
}
break
}
this.resetSmoothScroll()
},
async flushMessagesBuffer() {
if (this.messagesBuffer.length <= 0) {
return
}
if (!this.canScrollToBottom) {
if (!this.canScrollToBottomOrTimedOut()) {
if (this.messagesBuffer.length > this.maxNumber) {
// 未显示消息数 > 最大可显示数,丢弃
this.messagesBuffer.splice(0, this.messagesBuffer.length - this.maxNumber)
@@ -362,7 +429,7 @@ export default {
showNewMessages() {
let hasScrollBar = this.$refs.items.clientHeight > this.$refs.scroller.clientHeight
this.$refs.itemOffset.style.height = `${this.$refs.items.clientHeight}px`
if (!this.canScrollToBottom || !hasScrollBar) {
if (!this.canScrollToBottomOrTimedOut() || !hasScrollBar) {
return
}
@@ -436,7 +503,7 @@ export default {
this.maybeScrollToBottom()
},
maybeScrollToBottom() {
if (this.canScrollToBottom) {
if (this.canScrollToBottomOrTimedOut()) {
this.scrollToBottom()
}
},
@@ -445,546 +512,28 @@ export default {
this.atBottom = true
},
onScroll() {
this.refreshCantScrollStartTime()
let scroller = this.$refs.scroller
this.atBottom = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight < SCROLLED_TO_BOTTOM_EPSILON
this.flushMessagesBuffer()
},
canScrollToBottomOrTimedOut() {
if (this.canScrollToBottom) {
return true
}
// 防止在OBS中卡住超过一定时间也可以自动滚动
return new Date() - this.cantScrollStartTime >= 5 * 1000
},
refreshCantScrollStartTime() {
// 有鼠标事件时刷新,防止用户看弹幕时自动滚动
if (this.cantScrollStartTime) {
this.cantScrollStartTime = new Date()
}
}
}
}
</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>
<style src="@/assets/css/youtube/yt-html.css"></style>
<style src="@/assets/css/youtube/yt-live-chat-renderer.css"></style>
<style src="@/assets/css/youtube/yt-live-chat-item-list-renderer.css"></style>

View File

@@ -4,7 +4,7 @@ export default {
stylegen: 'Style generator',
help: 'Help',
projectAddress: 'Project address',
giftRecord: 'Super Chat record'
giftRecordOfficial: 'Official Super Chat record',
},
home: {
roomIdEmpty: "Room ID can't be empty",
@@ -14,7 +14,9 @@ export default {
roomId: 'Room ID',
showDanmaku: 'Show messages',
showGift: 'Show Super Chats',
showGiftName: 'Show gift name',
mergeSimilarDanmaku: 'Merge similar messages',
mergeGift: 'Merge gifts',
minGiftPrice: 'Min price of Super Chats to show (CNY)',
maxNumber: 'Max number of messages',
@@ -28,6 +30,10 @@ export default {
blockUsers: 'Block users',
blockMedalLevel: 'Block medal level lower than',
advanced: 'Advanced',
relayMessagesByServer: 'Relay messages by server',
autoTranslate: 'Auto translate messages to Japanese (requires relay messages by server)',
roomUrl: 'Room URL',
copy: 'Copy',
enterRoom: 'Enter room',
@@ -47,6 +53,7 @@ export default {
avatarSize: 'Avatar size',
userNames: 'User names',
showUserNames: 'Show user names',
font: 'Font',
fontSize: 'Font size',
lineHeight: 'Line height (0 for default)',
@@ -106,7 +113,7 @@ export default {
help: {
help: 'Help',
p1: '1. Copy the room ID from the Bilibili live room webpage',
p2: '2. Enter the room ID into the room ID on the home page. Copy the room URL after saving the configuration',
p2: '2. Enter the room ID into the room ID on the home page. Copy the room URL',
p3: '3. Generate styles with the style generator. Copy the CSS',
p4: '4. Add browser source in OBS',
p5: '5. Enter the previously copied room URL at URL, and enter the previously copied CSS at custom CSS'

View File

@@ -4,7 +4,7 @@ export default {
stylegen: 'スタイルジェネレータ',
help: 'ヘルプ',
projectAddress: 'プロジェクトアドレス',
giftRecord: 'スーパーチャット記録'
giftRecordOfficial: '公式スーパーチャット記録',
},
home: {
roomIdEmpty: 'ルームのIDを空白にすることはできません',
@@ -14,7 +14,9 @@ export default {
roomId: 'ルームID',
showDanmaku: 'コメントを表示する',
showGift: 'スーパーチャットと新メンバーを表示する',
showGiftName: 'ギフト名を表示する',
mergeSimilarDanmaku: '同じコメントを合併する',
mergeGift: 'ギフトを合併する',
minGiftPrice: '最低表示スーパーチャット価格CNY',
maxNumber: '最大コメント数',
@@ -28,6 +30,10 @@ export default {
blockUsers: 'ブロックユーザー',
blockMedalLevel: 'ブロック勲章等級がx未満',
advanced: 'アドバンスド',
relayMessagesByServer: 'サーバを介してメッセージを転送する',
autoTranslate: 'コメントを日本語に翻訳する(サーバを介してメッセージを転送する必要)',
roomUrl: 'ルームのURL',
copy: 'コピー',
enterRoom: 'ルームに入る',
@@ -47,6 +53,7 @@ export default {
avatarSize: 'アイコンのサイズ',
userNames: 'ユーザー名',
showUserNames: 'ユーザー名を表示する',
font: 'フォント',
fontSize: 'フォントサイズ',
lineHeight: '行の高さ0はデフォルト',
@@ -106,7 +113,7 @@ export default {
help: {
help: 'ヘルプ',
p1: '1. ビリビリの生放送ウェブから生放送ルームIDをこぴーする',
p2: '2. ホームページでコピーしたIDを入力し、配置を保存すると、ルームのURLをこぴーする',
p2: '2. ホームページでコピーしたIDを入力し、ルームのURLをこぴーする',
p3: '3. スタイルジェネレータでお好みのコメント様子を選び、出力したCSSをコピーする',
p4: '4. OBSでブラウザを新規作成する',
p5: '5. プロパティでこぴーしたURLを入力し、カスタムCSSでスタイルジェネレータのCSSを入力する'

View File

@@ -4,7 +4,7 @@ export default {
stylegen: '样式生成器',
help: '帮助',
projectAddress: '项目地址',
giftRecord: '打赏记录'
giftRecordOfficial: '官方打赏记录',
},
home: {
roomIdEmpty: '房间ID不能为空',
@@ -14,7 +14,9 @@ export default {
roomId: '房间ID',
showDanmaku: '显示弹幕',
showGift: '显示打赏和新舰长',
showGiftName: '显示礼物名',
mergeSimilarDanmaku: '合并相似弹幕',
mergeGift: '合并礼物',
minGiftPrice: '最低显示打赏价格(元)',
maxNumber: '最大弹幕数',
@@ -28,6 +30,10 @@ export default {
blockUsers: '屏蔽用户',
blockMedalLevel: '屏蔽当前直播间勋章等级低于',
advanced: '高级',
relayMessagesByServer: '通过服务器转发消息',
autoTranslate: '自动翻译弹幕到日语(需要通过服务器转发消息)',
roomUrl: '房间URL',
copy: '复制',
enterRoom: '进入房间',
@@ -47,6 +53,7 @@ export default {
avatarSize: '头像尺寸',
userNames: '用户名',
showUserNames: '显示用户名',
font: '字体',
fontSize: '字体尺寸',
lineHeight: '行高0为默认',
@@ -106,7 +113,7 @@ export default {
help: {
help: '帮助',
p1: '1. 从B站直播间网页复制房间ID',
p2: '2. 把房间ID输入到首页的房间ID保存配置后复制房间URL',
p2: '2. 把房间ID输入到首页的房间ID复制房间URL',
p3: '3. 使用样式生成器生成样式复制CSS',
p4: '4. 在OBS中添加浏览器源',
p5: '5. URL处输入之前复制的房间URL自定义CSS处输入之前复制的CSS'

View File

@@ -21,9 +21,9 @@
<i class="el-icon-share"></i>{{$t('sidebar.projectAddress')}}
</el-menu-item>
</a>
<a href="https://bilisc.com/" target="_blank">
<a href="http://link.bilibili.com/ctool/vtuber" target="_blank">
<el-menu-item>
<i class="el-icon-share"></i>{{$t('sidebar.giftRecord')}}
<i class="el-icon-share"></i>{{$t('sidebar.giftRecordOfficial')}}
</el-menu-item>
</a>
<el-submenu index="null">

View File

@@ -1,7 +1,7 @@
<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}">
<el-aside width="230px" class="sidebar-container" :class="{'hide-sidebar': hideSidebar}">
<div class="logo-container">
<router-link to="/">
<img src="@/assets/img/logo.png" class="sidebar-logo">
@@ -9,13 +9,15 @@
</router-link>
</div>
<div class="version">
v1.3.0
v1.5.0
</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>
<keep-alive>
<router-view></router-view>
</keep-alive>
</el-main>
</el-container>
</template>
@@ -51,7 +53,7 @@ export default {
<style>
html {
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "\5FAE\8F6F\96C5\9ED1", "微软雅黑", Arial, sans-serif;
}
html, body, #app, .app-wrapper, .sidebar-container {
@@ -92,7 +94,7 @@ a, a:focus, a:hover {
.app-wrapper.mobile .sidebar-container.hide-sidebar {
pointer-events: none;
transition-duration: 0.3s;
transform: translate3d(-210px, 0, 0);
transform: translate3d(-230px, 0, 0);
}
.sidebar-container .logo-container {

View File

@@ -1,8 +1,10 @@
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 {
Aside, Autocomplete, Badge, Button, Col, ColorPicker, Container, Divider, Form, FormItem, Image,
Input, Main, Menu, MenuItem, Message, Row, Scrollbar, Slider, Submenu, Switch, TabPane, Tabs, Tooltip
} from 'element-ui'
import axios from 'axios'
import App from './App.vue'
@@ -23,8 +25,32 @@ if (process.env.NODE_ENV === 'development') {
}
Vue.use(VueRouter)
Vue.use(ElementUI)
Vue.use(VueI18n)
// 初始化element
Vue.use(Aside)
Vue.use(Autocomplete)
Vue.use(Badge)
Vue.use(Button)
Vue.use(Col)
Vue.use(ColorPicker)
Vue.use(Container)
Vue.use(Divider)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Image)
Vue.use(Input)
Vue.use(Main)
Vue.use(Menu)
Vue.use(MenuItem)
Vue.use(Row)
Vue.use(Scrollbar)
Vue.use(Slider)
Vue.use(Submenu)
Vue.use(Switch)
Vue.use(TabPane)
Vue.use(Tabs)
Vue.use(Tooltip)
Vue.prototype.$message = Message
Vue.config.ignoredElements = [
/^yt-/

View File

@@ -8,7 +8,7 @@ export function mergeConfig (config, defaultConfig) {
export function toBool (val) {
if (typeof val === 'string') {
return val !== 'false' && val !== ''
return ['false', 'no', 'off', '0', ''].indexOf(val.toLowerCase()) === -1
}
return !!val
}
@@ -27,8 +27,17 @@ export function formatCurrency (price) {
}).format(price)
}
export function getTimeTextMinSec (date) {
export function getTimeTextHourMin (date) {
let hour = date.getHours()
let min = ('00' + date.getMinutes()).slice(-2)
let sec = ('00' + date.getSeconds()).slice(-2)
return `${min}:${sec}`
return `${hour}:${min}`
}
export function getUuid4Hex () {
let chars = []
for (let i = 0; i < 32; i++) {
let char = Math.floor(Math.random() * 16).toString(16)
chars.push(char)
}
return chars.join('')
}

View File

@@ -11,6 +11,8 @@
<p><el-image src="/static/img/tutorial/tutorial-4.png"></el-image></p>
<p>{{$t('help.p5')}}</p>
<p><el-image src="/static/img/tutorial/tutorial-5.png"></el-image></p>
<p><br><br><br><br><br><br><br><br>--------------------------------------------------------------------------------------------------------</p>
<p>喜欢的话可以推荐给别人专栏求支持_(:з)_ <a href="https://www.bilibili.com/read/cv4594365" target="_blank">https://www.bilibili.com/read/cv4594365</a></p>
</div>
</template>

View File

@@ -16,9 +16,15 @@
<el-form-item :label="$t('home.showGift')">
<el-switch v-model="form.showGift"></el-switch>
</el-form-item>
<el-form-item :label="$t('home.showGiftName')">
<el-switch v-model="form.showGiftName"></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.mergeGift')">
<el-switch v-model="form.mergeGift"></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>
@@ -47,14 +53,23 @@
<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-slider v-model="form.blockMedalLevel" show-input :min="0" :max="40"></el-slider>
</el-form-item>
</el-tab-pane>
<el-tab-pane :label="$t('home.advanced')">
<el-form-item :label="$t('home.relayMessagesByServer')">
<el-switch v-model="form.relayMessagesByServer"></el-switch>
</el-form-item>
<el-form-item :label="$t('home.autoTranslate')">
<el-switch v-model="form.autoTranslate" :disabled="!serverConfig.enableTranslate || !form.relayMessagesByServer"></el-switch>
</el-form-item>
</el-tab-pane>
</el-tabs>
<el-divider></el-divider>
<el-form-item :label="$t('home.roomUrl')">
<el-input ref="roomUrlInput" readonly :value="roomUrl" style="width: calc(100% - 8em); margin-right: 1em;"></el-input>
<el-input ref="roomUrlInput" readonly :value="obsRoomUrl" style="width: calc(100% - 8em); margin-right: 1em;"></el-input>
<el-button type="primary" @click="copyUrl">{{$t('home.copy')}}</el-button>
</el-form-item>
<el-form-item>
@@ -67,18 +82,23 @@
<script>
import _ from 'lodash'
import axios from 'axios'
import download from 'downloadjs'
import {mergeConfig} from '@/utils'
import * as config from '@/api/config'
import * as chatConfig from '@/api/chatConfig'
export default {
name: 'Home',
data() {
return {
serverConfig: {
enableTranslate: true,
loaderUrl: ''
},
form: {
roomId: parseInt(window.localStorage.roomId || '1'),
...config.getLocalConfig()
...chatConfig.getLocalConfig()
}
}
},
@@ -91,15 +111,36 @@ export default {
delete query.roomId
let resolved = this.$router.resolve({name: 'room', params: {roomId: this.form.roomId}, query})
return `${window.location.protocol}//${window.location.host}${resolved.href}`
},
obsRoomUrl() {
if (this.roomUrl === '') {
return ''
}
if (this.serverConfig.loaderUrl === '') {
return this.roomUrl
}
let url = new URL(this.serverConfig.loaderUrl)
url.searchParams.append('url', this.roomUrl)
return url.href
}
},
watch: {
roomUrl: _.debounce(function() {
window.localStorage.roomId = this.form.roomId
config.setLocalConfig(this.form)
chatConfig.setLocalConfig(this.form)
}, 500)
},
mounted() {
this.updateServerConfig()
},
methods: {
async updateServerConfig() {
try {
this.serverConfig = (await axios.get('/api/server_info')).data.config
} catch (e) {
this.$message.error('Failed to fetch server information: ' + e)
}
},
enterRoom() {
window.open(this.roomUrl, `room ${this.form.roomId}`, 'menubar=0,location=0,scrollbars=0,toolbar=0,width=600,height=600')
},
@@ -108,7 +149,7 @@ export default {
document.execCommand('Copy')
},
exportConfig() {
let cfg = mergeConfig(this.form, config.DEFAULT_CONFIG)
let cfg = mergeConfig(this.form, chatConfig.DEFAULT_CONFIG)
download(JSON.stringify(cfg, null, 2), 'blivechat.json', 'application/json')
},
importConfig() {
@@ -125,7 +166,7 @@ export default {
this.$message.error(this.$t('home.failedToParseConfig') + e)
return
}
cfg = mergeConfig(cfg, config.DEFAULT_CONFIG)
cfg = mergeConfig(cfg, chatConfig.DEFAULT_CONFIG)
this.form = {roomId: this.form.roomId, ...cfg}
}
reader.readAsText(input.files[0])

View File

@@ -1,21 +1,15 @@
<template>
<chat-renderer ref="renderer" :maxNumber="config.maxNumber"></chat-renderer>
<chat-renderer ref="renderer" :maxNumber="config.maxNumber" :showGiftName="config.showGiftName"></chat-renderer>
</template>
<script>
import {mergeConfig, toBool, toInt} from '@/utils'
import * as config from '@/api/config'
import * as chatConfig from '@/api/chatConfig'
import ChatClientDirect from '@/api/chat/ChatClientDirect'
import ChatClientRelay from '@/api/chat/ChatClientRelay'
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: {
@@ -23,14 +17,8 @@ export default {
},
data() {
return {
config: {...config.DEFAULT_CONFIG},
websocket: null,
retryCount: 0,
isDestroying: false,
heartbeatTimerId: null,
nextId: 0,
config: {...chatConfig.DEFAULT_CONFIG},
chatClient: null
}
},
computed: {
@@ -42,15 +30,21 @@ export default {
}
},
created() {
this.wsConnect()
this.updateConfig()
this.initConfig()
this.initChatClient()
// 提示用户已加载
this.$message({
message: 'Loaded',
duration: '500'
})
},
beforeDestroy() {
this.isDestroying = true
this.websocket.close()
if (this.chatClient) {
this.chatClient.stop()
}
},
methods: {
updateConfig() {
initConfig() {
let cfg = {}
// 留空的使用默认值
for (let i in this.$route.query) {
@@ -58,142 +52,127 @@ export default {
cfg[i] = this.$route.query[i]
}
}
cfg = mergeConfig(cfg, config.DEFAULT_CONFIG)
cfg = mergeConfig(cfg, chatConfig.DEFAULT_CONFIG)
cfg.minGiftPrice = toInt(cfg.minGiftPrice, config.DEFAULT_CONFIG.minGiftPrice)
cfg.mergeSimilarDanmaku = toBool(cfg.mergeSimilarDanmaku)
cfg.minGiftPrice = toInt(cfg.minGiftPrice, chatConfig.DEFAULT_CONFIG.minGiftPrice)
cfg.showDanmaku = toBool(cfg.showDanmaku)
cfg.showGift = toBool(cfg.showGift)
cfg.maxNumber = toInt(cfg.maxNumber, config.DEFAULT_CONFIG.maxNumber)
cfg.showGiftName = toBool(cfg.showGiftName)
cfg.mergeSimilarDanmaku = toBool(cfg.mergeSimilarDanmaku)
cfg.mergeGift = toBool(cfg.mergeGift)
cfg.maxNumber = toInt(cfg.maxNumber, chatConfig.DEFAULT_CONFIG.maxNumber)
cfg.blockGiftDanmaku = toBool(cfg.blockGiftDanmaku)
cfg.blockLevel = toInt(cfg.blockLevel, config.DEFAULT_CONFIG.blockLevel)
cfg.blockLevel = toInt(cfg.blockLevel, chatConfig.DEFAULT_CONFIG.blockLevel)
cfg.blockNewbie = toBool(cfg.blockNewbie)
cfg.blockNotMobileVerified = toBool(cfg.blockNotMobileVerified)
cfg.blockMedalLevel = toInt(cfg.blockMedalLevel, config.DEFAULT_CONFIG.blockMedalLevel)
cfg.blockMedalLevel = toInt(cfg.blockMedalLevel, chatConfig.DEFAULT_CONFIG.blockMedalLevel)
cfg.relayMessagesByServer = toBool(cfg.relayMessagesByServer)
cfg.autoTranslate = toBool(cfg.autoTranslate)
this.config = cfg
},
wsConnect() {
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'
// 开发时使用localhost:12450
const host = process.env.NODE_ENV === 'development' ? 'localhost:12450' : window.location.host
const url = `${protocol}://${host}/chat`
this.websocket = new WebSocket(url)
this.websocket.onopen = this.onWsOpen
this.websocket.onclose = this.onWsClose
this.websocket.onmessage = this.onWsMessage
this.heartbeatTimerId = window.setInterval(this.sendHeartbeat, 10 * 1000)
},
sendHeartbeat() {
this.websocket.send(JSON.stringify({
cmd: COMMAND_HEARTBEAT
}))
},
onWsOpen() {
this.retryCount = 0
this.websocket.send(JSON.stringify({
cmd: COMMAND_JOIN_ROOM,
data: {
roomId: parseInt(this.$route.params.roomId)
}
}))
},
onWsClose() {
if (this.heartbeatTimerId) {
window.clearInterval(this.heartbeatTimerId)
this.heartbeatTimerId = null
initChatClient() {
let roomId = parseInt(this.$route.params.roomId)
if (!this.config.relayMessagesByServer) {
this.chatClient = new ChatClientDirect(roomId)
} else {
this.chatClient = new ChatClientRelay(roomId, this.config.autoTranslate)
}
if (this.isDestroying) {
this.chatClient.onAddText = this.onAddText
this.chatClient.onAddGift = this.onAddGift
this.chatClient.onAddMember = this.onAddMember
this.chatClient.onAddSuperChat = this.onAddSuperChat
this.chatClient.onDelSuperChat = this.onDelSuperChat
this.chatClient.onUpdateTranslation = this.onUpdateTranslation
this.chatClient.start()
},
onAddText(data) {
if (!this.config.showDanmaku || !this.filterTextMessage(data) || this.mergeSimilarText(data.content)) {
return
}
window.console.log(`掉线重连中${++this.retryCount}`)
this.wsConnect()
let message = {
id: data.id,
type: constants.MESSAGE_TYPE_TEXT,
avatarUrl: data.avatarUrl,
time: new Date(data.timestamp * 1000),
authorName: data.authorName,
authorType: data.authorType,
content: data.content,
privilegeType: data.privilegeType,
repeated: 1,
translation: data.translation
}
this.$refs.renderer.addMessage(message)
},
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.mergeSimilarText(data.content)) {
break
}
message = {
id: `text_${this.nextId++}`,
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
}
if (this.mergeSimilarGift(data.authorName, price)) {
break
}
message = {
id: `gift_${this.nextId++}`,
type: constants.MESSAGE_TYPE_SUPER_CHAT,
avatarUrl: data.avatarUrl,
authorName: data.authorName,
price: price,
time: time,
content: '' // 有了SC礼物不需要内容了
}
break
onAddGift(data) {
if (!this.config.showGift) {
return
}
case COMMAND_ADD_MEMBER:
if (!this.config.showGift || !this.filterNewMemberMessage(data)) {
break
}
message = {
id: `member_${this.nextId++}`,
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 || !this.filterSuperChatMessage(data)) {
break
}
if (data.price < this.config.minGiftPrice) { // 丢人
break
}
message = {
id: `sc_${data.id}`,
type: constants.MESSAGE_TYPE_SUPER_CHAT,
avatarUrl: data.avatarUrl,
authorName: data.authorName,
price: data.price,
time: time,
content: data.content.trim()
}
break
case COMMAND_DEL_SUPER_CHAT:
for (let id of data.ids) {
id = `sc_${id}`
this.$refs.renderer.delMessage(id)
}
break
let price = data.totalCoin / 1000
if (this.mergeSimilarGift(data.authorName, price, data.giftName, data.num)) {
return
}
if (message) {
this.$refs.renderer.addMessage(message)
if (price < this.config.minGiftPrice) { // 丢人
return
}
let message = {
id: data.id,
type: constants.MESSAGE_TYPE_GIFT,
avatarUrl: data.avatarUrl,
time: new Date(data.timestamp * 1000),
authorName: data.authorName,
price: price,
giftName: data.giftName,
num: data.num
}
this.$refs.renderer.addMessage(message)
},
onAddMember(data) {
if (!this.config.showGift || !this.filterNewMemberMessage(data)) {
return
}
let message = {
id: data.id,
type: constants.MESSAGE_TYPE_MEMBER,
avatarUrl: data.avatarUrl,
time: new Date(data.timestamp * 1000),
authorName: data.authorName,
privilegeType: data.privilegeType,
title: 'New member'
}
this.$refs.renderer.addMessage(message)
},
onAddSuperChat(data) {
if (!this.config.showGift || !this.filterSuperChatMessage(data)) {
return
}
if (data.price < this.config.minGiftPrice) { // 丢人
return
}
let message = {
id: data.id,
type: constants.MESSAGE_TYPE_SUPER_CHAT,
avatarUrl: data.avatarUrl,
authorName: data.authorName,
price: data.price,
time: new Date(data.timestamp * 1000),
content: data.content.trim()
}
this.$refs.renderer.addMessage(message)
},
onDelSuperChat(data) {
for (let id of data.ids) {
this.$refs.renderer.delMessage(id)
}
},
onUpdateTranslation(data) {
if (!this.config.autoTranslate) {
return
}
this.$refs.renderer.updateMessage(data.id, {translation: data.translation})
},
filterTextMessage(data) {
if (this.config.blockGiftDanmaku && data.isGiftDanmaku) {
return false
@@ -230,11 +209,11 @@ export default {
}
return this.$refs.renderer.mergeSimilarText(content)
},
mergeSimilarGift(authorName, price) {
if (!this.config.mergeSimilarDanmaku) {
mergeSimilarGift(authorName, price, giftName, num) {
if (!this.config.mergeGift) {
return false
}
return this.$refs.renderer.mergeSimilarGift(authorName, price)
return this.$refs.renderer.mergeSimilarGift(authorName, price, giftName, num)
}
}
}

View File

@@ -22,6 +22,9 @@
</el-form-item>
<h3>{{$t('stylegen.userNames')}}</h3>
<el-form-item :label="$t('stylegen.showUserNames')">
<el-switch v-model="form.showUserNames"></el-switch>
</el-form-item>
<el-form-item :label="$t('stylegen.font')">
<el-autocomplete v-model="form.userNameFont" :fetch-suggestions="getFontSuggestions"></el-autocomplete>
</el-form-item>
@@ -216,17 +219,18 @@ let textMessageTemplate = {
authorType: constants.AUTHRO_TYPE_NORMAL,
content: '',
privilegeType: 0,
repeated: 1
repeated: 1,
translation: ''
}
let legacyPaidMessageTemplate = {
let membershipItemTemplate = {
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: ''
privilegeType: 3,
title: 'New member'
}
let paidMessageTemplate = {
id: 0,
@@ -236,20 +240,21 @@ let paidMessageTemplate = {
authorName: '',
price: 0,
time: time,
content: ''
content: '',
translation: ''
}
let nextId = 0
const EXAMPLE_MESSAGES = [
{
...textMessageTemplate,
id: nextId++,
id: (nextId++).toString(),
authorName: 'mob路人',
content: '8888888888',
repeated: 12
},
{
...textMessageTemplate,
id: nextId++,
id: (nextId++).toString(),
authorName: 'member舰长',
authorType: constants.AUTHRO_TYPE_MEMBER,
content: '草',
@@ -258,35 +263,34 @@ const EXAMPLE_MESSAGES = [
},
{
...textMessageTemplate,
id: nextId++,
id: (nextId++).toString(),
authorName: 'admin房管',
authorType: constants.AUTHRO_TYPE_ADMIN,
content: 'kksk'
},
{
...legacyPaidMessageTemplate,
id: nextId++,
authorName: '少年Pi',
content: 'Welcome 少年Pi!'
...membershipItemTemplate,
id: (nextId++).toString(),
authorName: '艾米亚official'
},
{
...paidMessageTemplate,
id: nextId++,
authorName: '无火的残渣',
id: (nextId++).toString(),
authorName: '愛里紗メイプル',
price: 66600,
content: 'Sent 小电视飞船x100'
},
{
...textMessageTemplate,
id: nextId++,
id: (nextId++).toString(),
authorName: 'streamer主播',
authorType: constants.AUTHRO_TYPE_OWNER,
content: '老板大气,老板身体健康'
},
{
...paidMessageTemplate,
id: nextId++,
authorName: '夏色祭保護協会会長',
id: (nextId++).toString(),
authorName: 'AstralisUP',
price: 30,
content: '言いたいことがあるんだよ!'
}

View File

@@ -9,6 +9,7 @@ export const DEFAULT_CONFIG = {
showAvatars: true,
avatarSize: 24,
showUserNames: true,
userNameFont: 'Changa One',
userNameFontSize: 20,
userNameLineHeight: 0,
@@ -16,7 +17,7 @@ export const DEFAULT_CONFIG = {
ownerUserNameColor: '#ffd600',
moderatorUserNameColor: '#5e84f1',
memberUserNameColor: '#0f9d58',
showBadges: false,
showBadges: true,
showColon: true,
messageFont: 'Imprima',
@@ -107,13 +108,13 @@ yt-live-chat-author-chip #author-name {
/* Outlines */
yt-live-chat-renderer * {
${getShowOutlinesStyle(config)}
font-family: "${config.messageFont}"${FALLBACK_FONTS};
font-family: "${cssEscapeStr(config.messageFont)}"${FALLBACK_FONTS};
font-size: ${config.messageFontSize}px !important;
line-height: ${config.messageLineHeight}px !important;
line-height: ${config.messageLineHeight || config.messageFontSize}px !important;
}
yt-live-chat-text-message-renderer #content,
yt-live-chat-legacy-paid-message-renderer #content {
yt-live-chat-membership-item-renderer #content {
overflow: initial !important;
}
@@ -133,12 +134,7 @@ yt-live-chat-message-input-renderer {
}
/* 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 {
yt-live-chat-text-message-renderer {
${getPaddingStyle(config)}
}
@@ -147,8 +143,8 @@ 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 {
yt-live-chat-membership-item-renderer #author-photo,
yt-live-chat-membership-item-renderer #author-photo img {
${config.showAvatars ? '' : 'display: none !important;'}
width: ${config.avatarSize}px !important;
height: ${config.avatarSize}px !important;
@@ -166,7 +162,7 @@ yt-live-chat-text-message-renderer #chat-badges {
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-family: "${cssEscapeStr(config.timeFont)}"${FALLBACK_FONTS};
font-size: ${config.timeFontSize}px !important;
line-height: ${config.timeLineHeight || config.timeFontSize}px !important;
}
@@ -189,8 +185,9 @@ yt-live-chat-text-message-renderer yt-live-chat-author-badge-renderer[type="memb
/* Channel names. */
yt-live-chat-text-message-renderer #author-name {
${config.showUserNames ? '' : 'display: none !important;'}
${config.userNameColor ? `color: ${config.userNameColor} !important;` : ''}
font-family: "${config.userNameFont}"${FALLBACK_FONTS};
font-family: "${cssEscapeStr(config.userNameFont)}"${FALLBACK_FONTS};
font-size: ${config.userNameFontSize}px !important;
line-height: ${config.userNameLineHeight || config.userNameFontSize}px !important;
}
@@ -201,7 +198,7 @@ ${getShowColonStyle(config)}
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-family: "${cssEscapeStr(config.messageFont)}"${FALLBACK_FONTS};
font-size: ${config.messageFontSize}px !important;
line-height: ${config.messageLineHeight || config.messageFontSize}px !important;
}
@@ -213,20 +210,20 @@ ${!config.messageOnNewLine ? '' : `yt-live-chat-text-message-renderer #message {
/* 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 * {
yt-live-chat-membership-item-renderer #header-content-inner-column,
yt-live-chat-membership-item-renderer #header-content-inner-column * {
${config.firstLineColor ? `color: ${config.firstLineColor} !important;` : ''}
font-family: "${config.firstLineFont}"${FALLBACK_FONTS};
font-family: "${cssEscapeStr(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 * {
yt-live-chat-membership-item-renderer #header-subtext,
yt-live-chat-membership-item-renderer #header-subtext * {
${config.secondLineColor ? `color: ${config.secondLineColor} !important;` : ''}
font-family: "${config.secondLineFont}"${FALLBACK_FONTS};
font-family: "${cssEscapeStr(config.secondLineFont)}"${FALLBACK_FONTS};
font-size: ${config.secondLineFontSize}px !important;
line-height: ${config.secondLineLineHeight || config.secondLineFontSize}px !important;
}
@@ -234,7 +231,7 @@ yt-live-chat-legacy-paid-message-renderer #detail-text * {
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-family: "${cssEscapeStr(config.scContentFont)}"${FALLBACK_FONTS};
font-size: ${config.scContentFontSize}px !important;
line-height: ${config.scContentLineHeight || config.scContentFontSize}px !important;
}
@@ -243,17 +240,18 @@ yt-live-chat-paid-message-renderer {
margin: 4px 0 !important;
}
yt-live-chat-legacy-paid-message-renderer #card {
yt-live-chat-membership-item-renderer #card,
yt-live-chat-membership-item-renderer #header {
${getShowNewMemberBgStyle(config)}
}
yt-live-chat-text-message-renderer a,
yt-live-chat-legacy-paid-message-renderer a {
yt-live-chat-membership-item-renderer a {
text-decoration: none !important;
}
yt-live-chat-text-message-renderer[is-deleted],
yt-live-chat-legacy-paid-message-renderer[is-deleted] {
yt-live-chat-membership-item-renderer[is-deleted] {
display: none !important;
}
@@ -275,7 +273,7 @@ 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};
font-family: "${cssEscapeStr(config.secondLineFont)}"${FALLBACK_FONTS};
}
yt-live-chat-mode-change-message-renderer,
@@ -339,6 +337,31 @@ function getShowOutlinesStyle (config) {
return `text-shadow: ${shadow.join(', ')};`
}
function cssEscapeStr (str) {
let res = []
for (let char of str) {
res.push(cssEscapeChar(char))
}
return res.join('')
}
function cssEscapeChar (char) {
if (!needEscapeChar(char)) {
return char
}
let hexCode = char.codePointAt(0).toString(16)
// https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
return `\\${hexCode} `
}
function needEscapeChar (char) {
let code = char.codePointAt(0)
if (0x20 <= code && code <= 0x7E) {
return char === '"'
}
return true
}
function getPaddingStyle (config) {
return `padding-left: ${config.useBarsInsteadOfBg ? 20 : 4}px !important;
padding-right: 4px !important;`
@@ -399,7 +422,8 @@ ${keyframes.join('\n')}
}
yt-live-chat-text-message-renderer,
yt-live-chat-legacy-paid-message-renderer {
yt-live-chat-membership-item-renderer,
yt-live-chat-paid-message-renderer {
animation: anim ${totalTime}ms;
animation-fill-mode: both;
}`

0
log/.gitkeep Normal file
View File

39
main.py
View File

@@ -2,6 +2,7 @@
import argparse
import logging
import logging.handlers
import os
import webbrowser
@@ -13,18 +14,22 @@ import api.main
import config
import models.avatar
import models.database
import models.translate
import update
logger = logging.getLogger(__name__)
WEB_ROOT = os.path.join(os.path.dirname(__file__), 'frontend', 'dist')
BASE_PATH = os.path.dirname(os.path.realpath(__file__))
WEB_ROOT = os.path.join(BASE_PATH, 'frontend', 'dist')
LOG_FILE_NAME = os.path.join(BASE_PATH, 'log', 'blivechat.log')
routes = [
(r'/chat', api.chat.ChatHandler),
(r'/api/server_info', api.main.ServerInfoHandler),
(r'/api/chat', api.chat.ChatHandler),
(r'/api/room_info', api.chat.RoomInfoHandler),
(r'/api/avatar_url', api.chat.AvatarHandler),
(r'/((css|fonts|img|js|static)/.*)', tornado.web.StaticFileHandler, {'path': WEB_ROOT}),
(r'/(favicon\.ico)', tornado.web.StaticFileHandler, {'path': WEB_ROOT}),
(r'/.*', api.main.MainHandler, {'path': WEB_ROOT})
(r'/(.*)', api.main.MainHandler, {'path': WEB_ROOT, 'default_filename': 'index.html'})
]
@@ -35,6 +40,7 @@ def main():
config.init()
models.database.init(args.debug)
models.avatar.init()
models.translate.init()
api.chat.init()
update.check_update()
@@ -42,7 +48,7 @@ def main():
def parse_args():
parser = argparse.ArgumentParser(description='用于OBS的仿YouTube风格的bilibili直播聊天层')
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='服务器端口默认为12450', type=int, default=12450)
parser.add_argument('--debug', help='调试模式', action='store_true')
@@ -50,13 +56,21 @@ def parse_args():
def init_logging(debug):
stream_handler = logging.StreamHandler()
file_handler = logging.handlers.TimedRotatingFileHandler(
LOG_FILE_NAME, encoding='utf-8', when='midnight', backupCount=7, delay=True
)
logging.basicConfig(
format='{asctime} {levelname} [{name}]: {message}',
datefmt='%Y-%m-%d %H:%M:%S',
style='{',
level=logging.INFO if not debug else logging.DEBUG
level=logging.INFO if not debug else logging.DEBUG,
handlers=[stream_handler, file_handler]
)
# 屏蔽访问日志
logging.getLogger('tornado.access').setLevel(logging.WARNING)
def run_server(host, port, debug):
app = tornado.web.Application(
@@ -65,13 +79,20 @@ def run_server(host, port, debug):
debug=debug,
autoreload=False
)
cfg = config.get_config()
try:
app.listen(port, host)
app.listen(
port,
host,
xheaders=cfg.tornado_xheaders
)
except OSError:
logger.warning('Address is used %s:%d', host, port)
return
finally:
url = 'http://localhost' if port == 80 else f'http://localhost:{port}'
url = 'http://localhost/' if port == 80 else f'http://localhost:{port}/'
# 防止更新版本后浏览器加载缓存
url += '?_v=' + update.VERSION
webbrowser.open(url)
logger.info('Server started: %s:%d', host, port)
tornado.ioloop.IOLoop.current().start()

View File

@@ -3,6 +3,7 @@
import asyncio
import datetime
import logging
import re
from typing import *
import aiohttp
@@ -20,9 +21,12 @@ _main_event_loop = asyncio.get_event_loop()
_http_session = aiohttp.ClientSession()
# user_id -> avatar_url
_avatar_url_cache: Dict[int, str] = {}
# (user_id, future)
_fetch_task_queue = asyncio.Queue(15)
_last_fetch_failed_time: Optional[datetime.datetime] = None
# 正在获取头像的Futureuser_id -> Future
_uid_fetch_future_map: Dict[int, asyncio.Future] = {}
# 正在获取头像的user_id队列
_uid_queue_to_fetch = asyncio.Queue(15)
# 上次被B站ban时间
_last_fetch_banned_time: Optional[datetime.datetime] = None
def init():
@@ -30,6 +34,13 @@ def init():
async def get_avatar_url(user_id):
avatar_url = await get_avatar_url_or_none(user_id)
if avatar_url is None:
avatar_url = DEFAULT_AVATAR_URL
return avatar_url
async def get_avatar_url_or_none(user_id):
avatar_url = get_avatar_url_from_memory(user_id)
if avatar_url is not None:
return avatar_url
@@ -60,10 +71,7 @@ def _do_get_avatar_url_from_database(user_id):
# 如果离上次更新太久就更新所有缓存
if (datetime.datetime.now() - user.update_time).days >= 3:
def refresh_cache():
try:
del _avatar_url_cache[user_id]
except KeyError:
pass
_avatar_url_cache.pop(user_id, None)
get_avatar_url_from_web(user_id)
_main_event_loop.call_soon(refresh_cache)
@@ -79,42 +87,46 @@ def _do_get_avatar_url_from_database(user_id):
return avatar_url
def get_avatar_url_from_web(user_id) -> Awaitable[str]:
future = _main_event_loop.create_future()
def get_avatar_url_from_web(user_id) -> Awaitable[Optional[str]]:
# 如果已有正在获取的future则返回防止重复获取同一个uid
future = _uid_fetch_future_map.get(user_id, None)
if future is not None:
return future
# 否则创建一个获取任务
_uid_fetch_future_map[user_id] = future = _main_event_loop.create_future()
future.add_done_callback(lambda _future: _uid_fetch_future_map.pop(user_id, None))
try:
_fetch_task_queue.put_nowait((user_id, future))
_uid_queue_to_fetch.put_nowait(user_id)
except asyncio.QueueFull:
future.set_result(DEFAULT_AVATAR_URL)
future.set_result(None)
return future
async def _get_avatar_url_from_web_consumer():
while True:
try:
user_id, future = await _fetch_task_queue.get()
# 先查缓存防止队列中出现相同uid时重复获取
avatar_url = get_avatar_url_from_memory(user_id)
if avatar_url is not None:
user_id = await _uid_queue_to_fetch.get()
future = _uid_fetch_future_map.get(user_id, None)
if future is None:
continue
# 防止在被ban的时候获取
global _last_fetch_failed_time
if _last_fetch_failed_time is not None:
global _last_fetch_banned_time
if _last_fetch_banned_time is not None:
cur_time = datetime.datetime.now()
if (cur_time - _last_fetch_failed_time).total_seconds() < 3 * 60 + 3:
# 3分钟以内被ban则先返回默认头像解封大约要15分钟
future.set_result(DEFAULT_AVATAR_URL)
if (cur_time - _last_fetch_banned_time).total_seconds() < 3 * 60 + 3:
# 3分钟以内被ban解封大约要15分钟
future.set_result(None)
continue
else:
_last_fetch_failed_time = None
_last_fetch_banned_time = None
asyncio.ensure_future(_get_avatar_url_from_web_coroutine(user_id, future))
# 限制频率防止被B站ban
await asyncio.sleep(0.2)
except:
pass
logger.exception('_get_avatar_url_from_web_consumer error:')
async def _get_avatar_url_from_web_coroutine(user_id, future):
@@ -122,8 +134,8 @@ async def _get_avatar_url_from_web_coroutine(user_id, future):
avatar_url = await _do_get_avatar_url_from_web(user_id)
except BaseException as e:
future.set_exception(e)
return
future.set_result(avatar_url)
else:
future.set_result(avatar_url)
async def _do_get_avatar_url_from_web(user_id):
@@ -134,18 +146,26 @@ async def _do_get_avatar_url_from_web(user_id):
logger.warning('Failed to fetch avatar: status=%d %s uid=%d', r.status, r.reason, user_id)
if r.status == 412:
# 被B站ban了
global _last_fetch_failed_time
_last_fetch_failed_time = datetime.datetime.now()
return DEFAULT_AVATAR_URL
global _last_fetch_banned_time
_last_fetch_banned_time = datetime.datetime.now()
return None
data = await r.json()
except (aiohttp.ClientConnectionError, asyncio.TimeoutError):
return DEFAULT_AVATAR_URL
return None
avatar_url = data['data']['face'].replace('http:', '').replace('https:', '')
avatar_url = process_avatar_url(data['data']['face'])
update_avatar_cache(user_id, avatar_url)
return avatar_url
def process_avatar_url(avatar_url):
# 去掉协议兼容HTTP、HTTPS
m = re.fullmatch(r'(?:https?:)?(.*)', avatar_url)
if m is not None:
avatar_url = m[1]
# 缩小图片加快传输
if not avatar_url.endswith('noface.gif'):
avatar_url += '@48w_48h'
update_avatar_cache(user_id, avatar_url)
return avatar_url
@@ -158,9 +178,8 @@ def update_avatar_cache(user_id, avatar_url):
def _update_avatar_cache_in_memory(user_id, avatar_url):
_avatar_url_cache[user_id] = avatar_url
if len(_avatar_url_cache) > 50000:
for _, key in zip(range(100), _avatar_url_cache):
del _avatar_url_cache[key]
while len(_avatar_url_cache) > 50000:
_avatar_url_cache.pop(next(iter(_avatar_url_cache)), None)
def _update_avatar_cache_in_database(user_id, avatar_url):
@@ -185,5 +204,5 @@ def _update_avatar_cache_in_database(user_id, avatar_url):
class BilibiliUser(models.database.OrmBase):
__tablename__ = 'bilibili_users'
uid = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
avatar_url = sqlalchemy.Column(sqlalchemy.Text)
avatar_url = sqlalchemy.Column(sqlalchemy.String(100))
update_time = sqlalchemy.Column(sqlalchemy.DateTime)

View File

@@ -16,7 +16,8 @@ DbSession: Optional[Type[sqlalchemy.orm.Session]] = None
def init(debug):
cfg = config.get_config()
global engine, DbSession
engine = sqlalchemy.create_engine(cfg.database_url, echo=debug)
# engine = sqlalchemy.create_engine(cfg.database_url, echo=debug)
engine = sqlalchemy.create_engine(cfg.database_url)
DbSession = sqlalchemy.orm.sessionmaker(bind=engine)
OrmBase.metadata.create_all(engine)

419
models/translate.py Normal file
View File

@@ -0,0 +1,419 @@
# -*- coding: utf-8 -*-
import asyncio
import functools
import hashlib
import logging
import random
import re
import time
import yarl
from typing import *
import aiohttp
logger = logging.getLogger(__name__)
NO_TRANSLATE_TEXTS = {
'', '草草', '草草草', '草生', '大草原', '上手', '上手上手', '理解', '理解理解', '天才', '天才天才',
'', '余裕', '余裕余裕', '大丈夫', '再放送', '放送事故', '清楚', '清楚清楚'
}
_main_event_loop = asyncio.get_event_loop()
_http_session = aiohttp.ClientSession()
_translate_providers: List['TranslateProvider'] = []
# text -> res
_translate_cache: Dict[str, str] = {}
# 正在翻译的Futuretext -> Future
_text_future_map: Dict[str, asyncio.Future] = {}
def init():
asyncio.ensure_future(_do_init())
async def _do_init():
# 考虑优先级
providers = [
TencentTranslate(),
YoudaoTranslate(),
BilibiliTranslate()
]
await asyncio.gather(*(provider.init() for provider in providers))
global _translate_providers
_translate_providers = providers
def need_translate(text):
text = text.strip()
# 没有中文,平时打不出的字不管
if not any(0x4E00 <= ord(c) <= 0x9FFF for c in text):
return False
# 含有日文假名
if any(0x3040 <= ord(c) <= 0x30FF for c in text):
return False
# 弹幕同传
if text.startswith(''):
return False
# 中日双语
if text in NO_TRANSLATE_TEXTS:
return False
return True
def get_translation_from_cache(text):
key = text.strip().lower()
return _translate_cache.get(key, None)
def translate(text) -> Awaitable[Optional[str]]:
key = text.strip().lower()
# 如果已有正在翻译的future则返回防止重复翻译
future = _text_future_map.get(key, None)
if future is not None:
return future
# 否则创建一个翻译任务
future = _main_event_loop.create_future()
# 查缓存
res = _translate_cache.get(key, None)
if res is not None:
future.set_result(res)
return future
for provider in _translate_providers:
if provider.is_available:
_text_future_map[key] = future
future.add_done_callback(functools.partial(_on_translate_done, key))
provider.translate(text, future)
return future
future.set_result(None)
return future
def _on_translate_done(key, future):
_text_future_map.pop(key, None)
# 缓存
try:
res = future.result()
except:
return
if res is None:
return
_translate_cache[key] = res
while len(_translate_cache) > 50000:
_translate_cache.pop(next(iter(_translate_cache)), None)
class TranslateProvider:
async def init(self):
return True
@property
def is_available(self):
return True
def translate(self, text, future):
raise NotImplementedError
class TencentTranslate(TranslateProvider):
def __init__(self):
# 过期时间1小时
self._qtv = ''
self._qtk = ''
self._reinit_future = None
# 连续失败的次数
self._fail_count = 0
self._cool_down_future = None
async def init(self):
self._reinit_future = asyncio.ensure_future(self._reinit_coroutine())
return await self._do_init()
async def _do_init(self):
try:
async with _http_session.get('https://fanyi.qq.com/') as r:
if r.status != 200:
logger.warning('TencentTranslate init request failed: status=%d %s', r.status, r.reason)
return False
html = await r.text()
except (aiohttp.ClientConnectionError, asyncio.TimeoutError):
logger.exception('TencentTranslate init error:')
return False
m = re.search(r"""\bqtv\s*=\s*['"](.+?)['"]""", html)
if m is None:
logger.exception('TencentTranslate init failed: qtv not found')
return False
qtv = m[1]
m = re.search(r"""\bqtk\s*=\s*['"](.+?)['"]""", html)
if m is None:
logger.exception('TencentTranslate init failed: qtk not found')
return False
qtk = m[1]
self._qtv = qtv
self._qtk = qtk
return True
async def _reinit_coroutine(self):
try:
while True:
await asyncio.sleep(55 * 60)
while True:
logger.info('TencentTranslate reinit')
try:
if await self._do_init():
break
except:
logger.exception('TencentTranslate init error:')
await asyncio.sleep(3 * 60)
except asyncio.CancelledError:
pass
@property
def is_available(self):
return self._qtv != '' and self._qtk != ''
def translate(self, text, future):
asyncio.ensure_future(self._translate_coroutine(text, future))
async def _translate_coroutine(self, text, future):
try:
res = await self._do_translate(text)
except BaseException as e:
future.set_exception(e)
self._on_fail()
return
future.set_result(res)
if res is None:
self._on_fail()
else:
self._fail_count = 0
async def _do_translate(self, text):
try:
async with _http_session.post(
'https://fanyi.qq.com/api/translate',
headers={
'Referer': 'https://fanyi.qq.com/'
},
data={
'source': 'zh',
'target': 'jp',
'sourceText': text,
'qtv': self._qtv,
'qtk': self._qtk
}
) as r:
if r.status != 200:
logger.warning('TencentTranslate request failed: status=%d %s', r.status, r.reason)
return None
data = await r.json()
except (aiohttp.ClientConnectionError, asyncio.TimeoutError):
return None
if data['errCode'] != 0:
logger.warning('TencentTranslate failed: %d %s', data['errCode'], data['errMsg'])
return None
res = ''.join(record['targetText'] for record in data['translate']['records'])
if res == '' and text.strip() != '':
# qtv、qtk过期
logger.warning('TencentTranslate result is empty %s', data)
return None
return res
def _on_fail(self):
self._fail_count += 1
# 目前没有测试出被ban的情况为了可靠性连续失败20次时冷却并重新init
if self._fail_count >= 20 and self._cool_down_future is None:
self._cool_down_future = asyncio.ensure_future(self._cool_down())
async def _cool_down(self):
logger.warning('TencentTranslate is cooling down')
self._qtv = self._qtk = ''
try:
while True:
await asyncio.sleep(3 * 60)
logger.info('TencentTranslate reinit')
try:
if await self._do_init():
self._fail_count = 0
break
except:
logger.exception('TencentTranslate init error:')
finally:
logger.warning('TencentTranslate finished cooling down')
self._cool_down_future = None
class YoudaoTranslate(TranslateProvider):
def __init__(self):
self._has_init = False
self._cool_down_future = None
async def init(self):
# 获取cookie
try:
async with _http_session.get('http://fanyi.youdao.com/') as r:
if r.status >= 400:
logger.warning('YoudaoTranslate init request failed: status=%d %s', r.status, r.reason)
return False
except (aiohttp.ClientConnectionError, asyncio.TimeoutError):
return False
cookies = _http_session.cookie_jar.filter_cookies(yarl.URL('http://fanyi.youdao.com/'))
res = 'JSESSIONID' in cookies and 'OUTFOX_SEARCH_USER_ID' in cookies
if res:
self._has_init = True
return res
@property
def is_available(self):
return self._has_init
def translate(self, text, future):
asyncio.ensure_future(self._translate_coroutine(text, future))
async def _translate_coroutine(self, text, future):
try:
res = await self._do_translate(text)
except BaseException as e:
future.set_exception(e)
else:
future.set_result(res)
async def _do_translate(self, text):
try:
async with _http_session.post(
'http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule',
headers={
'Referer': 'http://fanyi.youdao.com/'
},
data={
'i': text,
'from': 'zh-CHS',
'to': 'ja',
'smartresult': 'dict',
'client': 'fanyideskweb',
**self._generate_salt(text),
'doctype': 'json',
'version': '2.1',
'keyfrom': 'fanyi.web',
'action': 'FY_BY_REALTlME'
}
) as r:
if r.status != 200:
logger.warning('YoudaoTranslate request failed: status=%d %s', r.status, r.reason)
return None
data = await r.json()
except (aiohttp.ClientConnectionError, asyncio.TimeoutError):
return None
except aiohttp.ContentTypeError:
# 被ban了
if self._cool_down_future is None:
self._cool_down_future = asyncio.ensure_future(self._cool_down())
return None
if data['errorCode'] != 0:
logger.warning('YoudaoTranslate failed: %d', data['errorCode'])
return None
res = []
for outer_result in data['translateResult']:
for inner_result in outer_result:
res.append(inner_result['tgt'])
return ''.join(res)
@staticmethod
def _generate_salt(text):
timestamp = int(time.time() * 1000)
salt = f'{timestamp}{random.randint(0, 9)}'
md5 = hashlib.md5()
md5.update(f'fanyideskweb{text}{salt}n%A-rKaT5fb[Gy?;N5@Tj'.encode())
sign = md5.hexdigest()
return {
'ts': timestamp,
'bv': '7bcd9ea3ff9b319782c2a557acee9179', # md5(navigator.appVersion)
'salt': salt,
'sign': sign
}
async def _cool_down(self):
logger.warning('YoudaoTranslate is cooling down')
self._has_init = False
try:
while True:
await asyncio.sleep(3 * 60)
try:
is_success = await self.init()
except:
logger.exception('YoudaoTranslate init error:')
continue
if is_success:
break
finally:
logger.warning('YoudaoTranslate finished cooling down')
self._cool_down_future = None
# 目前B站后端是百度翻译
class BilibiliTranslate(TranslateProvider):
def __init__(self):
# 最长等待时间大约21秒(text, future)
self._text_queue = asyncio.Queue(7)
async def init(self):
asyncio.ensure_future(self._translate_consumer())
return True
@property
def is_available(self):
return not self._text_queue.full()
def translate(self, text, future):
try:
self._text_queue.put_nowait((text, future))
except asyncio.QueueFull:
future.set_result(None)
async def _translate_consumer(self):
while True:
try:
text, future = await self._text_queue.get()
asyncio.ensure_future(self._translate_coroutine(text, future))
# 频率限制一分钟20次
await asyncio.sleep(3.1)
except:
logger.exception('BilibiliTranslate error:')
async def _translate_coroutine(self, text, future):
try:
res = await self._do_translate(text)
except BaseException as e:
future.set_exception(e)
else:
future.set_result(res)
@staticmethod
async def _do_translate(text):
try:
async with _http_session.get(
'https://api.live.bilibili.com/av/v1/SuperChat/messageTranslate',
params={
'parent_area_id': '1',
'area_id': '199',
'msg': text
}
) as r:
if r.status != 200:
logger.warning('BilibiliTranslate request failed: status=%d %s', r.status, r.reason)
return None
data = await r.json()
except (aiohttp.ClientConnectionError, asyncio.TimeoutError):
return None
if data['code'] != 0:
logger.warning('BilibiliTranslate failed: %d %s', data['code'], data['msg'])
return None
return data['data']['message_trans']

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 159 KiB

View File

@@ -4,7 +4,7 @@ import asyncio
import aiohttp
VERSION = 'v1.3.0'
VERSION = 'v1.5.0'
def check_update():
@@ -17,8 +17,10 @@ async def _do_check_update():
async with session.get('https://api.github.com/repos/xfgryujk/blivechat/releases/latest') as r:
data = await r.json()
if data['name'] != VERSION:
print('---------------------------------------------')
print('New version available:', data['name'])
print(data['body'])
print('Download:', data['html_url'])
print('---------------------------------------------')
except aiohttp.ClientConnectionError:
print('Failed to check update: connection failed')