Fix vld bugs

This commit is contained in:
the1812 2019-06-30 16:16:32 +08:00
parent 9f9077d047
commit 7f93200f86
11 changed files with 802 additions and 986 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -154,7 +154,7 @@ namespace BilibiliEvolved.Build
}
return source;
};
input = RegexReplacer.Replace(input, @"import (.*) from ([^;]*)", match =>
input = RegexReplacer.Replace(input, @"import (.*) from ([^\r\n;]*)", match =>
{
var imported = match.Groups[1].Value.Replace(" as ", ":");
var source = convertToRuntimeSource(match.Groups[2].Value);
@ -173,6 +173,7 @@ namespace BilibiliEvolved.Build
};
})();";
}
// Console.WriteLine(input);
return new UglifyJs().Run(input);
}
}

Binary file not shown.

View File

@ -1,6 +1,6 @@
{
"name": "bilibili-evolved-video-link-downloader",
"version": "1.8.1",
"version": "1.8.2",
"description": "Bilibili Evolved 视频链接下载器",
"main": "video-link-downloader.js",
"bin": {

View File

@ -64,8 +64,8 @@ class Downloader {
headers: {
Range: range,
Origin: "https://www.bilibili.com",
Referer: "https://www.bilibili.com",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36",
Referer: this.inputData.referer || "https://www.bilibili.com",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0",
},
}).on("complete", response => {
if (response.statusCode.toString()[0] === "2") {
@ -78,15 +78,15 @@ class Downloader {
this.progressMap.set(req, this.progressMap.get(req) + data.length);
this.updateProgress();
}).on("error", error => {
// stream.close();
// fs.unlinkSync(partFilename);
// this.progressMap.delete(req);
// this.progressMap.set(makeRequest(), 0);
// this.updateProgress();
this.progressMap.delete(req);
this.progressMap.set(makeRequest(), 0);
this.updateProgress();
reject(`\n片段下载失败: ${error}`);
});
console.log(`created stream ${partFilename}`);
req.pipe(fs.createWriteStream(partFilename));
req.pipe(fs.createWriteStream(partFilename, {
autoClose: true
}));
return req;
};
this.progressMap.set(makeRequest(), 0);

View File

@ -6,31 +6,29 @@ import fs = require("fs");
import ProgressBar = require("progress");
import "colors";
interface Fragment
{
length: number;
size: number;
url: string;
backupUrls: string[];
interface Fragment {
length: number;
size: number;
url: string;
backupUrls: string[];
}
interface InputData
{
fragments: Fragment[];
title: string;
totalSize: number;
interface InputData {
fragments: Fragment[];
title: string;
totalSize: number;
referer: string;
}
interface Settings
{
parts: number;
info: string;
output: string;
interface Settings {
parts: number;
info: string;
output: string;
}
const optionDefinitions = [
{ name: 'danmaku', alias: 'd', defaultValue: false, type: Boolean },
{ name: 'info', alias: 'i', defaultOption: true, type: String, defaultValue: undefined },
{ name: 'parts', alias: 'p', type: Number, defaultValue: 12 },
{ name: 'output', alias: 'o', type: String, defaultValue: '.' },
{ name: 'danmaku', alias: 'd', defaultValue: false, type: Boolean },
{ name: 'info', alias: 'i', defaultOption: true, type: String, defaultValue: undefined },
{ name: 'parts', alias: 'p', type: Number, defaultValue: 12 },
{ name: 'output', alias: 'o', type: String, defaultValue: '.' },
];
const commandLineOptions = commandLineArgs(optionDefinitions) as Settings;
let options = commandLineOptions;
@ -41,277 +39,224 @@ options.parts = Math.round(options.parts);
// options = Object.assign(jsonOptions, options);
// }
if (options.parts < 1)
{
console.error("分段数不能小于1".red);
process.exit();
if (options.parts < 1) {
console.error("分段数不能小于1".red);
process.exit();
}
class Downloader
{
static workingDownloader: Downloader | null = null;
private progressMap = new Map<request.Request | string, number>();
private progressBar = new ProgressBar(":percent [:bar]", {
total: this.inputData.totalSize,
width: 20,
incomplete: ' ',
class Downloader {
static workingDownloader: Downloader | null = null;
private progressMap = new Map<request.Request | string, number>();
private progressBar = new ProgressBar(":percent [:bar]", {
total: this.inputData.totalSize,
width: 20,
incomplete: ' ',
});
private extension: string;
private title: string | string[];
constructor(
private inputData: InputData,
) { }
private getExtension(fragment: Fragment) {
this.extension = fragment.url.includes(".flv") ? ".flv" : ".mp4";
}
private updateProgress() {
const progress = this.progressMap ?
[...this.progressMap.values()].reduce((a, b) => a + b, 0) / this.inputData.totalSize : 0;
this.progressBar.update(progress);
}
cancelDownload() {
[...this.progressMap.keys()].forEach(it => {
if (typeof it !== "string") {
it.abort();
}
});
private extension: string;
private title: string | string[];
constructor(
private inputData: InputData,
)
{ }
private getExtension(fragment: Fragment)
{
this.extension = fragment.url.includes(".flv") ? ".flv" : ".mp4";
}
private updateProgress()
{
const progress = this.progressMap ?
[...this.progressMap.values()].reduce((a, b) => a + b, 0) / this.inputData.totalSize : 0;
this.progressBar.update(progress);
}
cancelDownload()
{
[...this.progressMap.keys()].forEach(it =>
{
if (typeof it !== "string")
{
it.abort();
}
this.progressBar.terminate();
const files = fs.readdirSync(".");
const parts = files.filter(it => it.includes(this.inputData.title));
parts.forEach(file => fs.unlinkSync(file));
console.log("已取消下载".blue);
}
private downloadFragmentPart(url: string, range: string, partFilename: string) {
return new Promise((resolve, reject) => {
const makeRequest = () => {
const req = request({
url: url,
method: "GET",
headers: {
Range: range,
Origin: "https://www.bilibili.com",
Referer: this.inputData.referer || "https://www.bilibili.com",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0",
},
}).on("complete", response => {
if (response.statusCode.toString()[0] === "2") {
resolve(response);
}
else {
reject(`请求失败: ${response.statusCode}`);
}
}).on("data", data => {
this.progressMap.set(req, this.progressMap.get(req)! + data.length);
this.updateProgress();
}).on("error", error => {
// fs.unlinkSync(partFilename);
this.progressMap.delete(req);
this.progressMap.set(makeRequest(), 0);
this.updateProgress();
reject(`\n片段下载失败: ${error}`);
});
this.progressBar.terminate();
const files = fs.readdirSync(".");
const parts = files.filter(it => it.includes(this.inputData.title));
parts.forEach(file => fs.unlinkSync(file));
console.log("已取消下载".blue);
req.pipe(fs.createWriteStream(partFilename, {
autoClose: true
}))
return req
};
this.progressMap.set(makeRequest(), 0);
});
}
private async downloadFragment(fragment: Fragment, index: number = -1) {
const partialLength = Math.round(fragment.size / options.parts);
const title = (index === -1 ? this.inputData.title : this.inputData.title + " - " + index.toString());
if (fs.existsSync(title + this.extension)) {
this.progressBar.terminate();
console.log(`跳过了已存在的文件 ${title + this.extension}`);
return title;
}
private downloadFragmentPart(url: string, range: string, partFilename: string)
{
return new Promise((resolve, reject) =>
{
const makeRequest = () =>
{
const req = request({
url: url,
method: "GET",
headers: {
Range: range,
Origin: "https://www.bilibili.com",
Referer: "https://www.bilibili.com",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36",
},
// maxAttempts: 8,
// retryDelay: 1000,
}).on("complete", response =>
{
if (response.statusCode.toString()[0] === "2")
{
resolve(response);
}
else
{
reject(`请求失败: ${response.statusCode}`);
}
}).on("data", data =>
{
this.progressMap.set(req, this.progressMap.get(req)! + data.length);
this.updateProgress();
}).on("error", error =>
{
// stream.close();
// fs.unlinkSync(partFilename);
// this.progressMap.delete(req);
// this.progressMap.set(makeRequest(), 0);
// this.updateProgress();
reject(`\n片段下载失败: ${error}`);
});
req.pipe(fs.createWriteStream(partFilename))
return req
};
this.progressMap.set(makeRequest(), 0);
});
let startByte = 0;
let part = 0;
const promises = [];
while (startByte < fragment.size) {
const partFilename = `${title}.part${part}`;
// if (fs.existsSync(partFilename))
// {
// this.progressMap.set(partFilename, partialLength);
// this.updateProgress();
// }
// else
// {
const endByte = Math.min(fragment.size - 1, Math.round(startByte + partialLength));
const range = `bytes=${startByte}-${endByte}`;
promises.push(this.downloadFragmentPart(fragment.url, range, partFilename));
// }
startByte = Math.round(startByte + partialLength) + 1;
part++;
}
private async downloadFragment(fragment: Fragment, index: number = -1)
{
const partialLength = Math.round(fragment.size / options.parts);
const title = (index === -1 ? this.inputData.title : this.inputData.title + " - " + index.toString());
if (fs.existsSync(title + this.extension))
{
this.progressBar.terminate();
console.log(`跳过了已存在的文件 ${title + this.extension}`);
return title;
}
let startByte = 0;
let part = 0;
const promises = [];
while (startByte < fragment.size)
{
const partFilename = `${title}.part${part}`;
// if (fs.existsSync(partFilename))
// {
// this.progressMap.set(partFilename, partialLength);
// this.updateProgress();
// }
// else
// {
const endByte = Math.min(fragment.size - 1, Math.round(startByte + partialLength));
const range = `bytes=${startByte}-${endByte}`;
promises.push(this.downloadFragmentPart(fragment.url, range, partFilename));
// }
startByte = Math.round(startByte + partialLength) + 1;
part++;
}
await Promise.all(promises);
return title;
await Promise.all(promises);
return title;
}
async download() {
console.log(`正在下载: ${this.inputData.title}`.green);
Downloader.workingDownloader = this;
this.progressBar.render();
const [fragment] = this.inputData.fragments;
this.getExtension(fragment);
if (this.inputData.fragments.length === 1) {
this.title = await this.downloadFragment(fragment);
}
async download()
{
console.log(`正在下载: ${this.inputData.title}`.green);
Downloader.workingDownloader = this;
this.progressBar.render();
const [fragment] = this.inputData.fragments;
this.getExtension(fragment);
if (this.inputData.fragments.length === 1)
{
this.title = await this.downloadFragment(fragment);
}
else
{
this.title = await Promise.all(this.inputData.fragments.map((f, i) => this.downloadFragment(f, i)));
}
else {
this.title = await Promise.all(this.inputData.fragments.map((f, i) => this.downloadFragment(f, i)));
}
private async mergeFragment(title: string, index = -1)
{
const dest = title + this.extension;
if (!this.progressBar.complete)
{
this.progressBar.update(1);
this.progressBar.terminate();
}
if (fs.existsSync(dest))
{
return dest;
}
if (index !== -1)
{
console.log(`正在合并片段${index.toString()}...`.blue);
}
else
{
console.log("正在合并文件...".blue);
}
if (options.parts === 1)
{
fs.renameSync(title + ".part0", dest);
}
else
{
if (fs.existsSync(dest))
{
fs.unlinkSync(dest);
}
const files = fs.readdirSync(".");
const parts = files.filter(it => it.includes(title + ".part"));
const partRegex = /.*\.part([\d]+)/;
parts.sort((a, b) =>
{
const partA = parseInt(a.replace(partRegex, "$1"));
const partB = parseInt(b.replace(partRegex, "$1"));
return partA - partB;
}).forEach(file =>
{
const buffer = fs.readFileSync(file);
fs.appendFileSync(dest, buffer);
});
parts.forEach(file => fs.unlinkSync(file));
}
return dest;
}
private async mergeFragment(title: string, index = -1) {
const dest = title + this.extension;
if (!this.progressBar.complete) {
this.progressBar.update(1);
this.progressBar.terminate();
}
async merge()
{
let result: string | string[];
if (typeof this.title === "string")
{
result = await this.mergeFragment(this.title);
}
else
{
result = await Promise.all(this.title.map((t, i) => this.mergeFragment(t, i)));
}
console.log(`完成: `.green);
if (typeof result === "string")
{
console.log(result);
}
else
{
result.forEach(it => console.log(it));
}
if (fs.existsSync(dest)) {
return dest;
}
if (index !== -1) {
console.log(`正在合并片段${index.toString()}...`.blue);
}
else {
console.log("正在合并文件...".blue);
}
if (options.parts === 1) {
fs.renameSync(title + ".part0", dest);
}
else {
if (fs.existsSync(dest)) {
fs.unlinkSync(dest);
}
const files = fs.readdirSync(".");
const parts = files.filter(it => it.includes(title + ".part"));
const partRegex = /.*\.part([\d]+)/;
parts.sort((a, b) => {
const partA = parseInt(a.replace(partRegex, "$1"));
const partB = parseInt(b.replace(partRegex, "$1"));
return partA - partB;
}).forEach(file => {
const buffer = fs.readFileSync(file);
fs.appendFileSync(dest, buffer);
});
parts.forEach(file => fs.unlinkSync(file));
}
return dest;
}
async merge() {
let result: string | string[];
if (typeof this.title === "string") {
result = await this.mergeFragment(this.title);
}
else {
result = await Promise.all(this.title.map((t, i) => this.mergeFragment(t, i)));
}
console.log(`完成: `.green);
if (typeof result === "string") {
console.log(result);
}
else {
result.forEach(it => console.log(it));
}
}
}
(async () =>
{
try
{
let jsonText = '';
if (fs.existsSync(options.info))
{
jsonText = fs.readFileSync(options.info).toString("utf-8");
}
else
{
jsonText = await clipboardy.read();
}
const inputData = JSON.parse(jsonText) as InputData | InputData[];
if (!(function (data: any): data is InputData | InputData[]
{
if (Array.isArray(data) && data.length > 0)
{
return data[0].fragments !== undefined;
}
else
{
return data.fragments !== undefined;
}
})(inputData))
{
throw new Error();
}
try
{
process.chdir(options.output);
process.on("SIGINT", () =>
{
Downloader.workingDownloader && Downloader.workingDownloader.cancelDownload();
process.exit();
});
if (Array.isArray(inputData))
{
const downloaders = inputData.map(data => new Downloader(data));
for (const downloader of downloaders)
{
await downloader.download();
await downloader.merge();
}
}
else
{
const downloader = new Downloader(inputData);
await downloader.download();
await downloader.merge();
}
}
catch (error)
{
Downloader.workingDownloader && Downloader.workingDownloader.cancelDownload();
console.error(`\n错误: ${error}`.red);
}
(async () => {
try {
let jsonText = '';
if (fs.existsSync(options.info)) {
jsonText = fs.readFileSync(options.info).toString("utf-8");
}
catch (error)
{
console.log(`[无数据] 未在剪贴板检测到有效数据/没有指定输入文件/输入文件的数据无效.`.red);
else {
jsonText = await clipboardy.read();
}
})();
const inputData = JSON.parse(jsonText) as InputData | InputData[];
if (!(function (data: any): data is InputData | InputData[] {
if (Array.isArray(data) && data.length > 0) {
return data[0].fragments !== undefined;
}
else {
return data.fragments !== undefined;
}
})(inputData)) {
throw new Error();
}
try {
process.chdir(options.output);
process.on("SIGINT", () => {
Downloader.workingDownloader && Downloader.workingDownloader.cancelDownload();
process.exit();
});
if (Array.isArray(inputData)) {
const downloaders = inputData.map(data => new Downloader(data));
for (const downloader of downloaders) {
await downloader.download();
await downloader.merge();
}
}
else {
const downloader = new Downloader(inputData);
await downloader.download();
await downloader.merge();
}
}
catch (error) {
Downloader.workingDownloader && Downloader.workingDownloader.cancelDownload();
console.error(`\n错误: ${error}`.red);
}
}
catch (error) {
console.log(`[无数据] 未在剪贴板检测到有效数据/没有指定输入文件/输入文件的数据无效.`.red);
}
})();

View File

@ -1 +1 @@
(()=>{return(t,e)=>{class i{static async test(){if(!document.URL.includes("/www.bilibili.com/video/av")){return false}return await SpinQuery.select("#multi_page")!==null}async collectData(t){const e=`https://api.bilibili.com/x/web-interface/view?aid=${unsafeWindow.aid}`;const i=await Ajax.getJson(e);if(i.code!==0){Toast.error(`获取视频选集列表失败, message=${i.message}`,"批量下载");return""}const s=i.data.pages;if(s===undefined){Toast.error(`获取视频选集列表失败, 没有找到选集信息.`,"批量下载");return""}const a=[];for(const e of s){const i=`https://api.bilibili.com/x/player/playurl?avid=${unsafeWindow.aid}&cid=${e.cid}&qn=${t}&otype=json`;const s=await Ajax.getJsonWithCredentials(i);const r=s.data||s.result||s;if(r.quality!==t){Toast.error("获取下载链接失败, 请确认当前账号有下载权限后重试.","批量下载");return""}const n=r.durl.map(t=>{return{length:t.length,size:t.size,url:t.url}});a.push({fragments:n,title:`${e.page} - ${e.part}`,totalSize:n.map(t=>t.size).reduce((t,e)=>t+e),cid:e.cid})}return JSON.stringify(a)}}class s{static async test(){return document.URL.includes("/www.bilibili.com/bangumi")}async collectData(t){const e=document.querySelector("meta[property='og:url']");if(e===null){Toast.error("获取番剧数据失败: 无法找到 Season ID","批量下载");return""}const i=e.getAttribute("content").match(/play\/ss(\d+)/)[1];if(i===undefined){Toast.error("获取番剧数据失败: 无法解析 Season ID","批量下载");return""}const s=await Ajax.getJson(`https://api.bilibili.com/pgc/web/season/section?season_id=${i}`);if(s.code!==0){Toast.error(`获取番剧数据失败: 无法获取番剧集数列表, message=${s.message}`,"批量下载");return""}const a=s.result.main_section.episodes.map(t=>{return{aid:t.aid,cid:t.cid,number:t.title,title:t.long_title}});const r=[];for(const e of a){const i=`https://api.bilibili.com/pgc/player/web/playurl?avid=${e.aid}&cid=${e.cid}&qn=${t}&otype=json`;const s=await Ajax.getJsonWithCredentials(i);const a=s.data||s.result||s;if(a.quality!==t){Toast.error("获取下载链接失败, 请确认当前账号有下载权限后重试.","批量下载");return""}const n=a.durl.map(t=>{return{length:t.length,size:t.size,url:t.url}});r.push({fragments:n,title:`${e.number} - ${e.title}`,totalSize:n.map(t=>t.size).reduce((t,e)=>t+e),cid:e.cid})}return JSON.stringify(r)}}const a=[s,i];let r=null;class n{static async test(){for(const t of a){if(await t.test()===true){r=t;return true}}r=null;return false}async collectData(t,e){if(r===null){logError("[批量下载] 未找到合适的解析模块.");return null}const i=new r;const s=await i.collectData(t.quality);e.dismiss();return s}}return{export:{BatchExtractor:n}}}})();
(()=>{return(t,e)=>{class i{static async test(){if(!document.URL.includes("/www.bilibili.com/video/av")){return false}return await SpinQuery.select("#multi_page")!==null}async collectData(t){const e=`https://api.bilibili.com/x/web-interface/view?aid=${unsafeWindow.aid}`;const i=await Ajax.getJson(e);if(i.code!==0){Toast.error(`获取视频选集列表失败, message=${i.message}`,"批量下载");return""}const r=i.data.pages;if(r===undefined){Toast.error(`获取视频选集列表失败, 没有找到选集信息.`,"批量下载");return""}const a=[];for(const e of r){const i=`https://api.bilibili.com/x/player/playurl?avid=${unsafeWindow.aid}&cid=${e.cid}&qn=${t}&otype=json`;const r=await Ajax.getJsonWithCredentials(i);const s=r.data||r.result||r;if(s.quality!==t){Toast.error("获取下载链接失败, 请确认当前账号有下载权限后重试.","批量下载");return""}const n=s.durl.map(t=>{return{length:t.length,size:t.size,url:t.url}});a.push({fragments:n,title:`${e.page} - ${e.part}`,totalSize:n.map(t=>t.size).reduce((t,e)=>t+e),cid:e.cid,referer:document.URL.replace(window.location.search,"")})}return JSON.stringify(a)}}class r{static async test(){return document.URL.includes("/www.bilibili.com/bangumi")}async collectData(t){const e=document.querySelector("meta[property='og:url']");if(e===null){Toast.error("获取番剧数据失败: 无法找到 Season ID","批量下载");return""}const i=e.getAttribute("content").match(/play\/ss(\d+)/)[1];if(i===undefined){Toast.error("获取番剧数据失败: 无法解析 Season ID","批量下载");return""}const r=await Ajax.getJson(`https://api.bilibili.com/pgc/web/season/section?season_id=${i}`);if(r.code!==0){Toast.error(`获取番剧数据失败: 无法获取番剧集数列表, message=${r.message}`,"批量下载");return""}const a=r.result.main_section.episodes.map(t=>{return{aid:t.aid,cid:t.cid,number:t.title,title:t.long_title}});const s=[];for(const e of a){const i=`https://api.bilibili.com/pgc/player/web/playurl?avid=${e.aid}&cid=${e.cid}&qn=${t}&otype=json`;const r=await Ajax.getJsonWithCredentials(i);const a=r.data||r.result||r;if(a.quality!==t){Toast.error("获取下载链接失败, 请确认当前账号有下载权限后重试.","批量下载");return""}const n=a.durl.map(t=>{return{length:t.length,size:t.size,url:t.url}});s.push({fragments:n,title:`${e.number} - ${e.title}`,totalSize:n.map(t=>t.size).reduce((t,e)=>t+e),cid:e.cid})}return JSON.stringify(s)}}const a=[r,i];let s=null;class n{static async test(){for(const t of a){if(await t.test()===true){s=t;return true}}s=null;return false}async collectData(t,e){if(s===null){logError("[批量下载] 未找到合适的解析模块.");return null}const i=new s;const r=await i.collectData(t.quality);e.dismiss();return r}}return{export:{BatchExtractor:n}}}})();

File diff suppressed because one or more lines are too long

View File

@ -1,148 +1,124 @@
class VideoEpisodeBatch
{
static async test()
{
if (!document.URL.includes("/www.bilibili.com/video/av"))
{
return false;
}
return await SpinQuery.select("#multi_page") !== null;
class VideoEpisodeBatch {
static async test () {
if (!document.URL.includes('/www.bilibili.com/video/av')) {
return false
}
async collectData(quality)
{
const api = `https://api.bilibili.com/x/web-interface/view?aid=${unsafeWindow.aid}`;
const json = await Ajax.getJson(api);
if (json.code !== 0)
{
Toast.error(`获取视频选集列表失败, message=${json.message}`, "批量下载");
return "";
}
const pages = json.data.pages;
if (pages === undefined)
{
Toast.error(`获取视频选集列表失败, 没有找到选集信息.`, "批量下载");
return "";
}
const result = [];
for (const page of pages)
{
const url = `https://api.bilibili.com/x/player/playurl?avid=${unsafeWindow.aid}&cid=${page.cid}&qn=${quality}&otype=json`;
const json = await Ajax.getJsonWithCredentials(url);
const data = json.data || json.result || json;
if (data.quality !== quality)
{
Toast.error("获取下载链接失败, 请确认当前账号有下载权限后重试.", "批量下载");
return "";
}
const fragments = data.durl.map(it =>
{
return {
length: it.length,
size: it.size,
url: it.url,
};
});
result.push({
fragments,
title: `${page.page} - ${page.part}`,
totalSize: fragments.map(it => it.size).reduce((acc, it) => acc + it),
cid: page.cid,
});
}
return JSON.stringify(result);
return await SpinQuery.select('#multi_page') !== null
}
async collectData (quality) {
const api = `https://api.bilibili.com/x/web-interface/view?aid=${unsafeWindow.aid}`
const json = await Ajax.getJson(api)
if (json.code !== 0) {
Toast.error(`获取视频选集列表失败, message=${json.message}`, '批量下载')
return ''
}
const pages = json.data.pages
if (pages === undefined) {
Toast.error(`获取视频选集列表失败, 没有找到选集信息.`, '批量下载')
return ''
}
const result = []
for (const page of pages) {
const url = `https://api.bilibili.com/x/player/playurl?avid=${unsafeWindow.aid}&cid=${page.cid}&qn=${quality}&otype=json`
const json = await Ajax.getJsonWithCredentials(url)
const data = json.data || json.result || json
if (data.quality !== quality) {
Toast.error('获取下载链接失败, 请确认当前账号有下载权限后重试.', '批量下载')
return ''
}
const fragments = data.durl.map(it => {
return {
length: it.length,
size: it.size,
url: it.url
}
})
result.push({
fragments,
title: `${page.page} - ${page.part}`,
totalSize: fragments.map(it => it.size).reduce((acc, it) => acc + it),
cid: page.cid,
referer: document.URL.replace(window.location.search, '')
})
}
return JSON.stringify(result)
}
}
class BangumiBatch
{
static async test()
{
return document.URL.includes("/www.bilibili.com/bangumi");
class BangumiBatch {
static async test () {
return document.URL.includes('/www.bilibili.com/bangumi')
}
async collectData (quality) {
const metaUrl = document.querySelector("meta[property='og:url']")
if (metaUrl === null) {
Toast.error('获取番剧数据失败: 无法找到 Season ID', '批量下载')
return ''
}
async collectData(quality)
{
const metaUrl = document.querySelector("meta[property='og:url']");
if (metaUrl === null)
{
Toast.error("获取番剧数据失败: 无法找到 Season ID", "批量下载");
return "";
}
const seasonId = metaUrl.getAttribute("content").match(/play\/ss(\d+)/)[1];
if (seasonId === undefined)
{
Toast.error("获取番剧数据失败: 无法解析 Season ID", "批量下载");
return "";
}
const json = await Ajax.getJson(`https://api.bilibili.com/pgc/web/season/section?season_id=${seasonId}`);
if (json.code !== 0)
{
Toast.error(`获取番剧数据失败: 无法获取番剧集数列表, message=${json.message}`, "批量下载");
return "";
}
const pages = json.result.main_section.episodes.map(it =>
{
return { aid: it.aid, cid: it.cid, number: it.title, title: it.long_title };
});
const result = [];
for (const page of pages)
{
const url = `https://api.bilibili.com/pgc/player/web/playurl?avid=${page.aid}&cid=${page.cid}&qn=${quality}&otype=json`;
const json = await Ajax.getJsonWithCredentials(url);
const data = json.data || json.result || json;
if (data.quality !== quality)
{
Toast.error("获取下载链接失败, 请确认当前账号有下载权限后重试.", "批量下载");
return "";
}
const fragments = data.durl.map(it =>
{
return {
length: it.length,
size: it.size,
url: it.url,
};
});
result.push({
fragments,
title: `${page.number} - ${page.title}`,
totalSize: fragments.map(it => it.size).reduce((acc, it) => acc + it),
cid: page.cid,
});
}
return JSON.stringify(result);
const seasonId = metaUrl.getAttribute('content').match(/play\/ss(\d+)/)[1]
if (seasonId === undefined) {
Toast.error('获取番剧数据失败: 无法解析 Season ID', '批量下载')
return ''
}
const json = await Ajax.getJson(`https://api.bilibili.com/pgc/web/season/section?season_id=${seasonId}`)
if (json.code !== 0) {
Toast.error(`获取番剧数据失败: 无法获取番剧集数列表, message=${json.message}`, '批量下载')
return ''
}
const pages = json.result.main_section.episodes.map(it => {
return { aid: it.aid, cid: it.cid, number: it.title, title: it.long_title }
})
const result = []
for (const page of pages) {
const url = `https://api.bilibili.com/pgc/player/web/playurl?avid=${page.aid}&cid=${page.cid}&qn=${quality}&otype=json`
const json = await Ajax.getJsonWithCredentials(url)
const data = json.data || json.result || json
if (data.quality !== quality) {
Toast.error('获取下载链接失败, 请确认当前账号有下载权限后重试.', '批量下载')
return ''
}
const fragments = data.durl.map(it => {
return {
length: it.length,
size: it.size,
url: it.url
}
})
result.push({
fragments,
title: `${page.number} - ${page.title}`,
totalSize: fragments.map(it => it.size).reduce((acc, it) => acc + it),
cid: page.cid
})
}
return JSON.stringify(result)
}
}
const extractors = [BangumiBatch, VideoEpisodeBatch];
let ExtractorClass = null;
export class BatchExtractor
{
static async test()
{
for (const e of extractors)
{
if (await e.test() === true)
{
ExtractorClass = e;
return true;
}
}
ExtractorClass = null;
return false;
const extractors = [BangumiBatch, VideoEpisodeBatch]
let ExtractorClass = null
export class BatchExtractor {
static async test () {
for (const e of extractors) {
if (await e.test() === true) {
ExtractorClass = e
return true
}
}
async collectData(format, toast)
{
if (ExtractorClass === null)
{
logError("[批量下载] 未找到合适的解析模块.");
return null;
}
const extractor = new ExtractorClass;
const result = await extractor.collectData(format.quality);
toast.dismiss();
return result;
ExtractorClass = null
return false
}
async collectData (format, toast) {
if (ExtractorClass === null) {
logError('[批量下载] 未找到合适的解析模块.')
return null
}
const extractor = new ExtractorClass()
const result = await extractor.collectData(format.quality)
toast.dismiss()
return result
}
}
export default {
export: {
BatchExtractor,
},
};
export: {
BatchExtractor
}
}

File diff suppressed because it is too large Load Diff