feat: Add cache during runtime (#995)
* feat: Runtime modification of Links * update * update * compatible with python 3.8 * update * update * update
This commit is contained in:
1 parent
b1f577b2dd
commit
02f8fb5c62
19 files changed
+314
-260
No files matched your search
@@ -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: |
|
||||
<details>
|
||||
<summary>
|
||||
配置文件
|
||||
任务平台
|
||||
</summary>
|
||||
|
||||
|
||||
```bash
|
||||
# 在这里填写你的配置文件
|
||||
```
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
- type: textarea
|
||||
@@ -84,13 +85,13 @@ body:
|
||||
<summary>
|
||||
### 日志
|
||||
</summary>
|
||||
|
||||
|
||||
```bash
|
||||
# 在这里填写你的报错日志
|
||||
```
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
### 其他补充描述
|
||||
|
||||
|
||||
<!--此处填写补充描述-->
|
||||
@@ -209,7 +209,7 @@ const Dashboard: React.FC = () => {
|
||||
</div>
|
||||
<Collapse keepDOM style={{ width: '100%' }}>
|
||||
{/* 哔哩哔哩 */}
|
||||
<Bilibili />
|
||||
<Bilibili entity={entity} list={list}/>
|
||||
{/* CC直播 */}
|
||||
<CC />
|
||||
{/* 抖音 */}
|
||||
|
||||
@@ -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> = (props) => {
|
||||
const entity = props.entity;
|
||||
const list = props.list;
|
||||
return (
|
||||
<>
|
||||
<Collapse.Panel header="哔哩哔哩" itemKey="bilibili">
|
||||
@@ -41,6 +48,46 @@ const Bilibili: React.FC = () => {
|
||||
field="bilibili_danmaku"
|
||||
extraText="录制哔哩哔哩弹幕,目前不支持视频按时长分段下的弹幕文件自动分段。仅限下载插件为非 stream-gears 时生效,默认关闭。"
|
||||
label="录制弹幕(bilibili_danmaku)"
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
<Form.Input
|
||||
field="user.bili_cookie"
|
||||
extraText={
|
||||
<div className="semi-form-field-extra">
|
||||
根据格式填入cookie。推荐使用「
|
||||
<a
|
||||
href="https://github.com/biliup/biliup-rs"
|
||||
title="「biliup-rs」 Github 项目主页"
|
||||
target="_blank"
|
||||
>
|
||||
biliup-rs
|
||||
</a>
|
||||
」来获取。
|
||||
<br />
|
||||
</div>
|
||||
}
|
||||
placeholder="SESSDATA=none;bili_jct=none;DedeUserID__ckMd5=none;DedeUserID=none;"
|
||||
label="哔哩哔哩 Cookie 文本(bili_cookie)"
|
||||
style={{ width: "100%" }}
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
<Form.Select
|
||||
field="user.bili_cookie_file"
|
||||
label="哔哩哔哩 Cookie 文件(bili_cookie_file)"
|
||||
style={{ width: "100%" }}
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
optionList={list}
|
||||
extraText="只支持「biliup-rs」生成的文件。当与上一个配置项同时存在时,将优先使用文件。"
|
||||
showClear={true}
|
||||
/>
|
||||
<Form.Select
|
||||
field="bili_protocol"
|
||||
|
||||
@@ -13,61 +13,6 @@ const Cookie: React.FC<Props> = (props) => {
|
||||
return (
|
||||
<>
|
||||
<Collapse.Panel header="用户 Cookie" itemKey="user">
|
||||
<Form.Input
|
||||
field="user.bili_cookie"
|
||||
extraText={
|
||||
<div className="semi-form-field-extra">
|
||||
根据格式填入cookie。推荐使用「
|
||||
<a
|
||||
href="https://github.com/biliup/biliup-rs"
|
||||
title="「biliup-rs」 Github 项目主页"
|
||||
target="_blank"
|
||||
>
|
||||
biliup-rs
|
||||
</a>
|
||||
」来获取。
|
||||
<br />
|
||||
</div>
|
||||
}
|
||||
placeholder="SESSDATA=none;bili_jct=none;DedeUserID__ckMd5=none;DedeUserID=none;"
|
||||
label="哔哩哔哩 Cookie 文本(bili_cookie)"
|
||||
style={{ width: "100%" }}
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
<Form.Select
|
||||
field="user.bili_cookie_file"
|
||||
label="哔哩哔哩 Cookie 文件(bili_cookie_file)"
|
||||
style={{ width: "100%" }}
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
optionList={list}
|
||||
extraText="只支持「biliup-rs」生成的文件。当与上一个配置项同时存在时,将优先使用文件。"
|
||||
showClear={true}
|
||||
/>
|
||||
<Form.Input
|
||||
field="user.douyin_cookie"
|
||||
extraText={
|
||||
<div className="semi-form-field-extra">
|
||||
如需要录制抖音 www.douyin.com/user/
|
||||
类型链接,或遭到风控,请在此填入 Cookie。
|
||||
<br />
|
||||
需要__ac_nonce、__ac_signature、sessionid的值,请不要将所有
|
||||
Cookie 填入。
|
||||
</div>
|
||||
}
|
||||
placeholder="__ac_nonce=none;__ac_signature=none;sessionid=none;"
|
||||
label="抖音 Cookie(douyin_cookie)"
|
||||
style={{ width: "100%" }}
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
<Form.Input
|
||||
field="user.kuaishou_cookie"
|
||||
extraText={
|
||||
@@ -85,71 +30,6 @@ const Cookie: React.FC<Props> = (props) => {
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
<Form.Input
|
||||
field="user.huya_cookie"
|
||||
extraText={
|
||||
<div className="semi-form-field-extra">
|
||||
填入 Cookie 防止游客2分30秒限制。
|
||||
需要 udb_biztoken, udb_uid, yyuid 的值,
|
||||
请不要将所有 Cookie 填入。
|
||||
</div>
|
||||
}
|
||||
placeholder="udb_uid=none;yyuid=none;udb_biztoken=none;"
|
||||
label="虎牙 Cookie(huya_cookie)"
|
||||
style={{ width: "100%" }}
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
<Form.Input
|
||||
field="user.twitch_cookie"
|
||||
extraText={
|
||||
<div className="semi-form-field-extra">
|
||||
【仅限Turbo会员】如录制Twitch时遇见视频流中广告过多的情况,可尝试在此填入cookie,可以大幅减少视频流中的twitch广告
|
||||
<br />
|
||||
该 Cookie
|
||||
存在过期风险,Cookie过期后会在日志输出警告,请注意及时更换。
|
||||
<br />
|
||||
当 Cookie 失效,录制时将忽略
|
||||
Cookie。(经作者个人测试,可保持未过期状态四个月以上)
|
||||
<br />
|
||||
twitch_cookie 获取方式:在浏览器中打开 twitch.tv ,
|
||||
F12 调出控制台,在控制台中执行如下代码:
|
||||
<br />
|
||||
<code
|
||||
style={{ color: "blue" }}
|
||||
>{`document.cookie.split("; ").find(item => item.startsWith("auth-token="))?.split("=")[1]`}</code>
|
||||
<br />
|
||||
twitch_cookie 需要在 downloader=
|
||||
"ffmpeg" 时候才会生效。
|
||||
</div>
|
||||
}
|
||||
label="Twitch Cookie(twitch_cookie)"
|
||||
style={{ width: "100%" }}
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
<Form.Input
|
||||
field="user.youtube_cookie"
|
||||
extraText={
|
||||
<div style={{ fontSize: "14px" }}>
|
||||
使用Cookies登陆YouTube帐号,可用于下载会限、私享等未登录账号无法访问的内容。
|
||||
<br />
|
||||
<style></style>可以使用 Chrome 插件「Get
|
||||
cookies.txt」来生成txt文件,请使用 Netscape 格式的
|
||||
Cookies 文本路径。
|
||||
</div>
|
||||
}
|
||||
label="TouTube Cookie(youtube_cookie)"
|
||||
style={{ width: "100%" }}
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
<Form.Input
|
||||
field="user.niconico-email"
|
||||
extraText="与您的 Niconico 账户相关联的电子邮件或电话号码。"
|
||||
|
||||
@@ -42,6 +42,29 @@ const Douyin: React.FC = () => {
|
||||
</div>
|
||||
}
|
||||
label="录制弹幕(douyin_danmaku)"
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
<Form.Input
|
||||
field="user.douyin_cookie"
|
||||
extraText={
|
||||
<div className="semi-form-field-extra">
|
||||
如需要录制抖音 www.douyin.com/user/
|
||||
类型链接,或遭到风控,请在此填入 Cookie。
|
||||
<br />
|
||||
需要__ac_nonce、__ac_signature、sessionid的值,请不要将所有
|
||||
Cookie 填入。
|
||||
</div>
|
||||
}
|
||||
placeholder="__ac_nonce=none;__ac_signature=none;sessionid=none;"
|
||||
label="抖音 Cookie(douyin_cookie)"
|
||||
style={{ width: "100%" }}
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
<Form.Select
|
||||
field="douyin_protocol"
|
||||
|
||||
@@ -259,7 +259,7 @@ const Global: React.FC = () => {
|
||||
<Form.Select
|
||||
field="lines"
|
||||
label="上传线路(lines)"
|
||||
extraText="b站上传线路选择,默认为自动模式,可手动切换为bda2, kodo, ws, qn, bldsa"
|
||||
extraText="b站上传线路选择,默认为自动模式,可手动切换为bda, bda2, ws, qn, bldsa, tx, txa"
|
||||
placeholder="AUTO(自动,默认)"
|
||||
style={{ width: "100%" }}
|
||||
fieldStyle={{
|
||||
@@ -269,11 +269,13 @@ const Global: React.FC = () => {
|
||||
showClear={true}
|
||||
>
|
||||
<Form.Select.Option value="AUTO">AUTO(自动,默认)</Form.Select.Option>
|
||||
<Form.Select.Option value="bda">bda</Form.Select.Option>
|
||||
<Form.Select.Option value="bda2">bda2</Form.Select.Option>
|
||||
<Form.Select.Option value="kodo">kodo</Form.Select.Option>
|
||||
<Form.Select.Option value="ws">ws</Form.Select.Option>
|
||||
<Form.Select.Option value="qn">qn</Form.Select.Option>
|
||||
<Form.Select.Option value="bldsa">bldsa</Form.Select.Option>
|
||||
<Form.Select.Option value="tx">tx</Form.Select.Option>
|
||||
<Form.Select.Option value="txa">txa</Form.Select.Option>
|
||||
</Form.Select>
|
||||
<Form.InputNumber
|
||||
field="threads"
|
||||
|
||||
@@ -64,12 +64,6 @@ const Huya: React.FC<Props> = (props) => {
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
rules={[
|
||||
{
|
||||
pattern: /^[A-Z]{2}(?:\d{2})?$/,
|
||||
message: "请仅输入线路别称"
|
||||
}
|
||||
]}
|
||||
showClear={true}
|
||||
>
|
||||
<Select.Option value="AL">直播线路3(AL)</Select.Option>
|
||||
@@ -109,11 +103,19 @@ const Huya: React.FC<Props> = (props) => {
|
||||
? entity["huya_imgplus"]
|
||||
: true
|
||||
}
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
<Form.Switch
|
||||
field="huya_mobile_api"
|
||||
extraText="使用移动端API可以绕过部分游客观看原画的时长限制"
|
||||
label="虎牙使用移动端API(huya_mobile_api)"
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
</Collapse.Panel>
|
||||
</>
|
||||
|
||||
@@ -11,6 +11,10 @@ const TwitCasting: React.FC = () => {
|
||||
field="twitcasting_danmaku"
|
||||
extraText="录制TwitCasting弹幕,默认关闭"
|
||||
label="录制弹幕(twitcasting_danmaku)"
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
<Form.Input
|
||||
field="twitcasting_password"
|
||||
|
||||
@@ -25,8 +25,42 @@ const Twitch: React.FC<Props> = (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,
|
||||
}}
|
||||
/>
|
||||
<Form.Input
|
||||
field="user.twitch_cookie"
|
||||
extraText={
|
||||
<div className="semi-form-field-extra">
|
||||
【仅限Turbo会员】如录制Twitch时遇见视频流中广告过多的情况,可尝试在此填入cookie,可以大幅减少视频流中的twitch广告
|
||||
<br />
|
||||
该 Cookie
|
||||
存在过期风险,Cookie过期后会在日志输出警告,请注意及时更换。
|
||||
<br />
|
||||
当 Cookie 失效,录制时将忽略
|
||||
Cookie。(经作者个人测试,可保持未过期状态四个月以上)
|
||||
<br />
|
||||
twitch_cookie 获取方式:在浏览器中打开 twitch.tv ,
|
||||
F12 调出控制台,在控制台中执行如下代码:
|
||||
<br />
|
||||
<code
|
||||
style={{ color: "blue" }}
|
||||
>{`document.cookie.split("; ").find(item => item.startsWith("auth-token="))?.split("=")[1]`}</code>
|
||||
<br />
|
||||
twitch_cookie 需要在 downloader=
|
||||
"ffmpeg" 时候才会生效。
|
||||
</div>
|
||||
}
|
||||
label="Twitch Cookie(twitch_cookie)"
|
||||
style={{ width: "100%" }}
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
</Collapse.Panel>
|
||||
</>
|
||||
|
||||
@@ -11,6 +11,24 @@ const YouTube: React.FC<Props> = (props) => {
|
||||
return (
|
||||
<>
|
||||
<Collapse.Panel header="YouTube" itemKey="youtube">
|
||||
<Form.Input
|
||||
field="user.youtube_cookie"
|
||||
extraText={
|
||||
<div style={{ fontSize: "14px" }}>
|
||||
使用Cookies登陆YouTube帐号,可用于下载会限、私享等未登录账号无法访问的内容。
|
||||
<br />
|
||||
<style></style>可以使用 Chrome 插件「Get
|
||||
cookies.txt」来生成txt文件,请使用 Netscape 格式的
|
||||
Cookies 文本路径。
|
||||
</div>
|
||||
}
|
||||
label="YouTube Cookie(youtube_cookie)"
|
||||
style={{ width: "100%" }}
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
<Form.Switch
|
||||
initValue={
|
||||
entity?.hasOwnProperty("youtube_enable_download_live")
|
||||
@@ -26,6 +44,10 @@ const YouTube: React.FC<Props> = (props) => {
|
||||
例如录制https://www.youtube.com/@NeneAmanoCh/streams,关闭后将忽略正在直播
|
||||
"
|
||||
label="下载直播(youtube_enable_download_live)"
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
<Form.Switch
|
||||
initValue={
|
||||
@@ -42,6 +64,10 @@ const YouTube: React.FC<Props> = (props) => {
|
||||
例如录制https://www.youtube.com/@NeneAmanoCh/streams,关闭后将忽略直播回放
|
||||
"
|
||||
label="下载回放(youtube_enable_download_playback)"
|
||||
fieldStyle={{
|
||||
alignSelf: "stretch",
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
<Form.Input
|
||||
field="youtube_after_date"
|
||||
|
||||
@@ -24,7 +24,7 @@ class Acfun(DownloadBase):
|
||||
cookies = dict(_did=did)
|
||||
data1 = {'sid': 'acfun.api.visitor'}
|
||||
r1 = await biliup.common.util.client.post("https://id.app.acfun.cn/rest/app/visitor/login",
|
||||
headers=self.fake_headers, data=data1, cookies=cookies, timeout=5)
|
||||
headers=self.fake_headers, data=data1, cookies=cookies)
|
||||
userid = r1.json()['userId']
|
||||
visitorst = r1.json()['acfun.api.visitor_st']
|
||||
params = {
|
||||
@@ -38,7 +38,7 @@ class Acfun(DownloadBase):
|
||||
data2 = {'authorId': rid, 'pullStreamType': 'FLV'}
|
||||
self.fake_headers['referer'] = "https://live.acfun.cn/"
|
||||
r2 = await biliup.common.util.client.post("https://api.kuaishouzt.com/rest/zt/live/web/startPlay",
|
||||
headers=self.fake_headers, data=data2, params=params, timeout=5)
|
||||
headers=self.fake_headers, data=data2, params=params)
|
||||
if r2.json().get('result') != 1:
|
||||
logger.debug(r2.json())
|
||||
return False
|
||||
|
||||
@@ -346,46 +346,45 @@ class BiliBili:
|
||||
"""
|
||||
preferred_upos_cdn = None
|
||||
if not self._auto_os:
|
||||
if lines == 'kodo':
|
||||
self._auto_os = {"os": "kodo", "query": "bucket=bvcupcdnkodobm&probe_version=20221109",
|
||||
"probe_url": "//up-na0.qbox.me/crossdomain.xml"}
|
||||
elif lines == 'bda2':
|
||||
self._auto_os = {"os": "upos", "query": "upcdn=bda2&probe_version=20221109",
|
||||
"probe_url": "//upos-sz-upcdnbda2.bilivideo.com/OK"}
|
||||
preferred_upos_cdn = 'bda2'
|
||||
elif lines == 'cs-bda2':
|
||||
if lines == 'bda':
|
||||
self._auto_os = {"os": "upos", "query": "upcdn=bda&probe_version=20221109",
|
||||
"probe_url": "//upos-cs-upcdnbda.bilivideo.com/OK"}
|
||||
preferred_upos_cdn = 'bda'
|
||||
elif lines in {'bda2', 'cs-bda2'}:
|
||||
self._auto_os = {"os": "upos", "query": "upcdn=bda2&probe_version=20221109",
|
||||
"probe_url": "//upos-cs-upcdnbda2.bilivideo.com/OK"}
|
||||
preferred_upos_cdn = 'bda2'
|
||||
elif lines == 'ws':
|
||||
self._auto_os = {"os": "upos", "query": "upcdn=ws&probe_version=20221109",
|
||||
"probe_url": "//upos-sz-upcdnws.bilivideo.com/OK"}
|
||||
"probe_url": "//upos-cs-upcdnws.bilivideo.com/OK"}
|
||||
preferred_upos_cdn = 'ws'
|
||||
elif lines == 'qn':
|
||||
self._auto_os = {"os": "upos", "query": "upcdn=qn&probe_version=20221109",
|
||||
"probe_url": "//upos-sz-upcdnqn.bilivideo.com/OK"}
|
||||
preferred_upos_cdn = 'qn'
|
||||
elif lines == 'cs-qn':
|
||||
elif lines in {'qn', 'cs-qn'}:
|
||||
self._auto_os = {"os": "upos", "query": "upcdn=qn&probe_version=20221109",
|
||||
"probe_url": "//upos-cs-upcdnqn.bilivideo.com/OK"}
|
||||
preferred_upos_cdn = 'qn'
|
||||
elif lines == 'cos':
|
||||
self._auto_os = {"os": "cos", "query": "",
|
||||
"probe_url": ""}
|
||||
elif lines == 'cos-internal':
|
||||
self._auto_os = {"os": "cos-internal", "query": "",
|
||||
"probe_url": ""}
|
||||
elif lines == 'bldsa':
|
||||
self._auto_os = {"os": "upos", "query": "upcdn=bldsa&probe_version=20221109",
|
||||
"probe_url": "//upos-cs-upcdnbldsa.bilivideo.com/OK"}
|
||||
preferred_upos_cdn = 'bldsa'
|
||||
elif lines == 'tx':
|
||||
self._auto_os = {"os": "upos", "query": "upcdn=tx&probe_version=20221109",
|
||||
"probe_url": "//upos-cs-upcdntx.bilivideo.com/OK"}
|
||||
preferred_upos_cdn = 'tx'
|
||||
elif lines == 'txa':
|
||||
self._auto_os = {"os": "upos", "query": "upcdn=txa&probe_version=20221109",
|
||||
"probe_url": "//upos-cs-upcdntxa.bilivideo.com/OK"}
|
||||
preferred_upos_cdn = 'txa'
|
||||
else:
|
||||
self._auto_os = self.probe()
|
||||
logger.info(f"线路选择 => {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'])
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+30
-27
@@ -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
|
||||
+58
-37
@@ -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)
|
||||
|
||||
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)
|
||||
@@ -72,6 +72,7 @@ def check_access(
|
||||
if hashed_password != hashed_request_password:
|
||||
return False
|
||||
|
||||
# log.debug('%r log in successed', "biliup")
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
+6
-7
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user