diff --git a/app/history/page.tsx b/app/history/page.tsx
index 8cdbf32..a2a5f8d 100644
--- a/app/history/page.tsx
+++ b/app/history/page.tsx
@@ -84,7 +84,7 @@ export default function Home() {
display: 'flex',
// justifyContent: 'center',
padding: '6px'
- }}>
录播管理
>}
+ }}>历史记录
>}
mode="horizontal"
>
diff --git a/app/job/page.tsx b/app/job/page.tsx
index deeb280..2761288 100644
--- a/app/job/page.tsx
+++ b/app/job/page.tsx
@@ -1,6 +1,7 @@
'use client'
-import {AutoComplete, Layout, Nav, Spin, Table, Typography} from "@douyinfe/semi-ui";
+import { AutoComplete, Layout, Nav, Spin, Table, Typography } from "@douyinfe/semi-ui";
+import { SortOrder } from "@douyinfe/semi-ui/lib/es/table/interface";
import useSWR from "swr";
import {fetcher, FileList} from "@/app/lib/api-streamer";
import {
@@ -17,7 +18,8 @@ export default function Home() {
if (isLoading) {
return ();
}
- const {Text} = Typography;
+ const { Text } = Typography;
+ const ascendSort: SortOrder = "ascend";
const columns = [
{
title: '名称',
@@ -47,6 +49,7 @@ export default function Home() {
{
title: '更新日期',
dataIndex: 'date',
+ defaultSortOrder: ascendSort,
render: (text: any, record: any, index: any) => {
return (<>{humDate(text)}>);
},
@@ -54,9 +57,16 @@ export default function Home() {
},
];
const expandRowRender = (record: any, index: number | undefined) => {
- return <>文件列表:{record.files.map((it: { id: Key | null | undefined; file: string | number | boolean | ReactElement> | Iterable | ReactPortal | null | undefined; }) => {
- return 文件名:{it.file}
;
- })}>;
+ return <>文件列表:{record.files.map(
+ (it:
+ {
+ id: Key | null | undefined;
+ file: string | number | boolean | ReactElement> | Iterable | ReactPortal | null | undefined;
+ }
+ ) => {
+ return 文件名:{it.file}
;
+ }
+ )}>;
};
return (<>
diff --git a/app/ui/UserList.tsx b/app/ui/UserList.tsx
index fdae0ca..afcd4c4 100644
--- a/app/ui/UserList.tsx
+++ b/app/ui/UserList.tsx
@@ -42,9 +42,10 @@ const UserList: React.FC = ({onCancel, visible}) => {
setVisible(false);
Toast.success('创建成功');
} catch (e: any) {
+ const messageObj = JSON.parse(e.message);
return Notification.error({
title: '创建失败',
- content: {e.message},
+ content: {messageObj.error},
// theme: 'light',
// duration: 0,
style: {width: 'min-content'}
diff --git a/app/ui/plugins/bilibili.tsx b/app/ui/plugins/bilibili.tsx
index 59bc105..6e3a58a 100644
--- a/app/ui/plugins/bilibili.tsx
+++ b/app/ui/plugins/bilibili.tsx
@@ -112,7 +112,7 @@ const Bilibili: React.FC = (props) => {
field="bili_fallback_api"
extraText={
- 上方的主要API不可用时,回退使用的 API。默认使用官方 API。
+ 上方的主要 API 不可用或受到区域限制时,回退使用的 API。默认使用官方 API。
海外机器玩法:哔哩哔哩直播API(bili_liveapi)设置为能获取大陆直播流的
API,并将哔哩哔哩直播回退API(bili_fallback_api)设置为官方
@@ -130,10 +130,10 @@ const Bilibili: React.FC
= (props) => {
padding: 0,
}}
/>
- = (props) => {
padding: 0,
}}
/>
+
+
>
);
diff --git a/app/ui/plugins/global.tsx b/app/ui/plugins/global.tsx
index c2d7d51..224fa57 100644
--- a/app/ui/plugins/global.tsx
+++ b/app/ui/plugins/global.tsx
@@ -225,13 +225,15 @@ const Global: React.FC = () => {
+ AUTO(自动,默认)
bda2
kodo
ws
diff --git a/biliup/__init__.py b/biliup/__init__.py
index 020ce07..785fd45 100644
--- a/biliup/__init__.py
+++ b/biliup/__init__.py
@@ -30,7 +30,8 @@ LOG_CONF = {
'interval': 1,
'backupCount': 1,
'filename': 'ds_update.log',
- 'formatter': 'verbose'
+ 'formatter': 'verbose',
+ 'encoding': 'utf-8'
}
},
'root': {
diff --git a/biliup/engine/download.py b/biliup/engine/download.py
index af255c1..a5cc1ae 100644
--- a/biliup/engine/download.py
+++ b/biliup/engine/download.py
@@ -4,6 +4,7 @@ import re
import subprocess
import sys
import time
+import shutil
from typing import Generator, List
from urllib.parse import urlparse
@@ -108,13 +109,17 @@ class DownloadBase:
self.danmaku_download_start(fmtname)
parsed_url_path = urlparse(self.raw_stream_url).path
- if self.downloader == 'streamlink':
- if '.flv' in parsed_url_path: # streamlink无法处理flv,所以回退到ffmpeg
+ if shutil.which("ffmpeg"):
+ if self.downloader == 'streamlink':
+ if '.flv' in parsed_url_path: # streamlink无法处理flv,所以回退到ffmpeg
+ return self.ffmpeg_download(fmtname)
+ else:
+ return self.streamlink_download(fmtname)
+ elif self.downloader == 'ffmpeg':
return self.ffmpeg_download(fmtname)
- else:
- return self.streamlink_download(fmtname)
- elif self.downloader == 'ffmpeg':
- return self.ffmpeg_download(fmtname)
+ else:
+ logger.error("未安装 FFMpeg 或不存在于 PATH 内,本次下载使用 stream-gears")
+ logger.debug("Current user's PATH is:" + os.getenv("PATH"))
if not self.suffix in ['flv', 'ts']:
self.suffix = 'flv' if '.flv' in parsed_url_path else 'ts'
@@ -338,8 +343,11 @@ class DownloadBase:
logger.exception(f'封面下载失败:{self.__class__.__name__} - {self.fname}')
def check_url_healthy(self, http_session, url):
+ timeout = 5
try:
- r = http_session.get(url, stream=True, timeout=5, allow_redirects=False)
+ if 'flv' in url:
+ timeout = 60
+ r = http_session.get(url, stream=True, timeout=timeout, allow_redirects=False)
if 'm3u8' in url:
import m3u8
m3u8_obj = m3u8.loads(r.text)
@@ -350,7 +358,7 @@ class DownloadBase:
elif r.headers.get('Location', False):
url = r.headers['Location']
logger.info(f'stream url: {url}')
- r = http_session.get(url, stream=True, timeout=5)
+ r = http_session.get(url, stream=True, timeout=timeout)
if r.status_code == 200:
return True, url
except Exception as e:
diff --git a/biliup/engine/event.py b/biliup/engine/event.py
index bfeaabc..50766d0 100644
--- a/biliup/engine/event.py
+++ b/biliup/engine/event.py
@@ -167,4 +167,4 @@ class Event:
"""事件对象"""
type_: str # 事件类型
args: tuple = ()
- dict: dict = field(default_factory=dict) # 字典用于保存具体的事件数据
+ dict: dict = field(default_factory=dict) # type: ignore # 字典用于保存具体的事件数据
diff --git a/biliup/handler.py b/biliup/handler.py
index 4f86573..6fe25e3 100644
--- a/biliup/handler.py
+++ b/biliup/handler.py
@@ -39,12 +39,12 @@ def singleton_check(platform, name, url):
yield Event(PRE_DOWNLOAD, args=(name, turl,))
return
if context['PluginInfo'].url_status[url] == 1:
- logger.debug(f'{url}正在下载中,跳过检测')
+ logger.debug(f'{url} 正在下载中,跳过检测')
return
# 可能对同一个url同时发送两次上传事件
with NamedLock(f"upload_count_{url}"):
if context['url_upload_count'][url] > 0:
- logger.debug(f'{url}正在上传中,跳过')
+ logger.debug(f'{url} 正在上传中,跳过')
return
# from .handler import event_manager, UPLOAD
# += 不是原子操作
@@ -59,9 +59,9 @@ def singleton_check(platform, name, url):
@event_manager.register(PRE_DOWNLOAD, block='Asynchronous1')
def pre_processor(name, url):
if context['PluginInfo'].url_status[url] == 1:
- logger.debug(f'{name}-正在下载中,跳过下载')
+ logger.debug(f'{name} 正在下载中,跳过下载')
return
- logger.info(f'{name}-{url}-开播了准备下载')
+ logger.info(f'{name} - {url} 开播了准备下载')
preprocessor = config['streamers'].get(name, {}).get('preprocessor')
if preprocessor:
processor(preprocessor, json.dumps({
@@ -94,7 +94,6 @@ def process(name, url):
finally:
# 下载结束
url_status[url] = 0
-
@event_manager.register(DOWNLOADED, block='Asynchronous1')
def processed(stream_info):
diff --git a/biliup/plugins/bili_webup.py b/biliup/plugins/bili_webup.py
index f1ae2ee..6d2db54 100644
--- a/biliup/plugins/bili_webup.py
+++ b/biliup/plugins/bili_webup.py
@@ -35,6 +35,8 @@ class BiliWeb(UploadBase):
super().__init__(principal, data, persistence_path='bili.cookie', postprocessor=postprocessor)
if tags is None:
tags = []
+ else:
+ tags = [str(tag).format(streamer=self.data['name']) for tag in tags]
self.user = user
self.lines = lines
self.submit_api = submit_api
diff --git a/biliup/plugins/bilibili.py b/biliup/plugins/bilibili.py
index 960bc99..ffd5d1a 100644
--- a/biliup/plugins/bilibili.py
+++ b/biliup/plugins/bilibili.py
@@ -1,6 +1,7 @@
import time
import requests
import json
+import re
from biliup.config import config
from . import match1, logger
@@ -54,11 +55,10 @@ class Bililive(DownloadBase):
_res = do_login(s)
user_data = _res.get('data', {})
is_login = user_data.get('isLogin', False)
- if not is_login:
- # logger.warning(f"{plugin_msg}: 登录态校验失败")
- logger.warning("登录态校验失败: " + str(_res))
- else:
+ if is_login:
logger.info(f"用户名:{user_data['uname']}, mid:{user_data['mid']}, isLogin:{is_login}")
+ else:
+ logger.warning("登录态校验失败: " + str(_res))
return True
protocol = config.get('bili_protocol', 'stream')
@@ -68,6 +68,8 @@ class Bililive(DownloadBase):
ov05_ip = config.get('bili_force_ov05_ip')
main_api = config.get('bili_liveapi', OFFICIAL_API).rstrip('/')
fallback_api = config.get('bili_fallback_api', OFFICIAL_API).rstrip('/')
+ cn01_sids = config.get('bili_replace_cn01_sid', '').split(',')
+ normalize_cn204 = config.get('bili_normalize_cn204', False)
params = {
'room_id': room_id,
@@ -86,13 +88,14 @@ class Bililive(DownloadBase):
if self.raw_stream_url is not None \
and qualityNumber >= 10000 \
and not is_new_live:
- # 同一个 streamName 即可复用,与其他参数无关,目前没有报告说链接会超时失效。
+ # 同一个 streamName 即可复用,除非被超管切断
# 前面拿不到 streamName,目前使用开播时间判断
health, url = super().check_url_healthy(s, self.raw_stream_url)
if health:
logger.debug(f"{plugin_msg}: 复用 {url}")
return True
else:
+ logger.debug(f"{plugin_msg}: health-{health}; url-{self.raw_stream_url}")
self.raw_stream_url = None
try:
@@ -146,16 +149,19 @@ class Bililive(DownloadBase):
streamName = match1(stream_url['base_url'], streamname_regexp)
if streamName is not None and force_source and qualityNumber >= 10000:
new_base_url = stream_url['base_url'].replace(f"_{streamName.split('_')[-1]}", '')
- if super().check_url_healthy(s, f"{stream_url['host']}{new_base_url}{stream_url['extra']}"):
+ if super().check_url_healthy(s, f"{stream_url['host']}{new_base_url}{stream_url['extra']}")[0]:
stream_url['base_url'] = new_base_url
logger.debug(stream_url['base_url'])
self.raw_stream_url = f"{stream_url['host']}{stream_url['base_url']}{stream_url['extra']}"
+ if normalize_cn204:
+ self.raw_stream_url = re.sub(r"(?<=cn-gotcha204)-[1-4]", "", self.raw_stream_url, 1)
+
if ov05_ip and "ov-gotcha05" in stream_url['host']:
self.raw_stream_url = oversea_expand(s, self.raw_stream_url, ov05_ip)
- url_health, url = super().check_url_healthy(s, self.raw_stream_url)
+ url_health, _url = super().check_url_healthy(s, self.raw_stream_url)
if not url_health:
if cdn_fallback:
i = len(stream_info['url_info'])
@@ -163,12 +169,15 @@ class Bililive(DownloadBase):
i -= 1
try:
self.raw_stream_url = "{}{}{}".format(stream_info['url_info'][i]['host'], stream_url['base_url'], stream_info['url_info'][i]['extra'])
- url_health, url = super().check_url_healthy(s, self.raw_stream_url)
+ url_health, _url = super().check_url_healthy(s, self.raw_stream_url)
if url_health:
- self.raw_stream_url = url
+ self.raw_stream_url = _url
break
- except Exception as e:
- logger.debug(e)
+ except TimeoutError as e:
+ logger.debug(f"Timeout Error: {e} at {stream_info['url_info'][i]['host']}")
+ continue
+ except Exception:
+ logger.exception("Uncaught exception:")
continue
finally:
logger.debug(f"{i} - {self.raw_stream_url}")
@@ -177,7 +186,7 @@ class Bililive(DownloadBase):
self.raw_stream_url = None
return False
else:
- self.raw_stream_url = url
+ self.raw_stream_url = _url
return True
@@ -219,7 +228,6 @@ def do_login(s):
def oversea_expand(s, url, ov05_ip):
# 强制替换ov05 302redirect之后的真实地址为指定的域名或ip达到自选ov05节点的目的
- import re
r = s.get(url, stream=True)
logger.debug(f'将ov-gotcha05的节点ip替换为了{ov05_ip}')
return re.sub(r".*(?=/d1--ov-gotcha05)", f"http://{ov05_ip}", r.url, 1)
diff --git a/biliup/plugins/biliuprs.py b/biliup/plugins/biliuprs.py
index 37a0032..c17ec3d 100644
--- a/biliup/plugins/biliuprs.py
+++ b/biliup/plugins/biliuprs.py
@@ -19,6 +19,8 @@ class BiliWeb(UploadBase):
super().__init__(principal, data, persistence_path='bili.cookie', postprocessor=postprocessor)
if tags is None:
tags = []
+ else:
+ tags = [str(tag).format(streamer=self.data['name']) for tag in tags]
self.lines = lines
self.submit_api = submit_api
self.threads = threads
diff --git a/biliup/plugins/egame.py b/biliup/plugins/egame.py
deleted file mode 100644
index 3349417..0000000
--- a/biliup/plugins/egame.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import requests
-from . import logger
-from ..engine.decorators import Plugin
-from ..engine.download import DownloadBase
-
-
-@Plugin.download(regexp=r'(?:https?://)?(?:egame\.)?qq\.com')
-class egame(DownloadBase):
- def __init__(self, fname, url, suffix='flv'):
- super().__init__(fname, url, suffix)
-
- def check_stream(self, is_check=False):
- if len(self.url.split("egame.qq.com/")) < 2:
- logger.debug("直播间格式错误")
- return False
- rid = self.url.split("egame.qq.com/")[1]
- url = 'https://share.egame.qq.com/cgi-bin/pgg_async_fcgi'
- data = {'param': '''{"0":{"module":"pgg_live_read_svr","method":"get_live_and_profile_info","param":{
- "anchor_id":''' + str(rid) + ''',"layout_id":"hot","index":1,"other_uid":0}}}'''}
- r = requests.post(url=url, data=data, timeout=5).json()
- if r['ecode'] != 0:
- logger.debug("直播间地址错误")
- return False
- pid = r['data']["0"]["retBody"]["data"]["video_info"]["pid"]
- if pid == "":
- logger.debug("直播间不存在")
- return False
- is_live = r['data']["0"]["retBody"]["data"]["profile_info"]["is_live"]
- if is_live != 1:
- logger.debug("主播未开播")
- return False
- self.raw_stream_url = r['data']["0"]["retBody"]["data"]["video_info"]["stream_infos"][0]["play_url"]
- self.room_title = r['data']["0"]["retBody"]['data']['video_info']['title']
- return True
diff --git a/biliup/web/__init__.py b/biliup/web/__init__.py
index 13fdfd1..cf27ef8 100644
--- a/biliup/web/__init__.py
+++ b/biliup/web/__init__.py
@@ -171,10 +171,13 @@ async def tag_check(request):
@routes.get('/v1/videos')
async def streamers(request):
media_extensions = ['.mp4', '.flv', '.3gp', '.webm', '.mkv', '.ts']
+ _blacklist = ['next-env.d.ts']
# 获取文件列表
file_list = []
i = 1
for file_name in os.listdir('.'):
+ if file_name in _blacklist:
+ continue
name, ext = os.path.splitext(file_name)
if ext in media_extensions:
file_list.append({'key': i, 'name': file_name, 'updateTime': os.path.getmtime(file_name),
@@ -445,7 +448,8 @@ async def app_status(request):
from biliup.app import context
from biliup.config import Config
from biliup.app import PluginInfo
- res = {}
+ from biliup import __version__
+ res = {'version': __version__,}
for key, value in context.items(): # 遍历删除不能被 json 序列化的键值对
if isinstance(value, Config):
continue
@@ -481,7 +485,7 @@ async def myinfo(request):
try:
config.load_cookies(file)
except FileNotFoundError:
- return web.json_response({"status": 500, 'error': f"找不到 {file} !!!"}, status=500)
+ return web.json_response({"status": 500, 'error': f"{file} 文件不存在"}, status=500)
cookies = config.data['user']['cookies']
return web.json_response(BiliBili.myinfo(cookies))