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; 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 imported = match.Groups[1].Value.Replace(" as ", ":");
var source = convertToRuntimeSource(match.Groups[2].Value); var source = convertToRuntimeSource(match.Groups[2].Value);
@ -173,6 +173,7 @@ namespace BilibiliEvolved.Build
}; };
})();"; })();";
} }
// Console.WriteLine(input);
return new UglifyJs().Run(input); return new UglifyJs().Run(input);
} }
} }

Binary file not shown.

View File

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

View File

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

View File

@ -6,31 +6,29 @@ import fs = require("fs");
import ProgressBar = require("progress"); import ProgressBar = require("progress");
import "colors"; import "colors";
interface Fragment interface Fragment {
{ length: number;
length: number; size: number;
size: number; url: string;
url: string; backupUrls: string[];
backupUrls: string[];
} }
interface InputData interface InputData {
{ fragments: Fragment[];
fragments: Fragment[]; title: string;
title: string; totalSize: number;
totalSize: number; referer: string;
} }
interface Settings interface Settings {
{ parts: number;
parts: number; info: string;
info: string; output: string;
output: string;
} }
const optionDefinitions = [ const optionDefinitions = [
{ name: 'danmaku', alias: 'd', defaultValue: false, type: Boolean }, { name: 'danmaku', alias: 'd', defaultValue: false, type: Boolean },
{ name: 'info', alias: 'i', defaultOption: true, type: String, defaultValue: undefined }, { name: 'info', alias: 'i', defaultOption: true, type: String, defaultValue: undefined },
{ name: 'parts', alias: 'p', type: Number, defaultValue: 12 }, { name: 'parts', alias: 'p', type: Number, defaultValue: 12 },
{ name: 'output', alias: 'o', type: String, defaultValue: '.' }, { name: 'output', alias: 'o', type: String, defaultValue: '.' },
]; ];
const commandLineOptions = commandLineArgs(optionDefinitions) as Settings; const commandLineOptions = commandLineArgs(optionDefinitions) as Settings;
let options = commandLineOptions; let options = commandLineOptions;
@ -41,277 +39,224 @@ options.parts = Math.round(options.parts);
// options = Object.assign(jsonOptions, options); // options = Object.assign(jsonOptions, options);
// } // }
if (options.parts < 1) if (options.parts < 1) {
{ console.error("分段数不能小于1".red);
console.error("分段数不能小于1".red); process.exit();
process.exit();
} }
class Downloader class Downloader {
{ static workingDownloader: Downloader | null = null;
static workingDownloader: Downloader | null = null; private progressMap = new Map<request.Request | string, number>();
private progressMap = new Map<request.Request | string, number>(); private progressBar = new ProgressBar(":percent [:bar]", {
private progressBar = new ProgressBar(":percent [:bar]", { total: this.inputData.totalSize,
total: this.inputData.totalSize, width: 20,
width: 20, incomplete: ' ',
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; this.progressBar.terminate();
private title: string | string[]; const files = fs.readdirSync(".");
const parts = files.filter(it => it.includes(this.inputData.title));
constructor( parts.forEach(file => fs.unlinkSync(file));
private inputData: InputData, console.log("已取消下载".blue);
) }
{ } private downloadFragmentPart(url: string, range: string, partFilename: string) {
private getExtension(fragment: Fragment) return new Promise((resolve, reject) => {
{ const makeRequest = () => {
this.extension = fragment.url.includes(".flv") ? ".flv" : ".mp4"; const req = request({
} url: url,
private updateProgress() method: "GET",
{ headers: {
const progress = this.progressMap ? Range: range,
[...this.progressMap.values()].reduce((a, b) => a + b, 0) / this.inputData.totalSize : 0; Origin: "https://www.bilibili.com",
this.progressBar.update(progress); 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",
cancelDownload() },
{ }).on("complete", response => {
[...this.progressMap.keys()].forEach(it => if (response.statusCode.toString()[0] === "2") {
{ resolve(response);
if (typeof it !== "string") }
{ else {
it.abort(); 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(); req.pipe(fs.createWriteStream(partFilename, {
const files = fs.readdirSync("."); autoClose: true
const parts = files.filter(it => it.includes(this.inputData.title)); }))
parts.forEach(file => fs.unlinkSync(file)); return req
console.log("已取消下载".blue); };
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) let startByte = 0;
{ let part = 0;
return new Promise((resolve, reject) => const promises = [];
{ while (startByte < fragment.size) {
const makeRequest = () => const partFilename = `${title}.part${part}`;
{ // if (fs.existsSync(partFilename))
const req = request({ // {
url: url, // this.progressMap.set(partFilename, partialLength);
method: "GET", // this.updateProgress();
headers: { // }
Range: range, // else
Origin: "https://www.bilibili.com", // {
Referer: "https://www.bilibili.com", const endByte = Math.min(fragment.size - 1, Math.round(startByte + partialLength));
"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", const range = `bytes=${startByte}-${endByte}`;
}, promises.push(this.downloadFragmentPart(fragment.url, range, partFilename));
// maxAttempts: 8, // }
// retryDelay: 1000, startByte = Math.round(startByte + partialLength) + 1;
}).on("complete", response => part++;
{
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);
});
} }
private async downloadFragment(fragment: Fragment, index: number = -1) await Promise.all(promises);
{ return title;
const partialLength = Math.round(fragment.size / options.parts); }
const title = (index === -1 ? this.inputData.title : this.inputData.title + " - " + index.toString()); async download() {
if (fs.existsSync(title + this.extension)) console.log(`正在下载: ${this.inputData.title}`.green);
{ Downloader.workingDownloader = this;
this.progressBar.terminate(); this.progressBar.render();
console.log(`跳过了已存在的文件 ${title + this.extension}`); const [fragment] = this.inputData.fragments;
return title; this.getExtension(fragment);
} if (this.inputData.fragments.length === 1) {
let startByte = 0; this.title = await this.downloadFragment(fragment);
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;
} }
async download() else {
{ this.title = await Promise.all(this.inputData.fragments.map((f, i) => this.downloadFragment(f, i)));
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)));
}
} }
private async mergeFragment(title: string, index = -1) }
{ private async mergeFragment(title: string, index = -1) {
const dest = title + this.extension; const dest = title + this.extension;
if (!this.progressBar.complete) if (!this.progressBar.complete) {
{ this.progressBar.update(1);
this.progressBar.update(1); this.progressBar.terminate();
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;
} }
async merge() if (fs.existsSync(dest)) {
{ return dest;
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 (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 () => (async () => {
{ try {
try let jsonText = '';
{ if (fs.existsSync(options.info)) {
let jsonText = ''; jsonText = fs.readFileSync(options.info).toString("utf-8");
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);
}
} }
catch (error) else {
{ jsonText = await clipboardy.read();
console.log(`[无数据] 未在剪贴板检测到有效数据/没有指定输入文件/输入文件的数据无效.`.red);
} }
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 class VideoEpisodeBatch {
{ static async test () {
static async test() if (!document.URL.includes('/www.bilibili.com/video/av')) {
{ return false
if (!document.URL.includes("/www.bilibili.com/video/av"))
{
return false;
}
return await SpinQuery.select("#multi_page") !== null;
} }
async collectData(quality) return await SpinQuery.select('#multi_page') !== null
{ }
const api = `https://api.bilibili.com/x/web-interface/view?aid=${unsafeWindow.aid}`; async collectData (quality) {
const json = await Ajax.getJson(api); const api = `https://api.bilibili.com/x/web-interface/view?aid=${unsafeWindow.aid}`
if (json.code !== 0) const json = await Ajax.getJson(api)
{ if (json.code !== 0) {
Toast.error(`获取视频选集列表失败, message=${json.message}`, "批量下载"); Toast.error(`获取视频选集列表失败, message=${json.message}`, '批量下载')
return ""; 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);
} }
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 class BangumiBatch {
{ static async test () {
static async test() return document.URL.includes('/www.bilibili.com/bangumi')
{ }
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 seasonId = metaUrl.getAttribute('content').match(/play\/ss(\d+)/)[1]
{ if (seasonId === undefined) {
const metaUrl = document.querySelector("meta[property='og:url']"); Toast.error('获取番剧数据失败: 无法解析 Season ID', '批量下载')
if (metaUrl === null) return ''
{
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 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]; const extractors = [BangumiBatch, VideoEpisodeBatch]
let ExtractorClass = null; let ExtractorClass = null
export class BatchExtractor export class BatchExtractor {
{ static async test () {
static async test() for (const e of extractors) {
{ if (await e.test() === true) {
for (const e of extractors) ExtractorClass = e
{ return true
if (await e.test() === true) }
{
ExtractorClass = e;
return true;
}
}
ExtractorClass = null;
return false;
} }
async collectData(format, toast) ExtractorClass = null
{ return false
if (ExtractorClass === null) }
{ async collectData (format, toast) {
logError("[批量下载] 未找到合适的解析模块."); if (ExtractorClass === null) {
return null; logError('[批量下载] 未找到合适的解析模块.')
} return null
const extractor = new ExtractorClass;
const result = await extractor.collectData(format.quality);
toast.dismiss();
return result;
} }
const extractor = new ExtractorClass()
const result = await extractor.collectData(format.quality)
toast.dismiss()
return result
}
} }
export default { export default {
export: { export: {
BatchExtractor, BatchExtractor
}, }
}; }

File diff suppressed because it is too large Load Diff