From 0a0a81351422e7b3732ad957013f77ad8dabdcea Mon Sep 17 00:00:00 2001
From: zhiyu1998 <542716863@qq.com>
Date: Sat, 10 Aug 2024 00:19:13 +0800
Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20=E6=9B=B4=E6=8D=A2=E5=93=94?=
=?UTF-8?q?=E5=93=A9=E5=93=94=E5=93=A9=E4=B8=8B=E8=BD=BD=E6=96=B9=E5=BC=8F?=
=?UTF-8?q?=EF=BC=9A=E6=80=A7=E8=83=BD=E3=80=81=E7=A8=B3=E5=AE=9A=E3=80=81?=
=?UTF-8?q?=E8=BD=BB=E9=87=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 15 +++++++
apps/tools.js | 8 ++--
constants/constant.js | 6 +++
guoba.support.js | 14 ++++---
utils/bilibili.js | 92 +++++++++++++++++++++++++++++++++++++------
5 files changed, 112 insertions(+), 23 deletions(-)
diff --git a/README.md b/README.md
index c76da7c..10f5ef2 100644
--- a/README.md
+++ b/README.md
@@ -301,6 +301,21 @@ aiApiKey: '' # 用于识图的api key,kimi接口申请:https://platform.moon
- Linux教程:https://pwa.sspai.com/post/83345
- Windows教程:https://github.com/nilaoda/BBDown/issues/305
+### 📺 关于使用 哔哩哔哩 下载方式
+
+- 轻量
+
+```shell
+apt install wget
+apt install axel
+```
+
+- 稳定(无须安装任何东西)
+
+- 性能
+```shell
+apt install aria2
+```
## 🤺 R插件交流群
diff --git a/apps/tools.js b/apps/tools.js
index ba68ec6..6b72aa1 100644
--- a/apps/tools.js
+++ b/apps/tools.js
@@ -210,7 +210,7 @@ export class tools extends plugin {
// 加载 BBDown 的CDN配置
this.biliCDN = this.toolsConfig.biliCDN;
// 加载哔哩哔哩是否使用Aria2
- this.biliUseAria2 = this.toolsConfig.biliUseAria2;
+ this.biliDownloadMethod = this.toolsConfig.biliDownloadMethod;
// 加载抖音Cookie
this.douyinCookie = this.toolsConfig.douyinCookie;
// 加载抖音是否压缩
@@ -629,7 +629,7 @@ export class tools extends plugin {
// 下载视频
await startBBDown(url, path, {
biliSessData: this.biliSessData,
- biliUseAria2: this.biliUseAria2,
+ biliUseAria2: this.biliDownloadMethod === 1,
biliCDN: BILI_CDN_SELECT_LIST.find(item => item.value === this.biliCDN)?.sign,
});
// 发送视频
@@ -1768,7 +1768,7 @@ export class tools extends plugin {
}),
1000,
),
- this.biliUseAria2,
+ this.biliDownloadMethod,
this.videoDownloadConcurrency
),
downloadBFile(
@@ -1781,7 +1781,7 @@ export class tools extends plugin {
}),
1000,
),
- this.biliUseAria2,
+ this.biliDownloadMethod,
this.videoDownloadConcurrency
),
]).then(data => {
diff --git a/constants/constant.js b/constants/constant.js
index e55cd33..72d9c83 100644
--- a/constants/constant.js
+++ b/constants/constant.js
@@ -126,4 +126,10 @@ export const BILI_CDN_SELECT_LIST = Object.freeze([
{ label: '华为CDN', value: 3, sign: BILI_CDN_TEMPLATE.replace('{}', 'hw') },
{ label: '阿卡迈(海外)', value: 4 , sign: BILI_CDN_TEMPLATE.replace('{}', 'akamai')},
{ label: 'HK-CDN', value: 5, sign: BILI_CDN_TEMPLATE.replace('{}', 'aliov') }
+]);
+
+export const BILI_DOWNLOAD_METHOD = Object.freeze([
+ { label: '稳定(原生)', value: 0 },
+ { label: '性能(Aria2)', value: 1 },
+ { label: '轻量(axel/wget)', value: 2 }
]);
\ No newline at end of file
diff --git a/guoba.support.js b/guoba.support.js
index d4c467e..470e7ef 100644
--- a/guoba.support.js
+++ b/guoba.support.js
@@ -1,7 +1,7 @@
import path from "path";
import model from "./model/index.js";
import _ from "lodash";
-import { BILI_CDN_SELECT_LIST } from "./constants/constant.js";
+import { BILI_CDN_SELECT_LIST, BILI_DOWNLOAD_METHOD } from "./constants/constant.js";
const _path = process.cwd() + "/plugins/rconsole-plugin";
export function supportGuoba() {
@@ -140,12 +140,14 @@ export function supportGuoba() {
}
},
{
- field: "tools.biliUseAria2",
- label: "使用Aria2下载",
+ field: "tools.biliDownloadMethod",
+ label: "bili下载方式",
bottomHelpMessage:
- "【默认不开启】如果不爱折腾就使用默认下载方式,如果喜欢折腾就开启,开启后更加强劲,宽带直接拉满,Debian和Ubuntu用户直接使用命令安装:apt-get install aria2",
- component: "Switch",
- required: false,
+ "哔哩哔哩的下载方式:默认使用原生稳定的下载方式,如果你在乎内存可以使用轻量的wget和axel下载方式,如果在乎性能可以使用Aria2下载",
+ component: "Select",
+ componentProps: {
+ options: BILI_DOWNLOAD_METHOD,
+ }
},
{
field: "tools.douyinCookie",
diff --git a/utils/bilibili.js b/utils/bilibili.js
index 03a392d..a1916e9 100644
--- a/utils/bilibili.js
+++ b/utils/bilibili.js
@@ -2,6 +2,7 @@ import fs from "node:fs";
import axios from 'axios'
import child_process from 'node:child_process'
import util from "util";
+import path from "path";
import {
BILI_BVID_TO_CID,
BILI_DYNAMIC,
@@ -11,7 +12,7 @@ import {
BILI_VIDEO_INFO
} from "../constants/tools.js";
import { mkdirIfNotExists } from "./file.js";
-import { spawn } from 'child_process';
+import { exec, spawn } from 'child_process';
import qrcode from "qrcode"
const biliHeaders = {
@@ -25,15 +26,21 @@ const biliHeaders = {
* @param url 下载链接
* @param fullFileName 文件名
* @param progressCallback 下载进度
- * @param isAria2 是否使用aria2
+ * @param biliDownloadMethod 下载方式 {BILI_DOWNLOAD_METHOD}
* @param videoDownloadConcurrency 视频下载并发
* @returns {Promise}
*/
-export async function downloadBFile(url, fullFileName, progressCallback, isAria2 = false, videoDownloadConcurrency = 1) {
- if (isAria2) {
+export async function downloadBFile(url, fullFileName, progressCallback, biliDownloadMethod = 0, videoDownloadConcurrency = 1) {
+ if (biliDownloadMethod === 0) {
+ // 原生
+ return normalDownloadBFile(url, fullFileName, progressCallback);
+ }
+ if (biliDownloadMethod === 1) {
+ // 性能 Aria2
return aria2DownloadBFile(url, fullFileName, progressCallback, videoDownloadConcurrency);
} else {
- return normalDownloadBFile(url, fullFileName, progressCallback);
+ // 轻量
+ return axelDownloadBFile(url, fullFileName, progressCallback, videoDownloadConcurrency);
}
}
@@ -42,7 +49,7 @@ export async function downloadBFile(url, fullFileName, progressCallback, isAria2
* @param url
* @param fullFileName
* @param progressCallback
- * @returns {Promise}
+ * @returns {Promise<{fullFileName: string, totalLen: number}>}
*/
async function normalDownloadBFile(url, fullFileName, progressCallback) {
return axios
@@ -80,7 +87,7 @@ async function normalDownloadBFile(url, fullFileName, progressCallback) {
* @param fullFileName
* @param progressCallback
* @param videoDownloadConcurrency
- * @returns {Promise}
+ * @returns {Promise<{fullFileName: string, totalLen: number}>}
*/
async function aria2DownloadBFile(url, fullFileName, progressCallback, videoDownloadConcurrency) {
return new Promise((resolve, reject) => {
@@ -93,8 +100,8 @@ async function aria2DownloadBFile(url, fullFileName, progressCallback, videoDown
'--console-log-level=warn', // 减少日志 verbosity
'--download-result=hide', // 隐藏下载结果概要
'--header', 'referer: https://www.bilibili.com', // 添加自定义标头
- `--max-connection-per-server=${videoDownloadConcurrency}`, // 每个服务器的最大连接数
- `--split=${videoDownloadConcurrency}`, // 分成 6 个部分进行下载
+ `--max-connection-per-server=${ videoDownloadConcurrency }`, // 每个服务器的最大连接数
+ `--split=${ videoDownloadConcurrency }`, // 分成 6 个部分进行下载
url
];
@@ -117,7 +124,7 @@ async function aria2DownloadBFile(url, fullFileName, progressCallback, videoDown
// 处理aria2c的stderr以捕获错误
aria2c.stderr.on('data', (data) => {
- console.error(`aria2c error: ${data}`);
+ console.error(`aria2c error: ${ data }`);
});
// 处理进程退出
@@ -125,7 +132,65 @@ async function aria2DownloadBFile(url, fullFileName, progressCallback, videoDown
if (code === 0) {
resolve({ fullFileName, totalLen });
} else {
- reject(new Error(`aria2c exited with code ${code}`));
+ reject(new Error(`aria2c exited with code ${ code }`));
+ }
+ });
+ });
+}
+
+/**
+ * 使用 C 语言写的轻量级下载工具 Axel 进行下载
+ * @param url
+ * @param fullFileName
+ * @param progressCallback
+ * @param videoDownloadConcurrency
+ * @returns {Promise<{fullFileName: string, totalLen: number}>}
+ */
+async function axelDownloadBFile(url, fullFileName, progressCallback, videoDownloadConcurrency) {
+ return new Promise((resolve, reject) => {
+ // 构建路径
+ fullFileName = path.resolve(fullFileName);
+
+ // 构建 -H 参数
+ const headerParams = Object.entries(biliHeaders).map(
+ ([key, value]) => `--header="${ key }: ${ value }"`
+ ).join(' ');
+
+ let command = '';
+ let downloadTool = 'wget';
+ if (videoDownloadConcurrency === 1) {
+ // wget 命令
+ command = `${ downloadTool } -O ${ fullFileName } ${ headerParams } '${ url }'`;
+ } else {
+ // AXEL 命令行
+ downloadTool = 'axel';
+ command = `${ downloadTool } -n ${ videoDownloadConcurrency } -o ${ fullFileName } ${ headerParams } '${ url }'`;
+ }
+
+ // 执行命令
+ const axel = exec(command);
+ logger.info(`[R插件][axel/wget] 执行命令:${ downloadTool } 下载方式为:${ downloadTool === 'wget' ? '单线程' : '多线程' }`);
+
+ axel.stdout.on('data', (data) => {
+ const match = data.match(/(\d+)%/);
+ if (match) {
+ const progress = parseInt(match[1], 10) / 100;
+ progressCallback?.(progress);
+ }
+ });
+
+ axel.stderr.on('data', (data) => {
+ logger.info(`[R插件][${ downloadTool }]: ${ data }`);
+ });
+
+ axel.on('close', (code) => {
+ if (code === 0) {
+ resolve({
+ fullFileName,
+ totalLen: fs.statSync(fullFileName).size,
+ });
+ } else {
+ reject(new Error(`[R插件][${ downloadTool }] 错误:${ code }`));
}
});
});
@@ -270,7 +335,7 @@ export async function getBiliVideoWithSession(bvid, cid, SESSDATA) {
fetch(BILI_PLAY_STREAM.replace("{bvid}", bvid).replace("{cid}", cid), {
headers: {
// SESSDATA 字段
- Cookie: `SESSDATA=${SESSDATA}`
+ Cookie: `SESSDATA=${ SESSDATA }`
}
})
.then(res => res.json())
@@ -362,7 +427,8 @@ export async function getDynamic(dynamicId, SESSDATA) {
* refresh_token
* }>}
*/
-export async function getScanCodeData(qrcodeSavePath = 'qrcode.png', detectTime = 10, hook = () => {}) {
+export async function getScanCodeData(qrcodeSavePath = 'qrcode.png', detectTime = 10, hook = () => {
+}) {
try {
const resp = await axios.get(BILI_SCAN_CODE_GENERATE, { ...biliHeaders });
// 保存扫码的地址、扫码登录秘钥