feat: 1.6.6 [#I9E25J] 增强downloadVideo下载函数功能 & 删除青年大学习 & 格式化PR代码

1. `downloadVideo`函数现在支持多线程下载,速度巨tm快,比之前的速度快了200%以上
2. `青年大学习`功能退役
3. 格式化PR代码
This commit is contained in:
zhiyu1998 2024-04-04 11:53:36 +08:00
parent 98cff7eec1
commit b70b5f5e89
4 changed files with 139 additions and 159 deletions

View File

@ -46,10 +46,6 @@ export class query extends plugin {
reg: "^#累了$",
fnc: "cospro",
},
{
reg: "^#青年大学习$",
fnc: "youthLearning",
},
{
reg: "^#搜书(.*)$",
fnc: "searchBook",
@ -181,105 +177,6 @@ export class query extends plugin {
return true;
}
// 青年大学习
async youthLearning(e) {
await axios
.get(
"https://qczj.h5yunban.com/qczj-youth-learning/cgi-bin/common-api/course/current",
{
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 Edg/95.0.1020.53",
},
},
)
.then(resp => {
// logger.info(resp.data);
return resp.data.result.uri.replace("index.html", "m.html");
})
.then(async uri => {
axios
.get(uri, {
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 Edg/95.0.1020.53",
},
})
.then(resp => {
const content = resp.data;
const resList = content.match(/<div class="w\d option" (.*)><\/div>/g);
const valueList = resList.map(item => {
return item.match(/data-a="(\d+)"/)[1];
});
let result = [];
// 转换ABCD
const digitToLetter = {
0: "A",
1: "B",
2: "C",
3: "D",
};
for (let i = 0; i < valueList.length; i += 4) {
const group = valueList.slice(i, i + 4);
if (group.length < 4) {
continue;
}
const letters = group
.map((d, indx) => {
if (d === "1") {
return digitToLetter[indx];
}
})
.join("");
result.push(letters);
}
// 封装答案
let ans = "";
for (let i = 0; i < result.length; i++) {
ans += `${ i + 1 }. ${ result[i] }\n`;
}
e.reply(ans);
const imgMatch = uri.match(/[^\/]+/g);
const imgId = imgMatch[imgMatch.length - 2];
axios
.get(`https://h5.cyol.com/special/daxuexi/${ imgId }/images/end.jpg`, {
headers: {
"User-Agent":
"Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Mobile Safari/537.36",
},
responseType: "stream",
})
.then(resp => {
const filePath = "./youthLearning.png";
const writer = fs.createWriteStream(filePath);
resp.data.pipe(writer);
return new Promise((resolve, reject) => {
writer.on("finish", () => {
writer.close(() => {
resolve(filePath);
});
});
writer.on("error", err => {
fs.unlink(filePath, () => {
reject(err);
});
});
});
})
.then(filePath => {
e.reply(segment.image(fs.readFileSync(filePath)));
fs.unlinkSync(filePath, err => {
if (err) throw err;
logger.error("删除青年大学习文件失败");
});
});
});
});
return true;
}
async cospro(e) {
let [res1, res2] = (
await Promise.allSettled([

View File

@ -186,6 +186,8 @@ export class tools extends plugin {
});
// 并发队列
this.queue = new PQueue({concurrency: Number(this.toolsConfig.queueConcurrency)});
// 视频下载的并发数量
this.videoDownloadConcurrency = this.toolsConfig.videoDownloadConcurrency;
}
// 翻译插件
@ -260,7 +262,7 @@ export class tools extends plugin {
);
const path = `${ this.getCurDownloadPath(e) }/temp.mp4`;
await this.downloadVideo(resUrl).then(() => {
this.video_file(e, path)
this.sendVideoToUpload(e, path)
});
} else if (urlType === "image") {
// 无水印图片列表
@ -464,7 +466,7 @@ export class tools extends plugin {
.then(data => {
this.downBili(`${ path }temp`, data.videoUrl, data.audioUrl)
.then(_ => {
this.video_file(e, `${ path }temp.mp4`)
this.sendVideoToUpload(e, `${ path }temp.mp4`)
})
.catch(err => {
logger.error(err);
@ -754,7 +756,7 @@ export class tools extends plugin {
parseM3u8(res.urlM3u8s[res.urlM3u8s.length - 1]).then(res2 => {
downloadM3u8Videos(res2.m3u8FullUrls, path).then(_ => {
mergeAcFileToMp4(res2.tsNames, path, `${ path }out.mp4`).then(_ => {
this.video_file(e, `${ path }out.mp4`)
this.sendVideoToUpload(e, `${ path }out.mp4`)
});
});
});
@ -814,7 +816,7 @@ export class tools extends plugin {
// 创建文件,如果不存在
path = `${ this.getCurDownloadPath(e) }/`;
}
this.video_file(e, `${ path }/temp.mp4`)
this.sendVideoToUpload(e, `${ path }/temp.mp4`)
});
return true;
} else if (type === "normal") {
@ -1093,7 +1095,7 @@ export class tools extends plugin {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"referer": "https://weibo.com/",
}).then(path => {
this.video_file(e, `${ path }/temp.mp4`)
this.sendVideoToUpload(e, `${ path }/temp.mp4`)
});
} catch (err) {
e.reply("视频资源获取失败");
@ -1127,7 +1129,7 @@ export class tools extends plugin {
// 视频https://www.kuaishou.com/short-video/3xhjgcmir24m4nm
const url = adapter.video;
this.downloadVideo(url).then(path => {
this.video_file(e, `${ path }/temp.mp4`)
this.sendVideoToUpload(e, `${ path }/temp.mp4`)
});
} else {
e.reply("解析失败:无法获取到资源");
@ -1262,7 +1264,7 @@ export class tools extends plugin {
// 暂时选取分辨率较低的video进行解析
const videoUrl = resolutions[i].url;
this.downloadVideo(videoUrl).then(path => {
this.video_file(e, `${ path }/temp.mp4`)
this.sendVideoToUpload(e, `${ path }/temp.mp4`)
});
break;
}
@ -1377,7 +1379,7 @@ export class tools extends plugin {
e.reply([segment.image(cover), `识别:微视,${ title }`]);
this.downloadVideo(noWatermarkDownloadUrl).then(path => {
this.video_file(e, `${ path }/temp.mp4`)
this.sendVideoToUpload(e, `${ path }/temp.mp4`)
});
} catch (err) {
logger.error(err);
@ -1441,7 +1443,7 @@ export class tools extends plugin {
}
if (shortVideoInfo.noWatermarkDownloadUrl) {
this.downloadVideo(shortVideoInfo.noWatermarkDownloadUrl).then(path => {
this.video_file(e, `${ path }/temp.mp4`)
this.sendVideoToUpload(e, `${ path }/temp.mp4`)
});
}
} catch (error) {
@ -1529,46 +1531,113 @@ export class tools extends plugin {
}
/**
* 工具根URL据下载视频 / 音频
* @param url 下载地址
* @param isProxy 是否需要魔法
* @param headers 覆盖头节点
* @returns {Promise<unknown>}
* 工具根据URL多线程下载视频 / 音频
* @param url
* @param isProxy
* @param headers
* @param numThreads
* @returns {Promise<void>}
*/
async downloadVideo(url, isProxy = false, headers = null) {
async downloadVideo(url, isProxy = false, headers = null, numThreads = 1) {
const { groupPath, target } = this.getGroupPathAndTarget.call(this);
await mkdirIfNotExists(groupPath);
const userAgent = "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Mobile Safari/537.36";
// 用户设置优先策略逻辑解释如果使用了这个函数优先查看用户是否设置了大于1的线程如果设置了优先使用没设置就开发者设定的函数设置
numThreads = this.videoDownloadConcurrency !== 1 ? this.videoDownloadConcurrency : numThreads;
const userAgent =
"Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Mobile Safari/537.36";
const axiosConfig = {
headers: headers || { "User-Agent": userAgent },
responseType: "stream",
...(isProxy && {
httpAgent: tunnel.httpOverHttp({
proxy: { host: this.proxyAddr, port: this.proxyPort },
// 如果是用户设置了单线程,则不分片下载
if (numThreads === 1) {
const axiosConfig = {
headers: headers || { "User-Agent": userAgent },
responseType: "stream",
...(isProxy && {
httpAgent: tunnel.httpOverHttp({
proxy: { host: this.proxyAddr, port: this.proxyPort },
}),
httpsAgent: tunnel.httpsOverHttp({
proxy: { host: this.proxyAddr, port: this.proxyPort },
}),
}),
httpsAgent: tunnel.httpsOverHttp({
proxy: { host: this.proxyAddr, port: this.proxyPort },
}),
}),
};
};
try {
await checkAndRemoveFile(target);
try {
await checkAndRemoveFile(target);
const res = await axios.get(url, axiosConfig);
logger.mark(`开始下载: ${ url }`);
const writer = fs.createWriteStream(target);
res.data.pipe(writer);
const res = await axios.get(url, axiosConfig);
logger.mark(`开始下载: ${ url }`);
const writer = fs.createWriteStream(target);
res.data.pipe(writer);
return new Promise((resolve, reject) => {
writer.on("finish", () => resolve(groupPath));
writer.on("error", reject);
});
} catch (err) {
logger.error(`下载视频发生错误!\ninfo:${ err }`);
return new Promise((resolve, reject) => {
writer.on("finish", () => resolve(groupPath));
writer.on("error", reject);
});
} catch (err) {
logger.error(`下载视频发生错误!\ninfo:${ err }`);
}
} else {
// 多线程分片下载
try {
await checkAndRemoveFile(target);
const sizeRes = await axios.head(url);
const contentLength = sizeRes.headers['content-length'];
const chunkSize = Math.ceil(contentLength / numThreads);
let promises = [];
for (let i = 0; i < numThreads; i++) {
let start = i * chunkSize;
let end = (i + 1) * chunkSize - 1;
if (i === numThreads - 1) end = ''; // Last chunk goes till the end
const axiosConfig = {
headers: {
"User-Agent": userAgent,
"Range": `bytes=${start}-${end}`,
...headers
},
responseType: 'stream',
...(isProxy && {
httpAgent: tunnel.httpOverHttp({
proxy: { host: 'proxyAddress', port: 'proxyPort' },
}),
httpsAgent: tunnel.httpsOverHttp({
proxy: { host: 'proxyAddress', port: 'proxyPort' },
}),
}),
};
promises.push(axios.get(url, axiosConfig));
}
const writer = fs.createWriteStream(target, { flags: 'a' });
// 同时下载所有部分
await Promise.all(promises.map(async (promise, index) => {
const res = await promise;
logger.mark(`开始下载部分: ${index + 1}`);
res.data.pipe(writer, { end: false });
await new Promise((resolve, reject) => {
res.data.on('end', () => {
logger.mark(`部分 ${index + 1} 下载完成`);
resolve();
});
res.data.on('error', reject);
});
}));
// 注意这里不应该在每个分块结束后立即调用writer.end()
// 我们只在所有分块都已经pipe完毕后调用writer.end()
writer.end();
await new Promise((resolve, reject) => {
writer.on('finish', () => {
logger.mark(`所有部分下载完成,文件已保存至 ${target}`);
resolve(target); // 返回目标文件路径
});
writer.on('error', reject);
});
} catch (err) {
logger.error(`下载视频发生错误!\ninfo:${err}`);
// 处理或抛出错误
throw err;
}
}
}
@ -1629,20 +1698,21 @@ export class tools extends plugin {
}
/**
* 上传视频文件
* @param {*} e
* @param {*} path
* 发送转上传视频
* @param e 交互事件
* @param path 视频所在路径
* @param videoSizeLimit 发送转上传视频的大小限制默认70MB
*/
async video_file(e, path) {
if(!fs.existsSync(path)) return e.reply('视频不存在')
const stats = fs.statSync(path)
const Video_size = (stats.size / (1024 * 1024)).toFixed(2)
if (Video_size > 70) {
e.reply(`当前视频大小:${ Video_size }MB\n大于设置的最大限制,\n改为上传群文件`)
if(this.e.bot?.sendUni) {
this.e.group.fs.upload(path)
async sendVideoToUpload(e, path, videoSizeLimit = 70) {
if (!fs.existsSync(path)) return e.reply('视频不存在');
const stats = fs.statSync(path);
const videoSize = (stats.size / (1024 * 1024)).toFixed(2);
if (videoSize > videoSizeLimit) {
e.reply(`当前视频大小:${ videoSize }MB\n大于设置的最大限制,\n改为上传群文件`);
if (this.e.bot?.sendUni) {
this.e.group.fs.upload(path);
} else {
this.e.group.sendFile(path)
this.e.group.sendFile(path);
}
} else {
e.reply(segment.video(path));

View File

@ -11,4 +11,6 @@ biliDuration: 480 # 哔哩哔哩限制的最大视频时长默认8分钟
douyinCookie: '' # douyin's cookie, 格式odin_tt=xxx;sessionid_ss=xxx;ttwid=xxx;passport_csrf_token=xxx;msToken=xxx;
queueConcurrency: 1 # 【目前只涉及哔哩哔哩的下载】根据服务器性能设置可以并发下载的个数如果你的服务器比较强劲就选择4~12较弱就一个一个下载选择1
queueConcurrency: 1 # 【目前只涉及哔哩哔哩的下载】根据服务器性能设置可以并发下载的个数如果你的服务器比较强劲就选择4~12较弱就一个一个下载选择1
videoDownloadConcurrency: 1 # 下载视频是否使用多线程如果不使用默认是1如果使用根据服务器进行选择如果不确定是否可以用4即可高性能服务器随意4~12都可以看CPU的实力

View File

@ -121,15 +121,26 @@ export function supportGuoba() {
},
{
field: "tools.queueConcurrency",
label: "并发下载个数",
label: "允许多用户下载个数",
bottomHelpMessage:
"【目前只涉及哔哩哔哩的下载】根据服务器性能设置可以并发下载的个数如果你的服务器比较强劲就选择4~12较弱就一个一个下载选择1",
"【目前只涉及哔哩哔哩的下载功能】根据服务器性能设置可以并发下载的个数如果你的服务器比较强劲就选择4~12较弱就一个一个下载选择1",
component: "Input",
required: false,
componentProps: {
placeholder: "如果你的服务器比较强劲就写4~12比如4就是可以4个人同时下载较弱就一个一个下载写1",
},
},
{
field: "tools.videoDownloadConcurrency",
label: "使用下载视频的并发个数",
bottomHelpMessage:
"与【允许多用户下载个数】不同这个功能影响下载速度。默认是1使用根据服务器性能进行选择如果不确定是否可以用1即可高性能服务器随意4~12都可以看CPU的实力",
component: "Input",
required: false,
componentProps: {
placeholder: "不确定用1即可高性能服务器随意4~12都可以看CPU的实力",
},
},
],
getConfigData() {
const toolsData = {