Skip to content

Commit

Permalink
Merge branch 'xfgryujk:dev' into kuma_dom_only
Browse files Browse the repository at this point in the history
  • Loading branch information
UncleChair authored Nov 22, 2024
2 parents d1d90f1 + 4b9c01b commit ab40bc7
Show file tree
Hide file tree
Showing 44 changed files with 661 additions and 134 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,4 @@ venv.bak/
.idea/
data/
log/
.vercel
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ RUN npm run build
# 准备后端
#

FROM python:3.8.12-bullseye
FROM python:3.12.7-bookworm
ARG BASE_PATH='/root/blivechat'
ARG EXT_DATA_PATH='/mnt/data'
WORKDIR "${BASE_PATH}"
Expand Down
53 changes: 31 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,30 @@

以下几种方式任选一种即可。**正式使用之前记得看[注意事项](https://github.com/xfgryujk/blivechat/wiki/%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9%E5%92%8C%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)**

推荐的方式:如果你需要使用插件、翻译等高级特性,则在本地使用;否则推荐直接通过公共服务器在线使用。因为本地使用时不会自动升级版本,有时候出了问题不能及时解决;但公共服务器会禁用部分高级特性,如果你有需要,只能本地使用了
推荐的方式:如果你需要使用插件、翻译等高级特性,则在本地使用;否则推荐直接通过公共服务器在线使用。因为本地使用时不会自动升级版本,有时候出了问题不能及时解决;但在线使用时会禁用部分高级特性,如果你有需要,只能本地使用了

### 一、本地使用
### 一、在线使用

1. 下载[本地分发版](https://github.com/xfgryujk/blivechat/releases)(仅提供x64 Windows版)。也可以在[B站商店](https://play-live.bilibili.com/details/1694397161340)下载
2. 双击`blivechat.exe`运行服务器。或者用命令行可以指定host和端口号:
1. 这些是作者维护的公共服务器,根据情况随便选一个,直接用浏览器打开

```sh
blivechat.exe --host 127.0.0.1 --port 12450
```
* [blive.chat](https://blive.chat/):自动节点,一般等于cn.blive.chat,如果不可用了会进行切换,但切换需要一段时间
* [cn.blive.chat](https://cn.blive.chat/):墙内专用节点,不容易被墙,但如果受到攻击会变得不可用
* [cloudflare.blive.chat](https://cloudflare.blive.chat/):Cloudflare美国节点,不容易被攻击,但容易被墙
* [vercel.blive.chat](https://vercel.blive.chat/):Vercel美国节点,不容易被攻击,但容易被墙

或者也可以在配置文件里指定host和端口号
2. 输入主播在开始直播时获得的身份码,复制房间URL
3. 用样式生成器生成样式,复制CSS
4. 在OBS中添加浏览器源,输入URL和自定义CSS

3. 用浏览器打开[http://localhost:12450](http://localhost:12450),输入主播在开始直播时获得的身份码,复制房间URL
4. 用样式生成器生成样式,复制CSS
5. 在OBS中添加浏览器源,输入URL和自定义CSS
### 二、本地使用

### 二、公共服务器
1. 下载[本地分发版](https://github.com/xfgryujk/blivechat/releases)(仅提供x64 Windows版)。也可以在[B站商店](https://play-live.bilibili.com/details/1694397161340)下载
2. 双击`blivechat.exe`(或者`start.exe`)运行服务器
3. 用浏览器打开[http://localhost:12450](http://localhost:12450),剩下的步骤和在线使用时是一样的

直接用浏览器打开[公共服务器](http://chat.bilisc.com/),剩下的步骤和本地使用时是一样的
### 三、从源码运行

### 三、源代码版(自建服务器或在Windows以外平台)
此方式适用于自建服务器或者在Windows以外的平台运行

0. 由于使用了git子模块,clone时需要加上`--recursive`参数:

Expand All @@ -67,22 +69,29 @@
npm run build
```

2. 运行服务器(需要Python3.8以上版本):
2. 安装服务器依赖(需要Python 3.12以上版本):

```sh
pip3 install -r requirements.txt
python3 main.py
pip install -r requirements.txt
```

3. 运行服务器:

```sh
python main.py
```

或者可以指定host和端口号:

```sh
python3 main.py --host 127.0.0.1 --port 12450
python main.py --host 127.0.0.1 --port 12450
```

3. 用浏览器打开[http://localhost:12450](http://localhost:12450),以下略
4. 用浏览器打开[http://localhost:12450](http://localhost:12450),以下略

### 四、Docker

### 四、Docker(自建服务器)
此方式适用于自建服务器。示例的运行参数只是最基本的,可以根据需要修改

1. ```sh
docker run --name blivechat -d -p 12450:12450 \
Expand All @@ -94,9 +103,9 @@

## 服务器配置

服务器配置在`data/config.ini`,可以配置数据库和允许自动翻译等,编辑后要重启生效
服务器配置文件在`data/config.ini`,可以配置数据库和允许自动翻译等,编辑后要重启生效

**自建服务器时强烈建议不使用加载器**否则可能因为混合HTTP和HTTPS等原因加载不出来
**自建服务器时强烈建议不使用加载器**否则可能因为各种原因加载不出来

## 常用链接

Expand Down
21 changes: 20 additions & 1 deletion api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,37 @@

import tornado.web

import config


class ApiHandler(tornado.web.RequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.json_args: Optional[dict] = None

def prepare(self):
def set_default_headers(self):
self.set_header('Cache-Control', 'no-cache')

self.add_header('Vary', 'Origin')
origin = self.request.headers.get('Origin', None)
if origin is None:
return
cfg = config.get_config()
if not cfg.is_allowed_cors_origin(origin):
return

self.set_header('Access-Control-Allow-Origin', origin)
self.set_header('Access-Control-Allow-Methods', '*')
self.set_header('Access-Control-Allow-Headers', '*')
self.set_header('Access-Control-Max-Age', '3600')

def prepare(self):
if not self.request.headers.get('Content-Type', '').startswith('application/json'):
return
try:
self.json_args = json.loads(self.request.body)
except json.JSONDecodeError:
pass

async def options(self, *_args, **_kwargs):
self.set_status(204)
9 changes: 5 additions & 4 deletions api/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,12 +223,13 @@ def _on_join_room_req(self, body: dict):

self._refresh_receive_timeout_timer()

# 跨域测试用
def check_origin(self, origin):
cfg = config.get_config()
if cfg.debug:
return True
return super().check_origin(origin)
return (
cfg.debug # 开发时前端localhost直连
or cfg.is_allowed_cors_origin(origin)
or super().check_origin(origin) # 和Host相同
)

@property
def has_joined_room(self):
Expand Down
19 changes: 17 additions & 2 deletions api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
CUSTOM_PUBLIC_PATH = os.path.join(config.DATA_PATH, 'custom_public')


class MainHandler(tornado.web.StaticFileHandler):
class StaticHandler(tornado.web.StaticFileHandler):
"""为了使用Vue Router的history模式,把不存在的文件请求转发到index.html"""
async def get(self, path, include_body=True):
if path == '':
Expand Down Expand Up @@ -52,6 +52,19 @@ async def get(self):
})


class ServiceDiscoveryHandler(api.base.ApiHandler):
async def get(self):
cfg = config.get_config()
self.write({
'endpoints': cfg.registered_endpoints,
})


class PingHandler(api.base.ApiHandler):
async def get(self):
self.set_status(204)


class UploadEmoticonHandler(api.base.ApiHandler):
async def post(self):
cfg = config.get_config()
Expand Down Expand Up @@ -94,12 +107,14 @@ def set_extra_headers(self, path):

ROUTES = [
(r'/api/server_info', ServerInfoHandler),
(r'/api/endpoints', ServiceDiscoveryHandler),
(r'/api/ping', PingHandler),
(r'/api/emoticon', UploadEmoticonHandler),
]
# 通配的放在最后
LAST_ROUTES = [
(rf'{EMOTICON_BASE_URL}/(.*)', tornado.web.StaticFileHandler, {'path': EMOTICON_UPLOAD_PATH}),
# 这个目录不保证文件内容不会变,还是不用缓存了
(r'/custom_public/(.*)', NoCacheStaticFileHandler, {'path': CUSTOM_PUBLIC_PATH}),
(r'/(.*)', MainHandler, {'path': config.WEB_ROOT}),
(r'/(.*)', StaticHandler, {'path': config.WEB_ROOT}),
]
53 changes: 32 additions & 21 deletions api/open_live.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,9 @@
GAME_HEARTBEAT_OPEN_LIVE_URL = OPEN_LIVE_BASE_URL + '/v2/app/heartbeat'
GAME_BATCH_HEARTBEAT_OPEN_LIVE_URL = OPEN_LIVE_BASE_URL + '/v2/app/batchHeartbeat'

COMMON_SERVER_BASE_URL = 'https://chat.bilisc.com'
START_GAME_COMMON_SERVER_URL = COMMON_SERVER_BASE_URL + '/api/internal/open_live/start_game'
END_GAME_COMMON_SERVER_URL = COMMON_SERVER_BASE_URL + '/api/internal/open_live/end_game'
GAME_HEARTBEAT_COMMON_SERVER_URL = COMMON_SERVER_BASE_URL + '/api/internal/open_live/game_heartbeat'
START_GAME_COMMON_SERVER_URL = '/api/internal/open_live/start_game'
END_GAME_COMMON_SERVER_URL = '/api/internal/open_live/end_game'
GAME_HEARTBEAT_COMMON_SERVER_URL = '/api/internal/open_live/game_heartbeat'

_error_auth_code_cache = cachetools.LRUCache(256)
# 应B站要求,抓一下刷请求的人,不会用于其他用途
Expand All @@ -54,24 +53,15 @@ def code(self) -> int:
return self.data['code']


async def request_open_live_or_common_server(open_live_url, common_server_url, body: dict) -> dict:
async def request_open_live_or_common_server(open_live_url, common_server_url, body: dict, **kwargs) -> dict:
"""如果配置了开放平台,则直接请求,否则转发请求到公共服务器的内部接口"""
cfg = config.get_config()
if cfg.is_open_live_configured:
return await request_open_live(open_live_url, body)

try:
req_ctx_mgr = utils.request.http_session.post(common_server_url, json=body)
return await _read_response(req_ctx_mgr, is_common_server=True)
except TransportError:
logger.exception('Request common server failed:')
raise
except BusinessError as e:
logger.warning('Request common server failed: %s', e)
raise
return await request_open_live(open_live_url, body, **kwargs)
return await request_common_server(common_server_url, body, **kwargs)


async def request_open_live(url, body: dict, *, ignore_rate_limit=False) -> dict:
async def request_open_live(url, body: dict, *, ignore_rate_limit=False, **kwargs) -> dict:
cfg = config.get_config()
assert cfg.is_open_live_configured

Expand Down Expand Up @@ -109,7 +99,7 @@ async def request_open_live(url, body: dict, *, ignore_rate_limit=False) -> dict
headers['Accept'] = 'application/json'

try:
req_ctx_mgr = utils.request.http_session.post(url, headers=headers, data=body_bytes)
req_ctx_mgr = utils.request.http_session.post(url, headers=headers, data=body_bytes, **kwargs)
return await _read_response(req_ctx_mgr)
except TransportError:
logger.exception('Request open live failed:')
Expand All @@ -126,6 +116,25 @@ async def request_open_live(url, body: dict, *, ignore_rate_limit=False) -> dict
raise


async def request_common_server(rel_url, body: dict, **kwargs) -> dict:
base_url, breaker = utils.request.get_common_server_base_url_and_circuit_breaker()
if base_url is None:
logger.error('No available common server endpoint')
raise TransportError('No available common server endpoint')
url = base_url + rel_url

with breaker:
try:
req_ctx_mgr = utils.request.http_session.post(url, json=body, **kwargs)
return await _read_response(req_ctx_mgr, is_common_server=True)
except TransportError:
logger.exception('Request common server failed:')
raise
except BusinessError as e:
logger.warning('Request common server failed: %s', e)
raise


async def _read_response(req_ctx_mgr: AsyncContextManager[aiohttp.ClientResponse], is_common_server=False) -> dict:
try:
async with req_ctx_mgr as r:
Expand Down Expand Up @@ -169,6 +178,9 @@ def __init__(self, *args, **kwargs):

def prepare(self):
super().prepare()
if self.request.method == 'OPTIONS':
return

if not isinstance(self.json_args, dict):
raise tornado.web.MissingArgumentError('body')

Expand Down Expand Up @@ -295,9 +307,8 @@ async def send_game_heartbeat_by_service_or_common_server(game_id):
cfg = config.get_config()
if cfg.is_open_live_configured:
return await services.open_live.send_game_heartbeat(game_id)
# 这里GAME_HEARTBEAT_OPEN_LIVE_URL没用,因为一定是请求公共服务器
return await request_open_live_or_common_server(
GAME_HEARTBEAT_OPEN_LIVE_URL, GAME_HEARTBEAT_COMMON_SERVER_URL, {'game_id': game_id}
return await request_common_server(
GAME_HEARTBEAT_COMMON_SERVER_URL, {'game_id': game_id}, timeout=aiohttp.ClientTimeout(total=15)
)


Expand Down
3 changes: 2 additions & 1 deletion api/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ def prepare(self):
if not cfg.enable_admin_plugins:
raise tornado.web.HTTPError(403)

logger.info('client=%s requesting admin plugin, cls=%s', self.request.remote_ip, type(self).__name__)
if self.request.method != 'OPTIONS':
logger.info('client=%s requesting admin plugin, cls=%s', self.request.remote_ip, type(self).__name__)

super().prepare()

Expand Down
2 changes: 1 addition & 1 deletion blivedm
Loading

0 comments on commit ab40bc7

Please sign in to comment.