diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml
index 25736ff..d5029a5 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.yaml
+++ b/.github/ISSUE_TEMPLATE/bug-report.yaml
@@ -9,27 +9,26 @@ body:
## 请补全标题并认真填写下方内容
请清晰描述问题,不必严格套用模板。
-
+
- type: checkboxes
id: terms
attributes:
label: Code of Conduct
description: 你必须先同意以下内容
options:
- - label: 我清楚知道,可以在 [Release](https://github.com/biliup/biliup/releases) 检阅最新的 Biliup 版本
+ - label: 我已在 [Release](https://github.com/biliup/biliup/releases) 检阅最新版本并尝试,且问题依旧
required: true
- label: 我清楚知道,反馈非最新 Release 版的错误是无意义的
required: true
- - label: 我清楚知道,日志中并不包含 Cookie 此类的信息,可以直接上传完整日志
- required: true
- - label: 我已经尝试过最新 Release 版,且问题依旧
+ - label: 我清楚知道,可以通过附件功能上传完整文件
required: true
- type: input
id: system_version
attributes:
label: 运行环境
- description: 如 Windows Server 2012、CentOS 8.2 等
+ description: 包括系统名称、系统版本、系统架构、Python版本
+ placeholder: Debian 12.6 x86_64 && Python 3.12.4 或 Docker tag:v0.4.71
validations:
required: true
@@ -38,6 +37,7 @@ body:
attributes:
label: biliup 版本
description: 可通过运行 `biliup --version` 查看
+ placeholder: v0.4.71
validations:
required: true
@@ -45,7 +45,8 @@ body:
id: url
attributes:
label: 所录制的主播网址
- description: 可以在配置文件中查看
+ description: 因平台内直播分区策略有所不同,请准确填写出现问题的主播网址
+ placeholder: https://www.twitch.tv/lck
validations:
required: true
@@ -60,18 +61,18 @@ body:
- type: textarea
id: config
attributes:
- label: 配置文件
- description: 请**脱敏**后上传你的配置文件, 或将文件内容复制到下面模板中
+ label: 任务平台
+ description: 请复制WEBUI的任务平台内容并**脱敏**后放入文件上传, 或直接复制到下面模板中
value: |
- 配置文件
+ 任务平台
-
+
```bash
# 在这里填写你的配置文件
```
-
+
- type: textarea
@@ -84,13 +85,13 @@ body:
### 日志
-
+
```bash
# 在这里填写你的报错日志
```
-
+
-
+
### 其他补充描述
-
+
diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx
index 73b5c3a..363b7f0 100644
--- a/app/dashboard/page.tsx
+++ b/app/dashboard/page.tsx
@@ -209,7 +209,7 @@ const Dashboard: React.FC = () => {
{/* 哔哩哔哩 */}
-
+
{/* CC直播 */}
{/* 抖音 */}
diff --git a/app/ui/plugins/bilibili.tsx b/app/ui/plugins/bilibili.tsx
index 4897f7f..382b063 100644
--- a/app/ui/plugins/bilibili.tsx
+++ b/app/ui/plugins/bilibili.tsx
@@ -2,7 +2,14 @@
import React from "react";
import { Form, Select, Collapse } from "@douyinfe/semi-ui";
-const Bilibili: React.FC = () => {
+type Props = {
+ entity: any;
+ list: any;
+};
+
+const Bilibili: React.FC = (props) => {
+ const entity = props.entity;
+ const list = props.list;
return (
<>
@@ -41,6 +48,46 @@ const Bilibili: React.FC = () => {
field="bilibili_danmaku"
extraText="录制哔哩哔哩弹幕,目前不支持视频按时长分段下的弹幕文件自动分段。仅限下载插件为非 stream-gears 时生效,默认关闭。"
label="录制弹幕(bilibili_danmaku)"
+ fieldStyle={{
+ alignSelf: "stretch",
+ padding: 0,
+ }}
+ />
+
+ 根据格式填入cookie。推荐使用「
+
+ biliup-rs
+
+ 」来获取。
+
+
+ }
+ placeholder="SESSDATA=none;bili_jct=none;DedeUserID__ckMd5=none;DedeUserID=none;"
+ label="哔哩哔哩 Cookie 文本(bili_cookie)"
+ style={{ width: "100%" }}
+ fieldStyle={{
+ alignSelf: "stretch",
+ padding: 0,
+ }}
+ />
+
= (props) => {
return (
<>
-
- 根据格式填入cookie。推荐使用「
-
- biliup-rs
-
- 」来获取。
-
-
- }
- placeholder="SESSDATA=none;bili_jct=none;DedeUserID__ckMd5=none;DedeUserID=none;"
- label="哔哩哔哩 Cookie 文本(bili_cookie)"
- style={{ width: "100%" }}
- fieldStyle={{
- alignSelf: "stretch",
- padding: 0,
- }}
- />
-
-
- 如需要录制抖音 www.douyin.com/user/
- 类型链接,或遭到风控,请在此填入 Cookie。
-
- 需要__ac_nonce、__ac_signature、sessionid的值,请不要将所有
- Cookie 填入。
-
- }
- placeholder="__ac_nonce=none;__ac_signature=none;sessionid=none;"
- label="抖音 Cookie(douyin_cookie)"
- style={{ width: "100%" }}
- fieldStyle={{
- alignSelf: "stretch",
- padding: 0,
- }}
- />
= (props) => {
padding: 0,
}}
/>
-
- 填入 Cookie 防止游客2分30秒限制。
- 需要 udb_biztoken, udb_uid, yyuid 的值,
- 请不要将所有 Cookie 填入。
-
- }
- placeholder="udb_uid=none;yyuid=none;udb_biztoken=none;"
- label="虎牙 Cookie(huya_cookie)"
- style={{ width: "100%" }}
- fieldStyle={{
- alignSelf: "stretch",
- padding: 0,
- }}
- />
-
- 【仅限Turbo会员】如录制Twitch时遇见视频流中广告过多的情况,可尝试在此填入cookie,可以大幅减少视频流中的twitch广告
-
- 该 Cookie
- 存在过期风险,Cookie过期后会在日志输出警告,请注意及时更换。
-
- 当 Cookie 失效,录制时将忽略
- Cookie。(经作者个人测试,可保持未过期状态四个月以上)
-
- twitch_cookie 获取方式:在浏览器中打开 twitch.tv ,
- F12 调出控制台,在控制台中执行如下代码:
-
- {`document.cookie.split("; ").find(item => item.startsWith("auth-token="))?.split("=")[1]`}
-
- twitch_cookie 需要在 downloader=
- "ffmpeg" 时候才会生效。
-
- }
- label="Twitch Cookie(twitch_cookie)"
- style={{ width: "100%" }}
- fieldStyle={{
- alignSelf: "stretch",
- padding: 0,
- }}
- />
-
- 使用Cookies登陆YouTube帐号,可用于下载会限、私享等未登录账号无法访问的内容。
-
- 可以使用 Chrome 插件「Get
- cookies.txt」来生成txt文件,请使用 Netscape 格式的
- Cookies 文本路径。
-
- }
- label="TouTube Cookie(youtube_cookie)"
- style={{ width: "100%" }}
- fieldStyle={{
- alignSelf: "stretch",
- padding: 0,
- }}
- />
{
}
label="录制弹幕(douyin_danmaku)"
+ fieldStyle={{
+ alignSelf: "stretch",
+ padding: 0,
+ }}
+ />
+
+ 如需要录制抖音 www.douyin.com/user/
+ 类型链接,或遭到风控,请在此填入 Cookie。
+
+ 需要__ac_nonce、__ac_signature、sessionid的值,请不要将所有
+ Cookie 填入。
+
+ }
+ placeholder="__ac_nonce=none;__ac_signature=none;sessionid=none;"
+ label="抖音 Cookie(douyin_cookie)"
+ style={{ width: "100%" }}
+ fieldStyle={{
+ alignSelf: "stretch",
+ padding: 0,
+ }}
/>
{
{
showClear={true}
>
AUTO(自动,默认)
+ bda
bda2
- kodo
ws
qn
bldsa
+ tx
+ txa
= (props) => {
alignSelf: "stretch",
padding: 0,
}}
- rules={[
- {
- pattern: /^[A-Z]{2}(?:\d{2})?$/,
- message: "请仅输入线路别称"
- }
- ]}
showClear={true}
>
直播线路3(AL)
@@ -109,11 +103,19 @@ const Huya: React.FC = (props) => {
? entity["huya_imgplus"]
: true
}
+ fieldStyle={{
+ alignSelf: "stretch",
+ padding: 0,
+ }}
/>
>
diff --git a/app/ui/plugins/twitcasting.tsx b/app/ui/plugins/twitcasting.tsx
index 1a35cb1..9d66844 100644
--- a/app/ui/plugins/twitcasting.tsx
+++ b/app/ui/plugins/twitcasting.tsx
@@ -11,6 +11,10 @@ const TwitCasting: React.FC = () => {
field="twitcasting_danmaku"
extraText="录制TwitCasting弹幕,默认关闭"
label="录制弹幕(twitcasting_danmaku)"
+ fieldStyle={{
+ alignSelf: "stretch",
+ padding: 0,
+ }}
/>
= (props) => {
field="twitch_disable_ads"
extraText="去除Twitch广告功能,默认开启
这个功能会导致Twitch录播分段,因为遇到广告就自动断开了,这就是去广告。若需要录播完整一整段可以关闭这个,但是关了之后就会有紫色屏幕的CommercialTime
-还有一个不想视频分段的办法是去花钱开一个Turbo会员,能不看广告,然后下面的user里把twitch的cookie填上,也能不看广告,自然就不会分段了"
+还有一个办法是去花钱开一个Turbo会员,然后下面的user里把twitch的cookie填上,也能去除广告"
label="去除广告(twitch_disable_ads)"
+ fieldStyle={{
+ alignSelf: "stretch",
+ padding: 0,
+ }}
+ />
+
+ 【仅限Turbo会员】如录制Twitch时遇见视频流中广告过多的情况,可尝试在此填入cookie,可以大幅减少视频流中的twitch广告
+
+ 该 Cookie
+ 存在过期风险,Cookie过期后会在日志输出警告,请注意及时更换。
+
+ 当 Cookie 失效,录制时将忽略
+ Cookie。(经作者个人测试,可保持未过期状态四个月以上)
+
+ twitch_cookie 获取方式:在浏览器中打开 twitch.tv ,
+ F12 调出控制台,在控制台中执行如下代码:
+
+ {`document.cookie.split("; ").find(item => item.startsWith("auth-token="))?.split("=")[1]`}
+
+ twitch_cookie 需要在 downloader=
+ "ffmpeg" 时候才会生效。
+
+ }
+ label="Twitch Cookie(twitch_cookie)"
+ style={{ width: "100%" }}
+ fieldStyle={{
+ alignSelf: "stretch",
+ padding: 0,
+ }}
/>
>
diff --git a/app/ui/plugins/youtube.tsx b/app/ui/plugins/youtube.tsx
index bfc1ee1..176a7aa 100644
--- a/app/ui/plugins/youtube.tsx
+++ b/app/ui/plugins/youtube.tsx
@@ -11,6 +11,24 @@ const YouTube: React.FC = (props) => {
return (
<>
+
+ 使用Cookies登陆YouTube帐号,可用于下载会限、私享等未登录账号无法访问的内容。
+
+ 可以使用 Chrome 插件「Get
+ cookies.txt」来生成txt文件,请使用 Netscape 格式的
+ Cookies 文本路径。
+
+ }
+ label="YouTube Cookie(youtube_cookie)"
+ style={{ width: "100%" }}
+ fieldStyle={{
+ alignSelf: "stretch",
+ padding: 0,
+ }}
+ />
= (props) => {
例如录制https://www.youtube.com/@NeneAmanoCh/streams,关闭后将忽略正在直播
"
label="下载直播(youtube_enable_download_live)"
+ fieldStyle={{
+ alignSelf: "stretch",
+ padding: 0,
+ }}
/>
= (props) => {
例如录制https://www.youtube.com/@NeneAmanoCh/streams,关闭后将忽略直播回放
"
label="下载回放(youtube_enable_download_playback)"
+ fieldStyle={{
+ alignSelf: "stretch",
+ padding: 0,
+ }}
/>
{self._auto_os['os']}: {self._auto_os['query']}. time: {self._auto_os.get('cost')}")
if self._auto_os['os'] == 'upos':
upload = self.upos
- elif self._auto_os['os'] == 'cos':
- upload = self.cos
- elif self._auto_os['os'] == 'cos-internal':
- upload = lambda *args, **kwargs: self.cos(*args, **kwargs, internal=True)
- elif self._auto_os['os'] == 'kodo':
- upload = self.kodo
+ # elif self._auto_os['os'] == 'cos':
+ # upload = self.cos
+ # elif self._auto_os['os'] == 'cos-internal':
+ # upload = lambda *args, **kwargs: self.cos(*args, **kwargs, internal=True)
+ # elif self._auto_os['os'] == 'kodo':
+ # upload = self.kodo
else:
logger.error(f"NoSearch:{self._auto_os['os']}")
raise NotImplementedError(self._auto_os['os'])
diff --git a/biliup/plugins/bilibili.py b/biliup/plugins/bilibili.py
index ae4c370..65511d0 100644
--- a/biliup/plugins/bilibili.py
+++ b/biliup/plugins/bilibili.py
@@ -12,8 +12,7 @@ from ..engine.download import DownloadBase
OFFICIAL_API = "https://api.live.bilibili.com"
-@Plugin.download(regexp=r'(?:https?://)?live\.bilibili\.com')
-@Plugin.download(regexp=r'(?:https?://)?b23\.tv')
+@Plugin.download(regexp=r'(?:https?://)?(b23\.tv|live\.bilibili\.com)')
class Bililive(DownloadBase):
def __init__(self, fname, url, suffix='flv'):
super().__init__(fname, url, suffix)
@@ -22,7 +21,7 @@ class Bililive(DownloadBase):
self.fake_headers['referer'] = url
if config.get('user', {}).get('bili_cookie'):
self.fake_headers['cookie'] = config.get('user', {}).get('bili_cookie')
- elif config.get('user', {}).get('bili_cookie_file'):
+ if config.get('user', {}).get('bili_cookie_file'):
cookie_file_name = config.get('user', {}).get('bili_cookie_file')
try:
with open(cookie_file_name, encoding='utf-8') as stream:
@@ -37,10 +36,23 @@ class Bililive(DownloadBase):
# logger.warning("No cookie provided. The original quality may not be available.")
async def acheck_stream(self, is_check=False):
- room_id = match1(self.url, r'/(\d+)')
- qualityNumber = int(config.get('bili_qn', 10000))
client.headers.update(self.fake_headers)
+
+ if "b23.tv" in self.url:
+ try:
+ url = (await client.get(self.url, follow_redirects=False)).next_request.url
+ if "live.bilibili" in str(url):
+ self.url = url
+ else:
+ raise
+ except:
+ logger.error(f"{self.plugin_msg}: 不支持的链接")
+ return False
+
+ room_id = match1(self.url, r'bilibili.com/(\d+)')
+ qualityNumber = int(config.get('bili_qn', 10000))
+
# 获取直播状态与房间标题
info_by_room_url = f"{OFFICIAL_API}/xlive/web-room/v1/index/getInfoByRoom?room_id={room_id}"
try:
diff --git a/biliup/plugins/biliuprs.py b/biliup/plugins/biliuprs.py
index 4319acd..7b78fe3 100644
--- a/biliup/plugins/biliuprs.py
+++ b/biliup/plugins/biliuprs.py
@@ -118,18 +118,18 @@ class BiliWeb(UploadBase):
def stream_gears_upload(ex_conn, lines, *args, **kwargs):
try:
- if lines == 'kodo':
- kwargs['line'] = stream_gears.UploadLine.Kodo
+ if lines == 'bda':
+ kwargs['line'] = stream_gears.UploadLine.Bda
elif lines == 'bda2':
kwargs['line'] = stream_gears.UploadLine.Bda2
elif lines == 'ws':
kwargs['line'] = stream_gears.UploadLine.Ws
elif lines == 'qn':
kwargs['line'] = stream_gears.UploadLine.Qn
- elif lines == 'cos':
- kwargs['line'] = stream_gears.UploadLine.Cos
- elif lines == 'cos-internal':
- kwargs['line'] = stream_gears.UploadLine.CosInternal
+ elif lines == 'tx':
+ kwargs['line'] = stream_gears.UploadLine.Tx
+ elif lines == 'txa':
+ kwargs['line'] = stream_gears.UploadLine.Txa
elif lines == 'bldsa':
kwargs['line'] = stream_gears.UploadLine.Bldsa
diff --git a/biliup/plugins/douyu.py b/biliup/plugins/douyu.py
index 9b256d2..0eb7049 100644
--- a/biliup/plugins/douyu.py
+++ b/biliup/plugins/douyu.py
@@ -1,19 +1,21 @@
import hashlib
import time
from urllib.parse import parse_qs
+from functools import lru_cache
from biliup.common.util import client
from biliup.config import config
from biliup.Danmaku import DanmakuClient
from ..engine.decorators import Plugin
from ..engine.download import DownloadBase
-from ..plugins import logger, match1
+from ..plugins import logger, match1, random_user_agent
@Plugin.download(regexp=r'(?:https?://)?(?:(?:www|m)\.)?douyu\.com')
class Douyu(DownloadBase):
def __init__(self, fname, url, suffix='flv'):
super().__init__(fname, url, suffix)
+ self._room_id = match1(url, r'rid=(\d+)')
self.douyu_danmaku = config.get('douyu_danmaku', False)
async def acheck_stream(self, is_check=False):
@@ -22,24 +24,15 @@ class Douyu(DownloadBase):
return False
try:
- room_id = self.url.split('douyu.com/')[1].split('/')[0].split('?')[0]
- # 暂时只判断纯数字的room_id
- if not room_id.isdigit() or len(room_id) < 6:
- room_id = 0
- if room_id == 0:
- resp = await client.get(self.url, headers=self.fake_headers, timeout=5)
- room_id = match1(resp.text, r'\$ROOM\.room_id\s*=\s*(\d+)', r'apm_room_id\s*=\s*(\d+)')[0]
- if not room_id:
- logger.error(f"{self.plugin_msg}: 直播间不存在或已关闭")
- return False
+ if not self._room_id:
+ self._room_id = _get_real_rid(self.url)
except:
logger.exception(f"{self.plugin_msg}: 获取房间号错误")
return False
try:
room_info = (
- await client.get(f"https://www.douyu.com/betard/{room_id}",
- headers=self.fake_headers, timeout=5)
+ await client.get(f"https://www.douyu.com/betard/{self._room_id}", headers=self.fake_headers)
).json()['room']
except:
logger.exception(f"{self.plugin_msg}: 获取直播间信息错误")
@@ -52,8 +45,10 @@ class Douyu(DownloadBase):
logger.debug(f"{self.plugin_msg}: 正在放录播")
return False
if config.get('douyu_disable_interactive_game', False):
- gift_info = (await client.get(f"https://www.douyu.com/api/interactive/web/v2/list?rid={room_id}",
- headers=self.fake_headers, timeout=5)).json().get('data', {})
+ gift_info = (
+ await client.get(f"https://www.douyu.com/api/interactive/web/v2/list?rid={self._room_id}",
+ headers=self.fake_headers)
+ ).json().get('data', {})
if gift_info:
logger.debug(f"{self.plugin_msg}: 正在运行互动游戏")
return False
@@ -77,18 +72,18 @@ class Douyu(DownloadBase):
import jsengine
ctx = jsengine.jsengine()
js_enc = (
- await client.get(f'https://www.douyu.com/swf_api/homeH5Enc?rids={room_id}',
- headers=self.fake_headers, timeout=5)
- ).json()['data'][f'room{room_id}']
+ await client.get(f'https://www.douyu.com/swf_api/homeH5Enc?rids={self._room_id}',
+ headers=self.fake_headers)
+ ).json()['data'][f'room{self._room_id}']
js_enc = js_enc.replace('return eval', 'return [strc, vdwdae325w_64we];')
sign_fun, sign_v = ctx.eval(f'{js_enc};ub98484234();')
tt = str(int(time.time()))
did = hashlib.md5(tt.encode('utf-8')).hexdigest()
- rb = hashlib.md5(f"{room_id}{did}{tt}{sign_v}".encode('utf-8')).hexdigest()
+ rb = hashlib.md5(f"{self._room_id}{did}{tt}{sign_v}".encode('utf-8')).hexdigest()
sign_fun = sign_fun.rstrip(';').replace("CryptoJS.MD5(cb).toString()", f'"{rb}"')
- sign_fun += f'("{room_id}","{did}","{tt}");'
+ sign_fun += f'("{self._room_id}","{did}","{tt}");'
params = parse_qs(ctx.eval(sign_fun))
except:
@@ -100,7 +95,7 @@ class Douyu(DownloadBase):
params['rate'] = config.get('douyu_rate', 0)
try:
- live_data = await self.get_play_info(room_id, params)
+ live_data = await self.get_play_info(self._room_id, params)
self.raw_stream_url = f"{live_data['rtmp_url']}/{live_data['rtmp_live']}"
except:
logger.exception(f"{self.plugin_msg}: ")
@@ -113,17 +108,25 @@ class Douyu(DownloadBase):
self.danmaku = DanmakuClient(self.url, self.gen_download_filename())
async def get_play_info(self, room_id, params):
- __cdn_check = lambda _name, _list: any(_name in _item['cdn'] for _item in _list)
-
- live_data = await client.post(f'https://www.douyu.com/lapi/live/getH5Play/{room_id}',
- headers=self.fake_headers, params=params, timeout=5)
+ live_data = await client.post(
+ f'https://www.douyu.com/lapi/live/getH5Play/{room_id}', headers=self.fake_headers, params=params)
if not live_data.is_success:
raise RuntimeError(live_data.text)
live_data = live_data.json().get('data')
if isinstance(live_data, dict):
if not live_data['rtmp_cdn'].endswith('h5') or 'akm' in live_data['rtmp_cdn']:
- params['cdn'] = 'tct-h5' if __cdn_check('tct-h5', live_data['cdnsWithName']) \
- else live_data['cdnsWithName'][-1]['cdn']
+ params['cdn'] = live_data['cdnsWithName'][-1]['cdn']
return await self.get_play_info(room_id, params)
return live_data
raise RuntimeError(live_data)
+
+@lru_cache(maxsize=None)
+def _get_real_rid(url):
+ import requests
+ headers = {
+ "user-agent": random_user_agent('mobile'),
+ }
+ rid = url.split('douyu.com/')[1].split('/')[0].split('?')[0] or match1(url, r'douyu.com/(\d+)')
+ resp = requests.get(f"https://m.douyu.com/{rid}", headers=headers)
+ real_rid = match1(resp.text, r'roomInfo":{"rid":(\d+)')
+ return real_rid
\ No newline at end of file
diff --git a/biliup/plugins/huya.py b/biliup/plugins/huya.py
index 9986981..1c40b66 100644
--- a/biliup/plugins/huya.py
+++ b/biliup/plugins/huya.py
@@ -4,40 +4,37 @@ import json
import random
import time
from urllib.parse import parse_qs, unquote
+from functools import lru_cache
from biliup.common.util import client
from biliup.config import config
from biliup.Danmaku import DanmakuClient
from ..engine.decorators import Plugin
from ..engine.download import DownloadBase
-from ..plugins import logger
+from ..plugins import logger, random_user_agent
@Plugin.download(regexp=r'(?:https?://)?(?:(?:www|m)\.)?huya\.com')
class Huya(DownloadBase):
def __init__(self, fname, url, suffix='flv'):
super().__init__(fname, url, suffix)
- self.huya_danmaku = config.get('huya_danmaku', False)
self.fake_headers['referer'] = url
self.fake_headers['cookie'] = config.get('user', {}).get('huya_cookie', '')
- self.__room_id = None
+ self._room_id = url.split('huya.com/')[1].split('?')[0]
+ self.huya_danmaku = config.get('huya_danmaku', False)
+
async def acheck_stream(self, is_check=False):
try:
- await self.__verify_cookie()
- self.__room_id = self.url.split('huya.com/')[1]
- if not self.__room_id:
- raise
- if not self.__room_id.isdigit():
- html_info = await self.get_room_profile()
- if not html_info:
- raise
- self.__room_id = html_info['data'][0]['gameLiveInfo']['profileRoom']
+ if self.fake_headers.get('cookie'):
+ await self.verify_cookie()
+ if not self._room_id.isdigit():
+ self._room_id = _get_real_rid(self.url)
+ room_profile = await self.get_room_profile(use_api=True)
except Exception as e:
logger.error(f"{self.plugin_msg}: {e}")
return False
- room_profile = await self.get_room_profile(use_api=True)
if room_profile['realLiveStatus'] != 'ON' or room_profile['liveStatus'] != 'ON':
'''
ON: 直播
@@ -48,6 +45,11 @@ class Huya(DownloadBase):
self.raw_stream_url = None
return False
+ if not room_profile['liveData'].get('bitRateInfo'):
+ # 主播未推流
+ logger.debug(f"{self.plugin_msg} : 未推流")
+ return False
+
if is_check:
return True
@@ -117,6 +119,7 @@ class Huya(DownloadBase):
self.raw_stream_url += f"&ratio={record_ratio}"
return True
+
def danmaku_init(self):
if self.huya_danmaku:
self.danmaku = DanmakuClient(self.url, self.gen_download_filename())
@@ -124,17 +127,18 @@ class Huya(DownloadBase):
async def get_room_profile(self, use_api=False) -> dict:
if use_api:
- resp = (await client.get(f"https://mp.huya.com/cache.php?m=Live&do=profileRoom&roomid={self.__room_id}", \
- headers=self.fake_headers)).json()
+ resp = (await client.get(f"https://mp.huya.com/cache.php?m=Live&do=profileRoom&roomid={self._room_id}", \
+ headers=self.fake_headers)).json()
if resp['status'] != 200:
- raise Exception(f"{self.plugin_msg}: {resp['message']}")
+ raise Exception(f"{resp['message']}")
return resp['data']
else:
- html = (await client.get(f"https://www.huya.com/{self.__room_id}", headers=self.fake_headers)).text
+ html = (await client.get(f"https://www.huya.com/{self._room_id}", headers=self.fake_headers)).text
if '找不到这个主播' in html:
- raise Exception(f"{self.plugin_msg}: 找不到这个主播")
+ raise Exception(f"找不到这个主播")
return json.loads(html.split('stream: ')[1].split('};')[0])
+
async def get_stream_urls(self, protocol, use_api=False, allow_imgplus=True) -> dict:
'''
返回指定协议的所有CDN流
@@ -145,25 +149,27 @@ class Huya(DownloadBase):
try:
stream_info = room_profile['data'][0]['gameStreamInfoList']
except KeyError:
- raise Exception(f"{self.plugin_msg}: {room_profile}")
+ raise Exception(f"{room_profile}")
else:
stream_info = room_profile['stream']['baseSteamInfoList']
- streams = self.__dict_sorting(json.loads(room_profile['liveData']['mStreamRatioWeb']))
+ streams = _dict_sorting(json.loads(room_profile['liveData'].get('mStreamRatioWeb', '{}')))
stream = stream_info[0]
stream_name = stream['sStreamName']
suffix, anti_code = stream[f's{protocol}UrlSuffix'], stream[f's{protocol}AntiCode']
if not allow_imgplus:
stream_name = stream_name.replace('-imgplus', '')
- anti_code = self.__build_query(stream_name, anti_code, )
+ anti_code = self.__build_query(stream_name, anti_code, self.fake_headers['cookie'])
for stream in stream_info:
+ stream_url = f"{stream[f's{protocol}Url']}/{stream_name}.{suffix}?{anti_code}"
if stream['sCdnType'] in ['HY', 'HUYA', 'HYZJ']: continue
- streams[stream['sCdnType']] = f"{stream[f's{protocol}Url']}/{stream_name}.{suffix}?{anti_code}"
+ streams[stream['sCdnType']] = stream_url
return streams
- def __build_query(self, stream_name, anti_code) -> str:
+ @staticmethod
+ def __build_query(stream_name, anti_code, cookies=None) -> str:
url_query = parse_qs(anti_code)
platform_id = 100
- uid = self.__get_uid(self.fake_headers['cookie'], stream_name)
+ uid = _get_uid(cookies, stream_name)
convert_uid = (uid << 8 | uid >> (32 - 8)) & 0xFFFFFFFF
ws_time = url_query['wsTime'][0]
ct = int((int(ws_time, 16) + random.random()) * 1000)
@@ -197,7 +203,8 @@ class Huya(DownloadBase):
}
return '&'.join([f"{k}={v}" for k, v in anti_code.items()])
- async def __verify_cookie(self):
+
+ async def verify_cookie(self):
if self.fake_headers['cookie']:
resp = (await client.post('https://udblgn.huya.com/web/cookie/verify', \
headers=self.fake_headers, data={'appId': 5002})).json()
@@ -205,18 +212,32 @@ class Huya(DownloadBase):
logger.error(f"{self.plugin_msg}: {resp.json()['message']}")
self.fake_headers['cookie'] = ''
- @staticmethod
- def __dict_sorting(data: dict) -> dict:
+@lru_cache(maxsize=None)
+def _get_real_rid(url):
+ import requests
+ headers = {
+ 'user-agent': random_user_agent(),
+ }
+ html = requests.get(url, headers=headers).text
+ if '找不到这个主播' in html:
+ raise Exception(f"找不到这个主播")
+ html_obj = json.loads(html.split('stream: ')[1].split('};')[0])
+ return html_obj['data'][0]['gameLiveInfo']['profileRoom']
+
+
+def _dict_sorting(data: dict) -> dict:
+ if data:
data = {k: v for k, v in data.items() if k not in ['HY', 'HUYA', 'HYZJ']}
return dict(sorted(data.items(), key=lambda x: x[1], reverse=True))
+ return {}
- @staticmethod
- def __get_uid(cookie: str, stream_name: str) -> int:
- if cookie:
- cookie_dict = {k.strip(): v for k, v in (item.split('=') for item in cookie.split(';'))}
- for key in ['udb_uid', 'yyuid']:
- if key in cookie_dict:
- return int(cookie_dict[key])
- if stream_name:
- return int(stream_name.split('-')[0])
- return random.randint(1400000000000, 1499999999999)
\ No newline at end of file
+
+def _get_uid(cookie: str, stream_name: str) -> int:
+ if cookie:
+ cookie_dict = {k.strip(): v for k, v in (item.split('=') for item in cookie.split(';'))}
+ for key in ['udb_uid', 'yyuid']:
+ if key in cookie_dict:
+ return int(cookie_dict[key])
+ if stream_name:
+ return int(stream_name.split('-')[0])
+ return random.randint(1400000000000, 1499999999999)
diff --git a/biliup/web/aiohttp_basicauth_middleware.py b/biliup/web/aiohttp_basicauth_middleware.py
index f52598a..179cbdd 100644
--- a/biliup/web/aiohttp_basicauth_middleware.py
+++ b/biliup/web/aiohttp_basicauth_middleware.py
@@ -72,6 +72,7 @@ def check_access(
if hashed_password != hashed_request_password:
return False
+ # log.debug('%r log in successed', "biliup")
return True
diff --git a/biliup/web/http_basic_auth.py b/biliup/web/http_basic_auth.py
index 649240d..0fda1c1 100644
--- a/biliup/web/http_basic_auth.py
+++ b/biliup/web/http_basic_auth.py
@@ -3,6 +3,7 @@ HTTP Basic Auth python lib from https://github.com/bugov/http-basic-auth
"""
import base64
+from typing import Tuple
__version__ = '1.2.0'
@@ -11,8 +12,7 @@ class BasicAuthException(Exception):
"""General exception for all http-basic-auth problems
"""
-# FIXME: 应使用 aes-128-gcm
-def parse_token(token: str, coding='utf-8') -> tuple[str, str]:
+def parse_token(token: str, coding='utf-8') -> Tuple[str, str]:
"""Get login + password tuple from Basic Auth token.
"""
try:
@@ -57,7 +57,7 @@ def generate_token(login: str, password: str, coding='utf-8') -> str:
return str(b_token, encoding=coding)
-def parse_header(header_value: str, coding='utf-8') -> tuple[str, str]:
+def parse_header(header_value: str, coding='utf-8') -> Tuple[str, str]:
"""Get login + password tuple from Basic Auth header value.
"""
if header_value is None:
diff --git a/pyproject.toml b/pyproject.toml
index 46352f0..c05682c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -14,7 +14,7 @@ dependencies = [
"stream-gears >= 0.2.0,<0.3",
"psutil >= 5.4.6",
"aiofiles >= 22.1.0",
- "yt-dlp >= 2024.3.10",
+ "yt-dlp >= 2024.7.9",
"Pillow >= 5.2.0",
"aiohttp[speedups]",
"aiohttp-cors",
@@ -22,13 +22,12 @@ dependencies = [
"httpx[http2] >= 0.27.0",
"httpcore > 1.0.3",
"PyYAML >= 4.2b1",
- "streamlink >= 6.5.0",
- "ykdl == 1.8.0; python_version < '3.8'",
- "ykdl >= 1.8.0; python_version >= '3.8'",
+ "streamlink >= 6.8.3",
+ "ykdl >= 1.8.0",
"rsa >= 4.6",
- 'importlib-resources; python_version<"3.9"',
- 'tomli >= 1.1.0 ; python_version < "3.11"',
- 'protobuf >= 4.23.0',
+ "importlib-resources; python_version < '3.9'",
+ "tomli >= 1.1.0 ; python_version < '3.11'",
+ "protobuf >= 4.23.0",
"SQLAlchemy>=2.0.0",
"alembic>=1.12.1",
"m3u8",