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 }); // 保存扫码的地址、扫码登录秘钥