Add types for batch download

This commit is contained in:
the1812 2019-11-06 23:02:42 +08:00
parent a597f9e0f3
commit 7bfd55a793
11 changed files with 61 additions and 66 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
(()=>{return(t,e)=>{const i=12;class r{constructor(){this.itemList=[];this.itemFilter=(()=>true)}async getItemList(){}async collectData(){}async collectAria2(r,s){const n=JSON.parse(await this.collectData(r));if(s){const r=t.aria2RpcOption;const{sendRpc:s}=await e.importAsync("aria2-rpc");for(const t of n){const e=t.fragments.map((e,s)=>{let n="";if(t.fragments.length>1){n=" - "+(s+1)}const a=[];if(r.secretKey!==""){a.push(`token:${r.secretKey}`)}a.push([e.url]);a.push({referer:document.URL.replace(window.location.search,""),"user-agent":UserAgent,out:`${t.title}${n}.flv`,split:i,dir:r.baseDir+r.dir||undefined,"max-download-limit":r.maxDownloadLimit||undefined});const o=encodeURIComponent(`${t.title}${n}`);return{params:a,id:o}});await s(e,true)}}else{return`\n# Generated by Bilibili Evolved Video Export\n# https://github.com/the1812/Bilibili-Evolved/\n${n.map(t=>{return t.fragments.map(e=>{return`\n${e.url}\n referer=${t.referer}\n user-agent=${UserAgent}\n out=${t.title}.flv\n split=${i}\n`.trim()})}).join("\n")}\n`.trim()}}}class s extends r{static async test(){if(!document.URL.includes("/www.bilibili.com/video/av")){return false}return await SpinQuery.select("#multi_page")!==null}async getItemList(){if(this.itemList.length>0){return this.itemList}const t=`https://api.bilibili.com/x/web-interface/view?aid=${unsafeWindow.aid}`;const e=await Ajax.getJson(t);if(e.code!==0){Toast.error(`获取视频选集列表失败, message=${e.message}`,"批量下载");return""}const i=e.data.pages;if(i===undefined){Toast.error(`获取视频选集列表失败, 没有找到选集信息.`,"批量下载");return""}this.itemList=i.map(t=>{return{title:`P${t.page} ${t.part}`,cid:t.cid,aid:unsafeWindow.aid}});return this.itemList}async collectData(t){const e=[];for(const i of(await this.getItemList()).filter(this.itemFilter)){const r=`https://api.bilibili.com/x/player/playurl?avid=${i.aid}&cid=${i.cid}&qn=${t}&otype=json`;const s=await Ajax.getJsonWithCredentials(r);const n=s.data||s.result||s;if(n.quality!==t){console.warn(`${i.title} 不支持所选画质, 已回退到较低画质. (quality=${n.quality})`)}const a=n.durl.map(t=>{return{length:t.length,size:t.size,url:t.url}});e.push({fragments:a,title:i.title.replace(/[\/\\:\*\?"<>\|]/g," "),totalSize:a.map(t=>t.size).reduce((t,e)=>t+e),cid:i.cid,referer:document.URL.replace(window.location.search,"")})}return JSON.stringify(e)}}class n extends r{static async test(){return document.URL.includes("/www.bilibili.com/bangumi")}async getItemList(){if(this.itemList.length>0){return this.itemList}const t=document.querySelector("meta[property='og:url']");if(t===null){Toast.error("获取番剧数据失败: 无法找到 Season ID","批量下载");return""}const e=t.getAttribute("content").match(/play\/ss(\d+)/)[1];if(e===undefined){Toast.error("获取番剧数据失败: 无法解析 Season ID","批量下载");return""}const i=await Ajax.getJson(`https://api.bilibili.com/pgc/web/season/section?season_id=${e}`);if(i.code!==0){Toast.error(`获取番剧数据失败: 无法获取番剧集数列表, message=${i.message}`,"批量下载");return""}this.itemList=i.result.main_section.episodes.map((t,e)=>{return{aid:t.aid,cid:t.cid,title:t.long_title?`${t.title} - ${t.long_title}`:`${e+1} - ${t.title}`}});return this.itemList}async collectData(t){const e=[];for(const i of(await this.getItemList()).filter(this.itemFilter)){const r=`https://api.bilibili.com/pgc/player/web/playurl?avid=${i.aid}&cid=${i.cid}&qn=${t}&otype=json`;const s=await Ajax.getJsonWithCredentials(r);const n=s.data||s.result||s;if(n.quality!==t){console.warn(`${i.title} 不支持所选画质, 已回退到较低画质. (quality=${n.quality})`)}const a=n.durl.map(t=>{return{length:t.length,size:t.size,url:t.url}});e.push({fragments:a,title:i.title.replace(/[\/\\:\*\?"<>\|]/g," "),totalSize:a.map(t=>t.size).reduce((t,e)=>t+e),cid:i.cid,referer:document.URL.replace(window.location.search,"")})}return JSON.stringify(e)}}const a=[n,s];let o=null;class c{constructor(){this.itemFilter=(()=>true)}static async test(){for(const t of a){if(await t.test()===true){o=t;return true}}o=null;return false}getExtractor(){if(o===null){logError("[批量下载] 未找到合适的解析模块.");throw new Error(`[Batch Download] module not found.`)}const t=new o;t.itemFilter=this.itemFilter;return t}async getItemList(){const t=this.getExtractor();return await t.getItemList()}async collectData(t,e){const i=this.getExtractor();const r=await i.collectData(t.quality);e.dismiss();return r}async collectAria2(t,e,i){const r=this.getExtractor();const s=await r.collectAria2(t.quality,i);e.dismiss();return s}}return{export:{BatchExtractor:c}}}})();
(()=>{return(t,e)=>{const i=12;class r{constructor(){this.itemList=[];this.itemFilter=(()=>true)}async collectAria2(r,s){const n=JSON.parse(await this.collectData(r));if(s){const r=t.aria2RpcOption;const{sendRpc:s}=await e.importAsync("aria2-rpc");for(const t of n){const e=t.fragments.map((e,s)=>{let n="";if(t.fragments.length>1){n=" - "+(s+1)}const a=[];if(r.secretKey!==""){a.push(`token:${r.secretKey}`)}a.push([e.url]);a.push({referer:document.URL.replace(window.location.search,""),"user-agent":UserAgent,out:`${t.title}${n}.flv`,split:i,dir:r.baseDir+r.dir||undefined,"max-download-limit":r.maxDownloadLimit||undefined});const o=encodeURIComponent(`${t.title}${n}`);return{params:a,id:o}});await s(e,true)}}else{return`\n# Generated by Bilibili Evolved Video Export\n# https://github.com/the1812/Bilibili-Evolved/\n${n.map(t=>{return t.fragments.map(e=>{return`\n${e.url}\n referer=${t.referer}\n user-agent=${UserAgent}\n out=${t.title}.flv\n split=${i}\n`.trim()})}).join("\n")}\n`.trim()}}}class s extends r{static async test(){if(!document.URL.includes("/www.bilibili.com/video/av")){return false}return await SpinQuery.select("#multi_page")!==null}async getItemList(){if(this.itemList.length>0){return this.itemList}const t=`https://api.bilibili.com/x/web-interface/view?aid=${unsafeWindow.aid}`;const e=await Ajax.getJson(t);if(e.code!==0){Toast.error(`获取视频选集列表失败, message=${e.message}`,"批量下载");return[]}const i=e.data.pages;if(i===undefined){Toast.error(`获取视频选集列表失败, 没有找到选集信息.`,"批量下载");return[]}this.itemList=i.map(t=>{return{title:`P${t.page} ${t.part}`,cid:t.cid,aid:unsafeWindow.aid}});return this.itemList}async collectData(t){const e=[];for(const i of(await this.getItemList()).filter(this.itemFilter)){const r=`https://api.bilibili.com/x/player/playurl?avid=${i.aid}&cid=${i.cid}&qn=${t}&otype=json`;const s=await Ajax.getJsonWithCredentials(r);const n=s.data||s.result||s;if(n.quality!==t){console.warn(`${i.title} 不支持所选画质, 已回退到较低画质. (quality=${n.quality})`)}const a=n.durl.map(t=>{return{length:t.length,size:t.size,url:t.url}});e.push({fragments:a,title:i.title.replace(/[\/\\:\*\?"<>\|]/g," "),totalSize:a.map(t=>t.size).reduce((t,e)=>t+e),cid:i.cid,referer:document.URL.replace(window.location.search,"")})}return JSON.stringify(e)}}class n extends r{static async test(){return document.URL.includes("/www.bilibili.com/bangumi")}async getItemList(){if(this.itemList.length>0){return this.itemList}const t=document.querySelector("meta[property='og:url']");if(t===null){Toast.error("获取番剧数据失败: 无法找到 Season ID","批量下载");return[]}const e=t.getAttribute("content").match(/play\/ss(\d+)/)[1];if(e===undefined){Toast.error("获取番剧数据失败: 无法解析 Season ID","批量下载");return[]}const i=await Ajax.getJson(`https://api.bilibili.com/pgc/web/season/section?season_id=${e}`);if(i.code!==0){Toast.error(`获取番剧数据失败: 无法获取番剧集数列表, message=${i.message}`,"批量下载");return[]}this.itemList=i.result.main_section.episodes.map((t,e)=>{return{aid:t.aid,cid:t.cid,title:t.long_title?`${t.title} - ${t.long_title}`:`${e+1} - ${t.title}`}});return this.itemList}async collectData(t){const e=[];for(const i of(await this.getItemList()).filter(this.itemFilter)){const r=`https://api.bilibili.com/pgc/player/web/playurl?avid=${i.aid}&cid=${i.cid}&qn=${t}&otype=json`;const s=await Ajax.getJsonWithCredentials(r);const n=s.data||s.result||s;if(n.quality!==t){console.warn(`${i.title} 不支持所选画质, 已回退到较低画质. (quality=${n.quality})`)}const a=n.durl.map(t=>{return{length:t.length,size:t.size,url:t.url}});e.push({fragments:a,title:i.title.replace(/[\/\\:\*\?"<>\|]/g," "),totalSize:a.map(t=>t.size).reduce((t,e)=>t+e),cid:i.cid,referer:document.URL.replace(window.location.search,"")})}return JSON.stringify(e)}}const a=[n,s];let o;class c{constructor(){this.itemFilter=(()=>true)}static async test(){for(const t of a){if(await t.test()===true){o=t;return true}}return false}getExtractor(){if(o===null){logError("[批量下载] 未找到合适的解析模块.");throw new Error(`[Batch Download] module not found.`)}const t=new o;t.itemFilter=this.itemFilter;return t}async getItemList(){const t=this.getExtractor();return await t.getItemList()}async collectData(t,e){const i=this.getExtractor();const r=await i.collectData(t.quality);e.dismiss();return r}async collectAria2(t,e,i){const r=this.getExtractor();const s=await r.collectAria2(t.quality,i);e.dismiss();return s}}return{export:{BatchExtractor:c}}}})();

View File

@ -10,7 +10,7 @@
"auto-continue.min.js": "96CD47C367D7397CE1467A69764409BAEFF16E8F535BC8E15523CD0B8A86687E",
"auto-draw.min.js": "AE72CF2623DF2D15AD4AF82D125FFAF7EF5B1E6B36D9E9AC646AC98AA6AA8698",
"auto-play.min.js": "DC9938AC15DADDC9D88DCA0C9BE64BE142C37D32CB85E42E23DAB2A7378531E5",
"batch-download.min.js": "A0F979F33A06E23D3800525CF70700170565EBD98579CE394ECE4CC66AAEEB5A",
"batch-download.min.js": "2937E9622D79F7512405902587DDDA3C647453ADAE7B18245CE934808FA02712",
"biliplus-redirect.min.js": "9882D14DAC5C103212A101A5168883C41B4B3B2737FD5F222DC343D0304FF8AF",
"blackboard.min.js": "8BB9CCDE72302AE7DE39D66F9231EFB82BA27DDA799BA4AAAC06F6DE802661BF",
"blackboard.vue.min.js": "8F1984AB69B029701DC88F33568EF1881D0C54EEC72D65EA3029CF2F68BD6674",
@ -47,7 +47,7 @@
"download-video-package.min.js": "B9BFBA2E5E9267ED901359BEE10E0B92619A429F4B09F7E96B15EE6B9CD81A30",
"download-video.min.css": "95FF9DA3C1FFF1C1134D70AE3BB609049B7D5D3FBB71CAAAE84C8742C51AD841",
"download-video.min.html": "DDC359FD5551C01572CDE93CF7A8C63D4DD956568930AA97AA2C6FFEA7A6FC2A",
"download-video.min.js": "0066CA903974E0F70EB4588D9A4E9FE697C7D63815C4A95056B95A2D79EBA246",
"download-video.min.js": "BB4E9B82DB9109ACC45488F081BE5875CB3BE2EC351E8DFE3192CA911AEB6A1F",
"dpi-img.vue.min.js": "3D9D93079365DCABA8DC8970F051AD1A7F25C3EFFF850CD1000C4312E9B910DD",
"expand-danmaku.min.js": "B21658C40085AEA8DC49652AE62EB8610BB2EBEAF7A9C9AF69EF3B11E08EEC8D",
"expand-description.min.css": "58C7710A50521B80F7D872BDC4C652610D84C4FABC6874BA66DA37B4F8759224",

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -1,13 +0,0 @@
export interface BatchItem {
title: string
aid: string
cid: string
}
export class BatchExtractor {
itemList: BatchItem[]
itemFilter: (item: BatchItem) => boolean
static test(): Promise<boolean>
getItemList(): Promise<BatchItem[]>
collectData(format: any, toast: Toast): Promise<string>
collectAria2(format: any, toast: Toast, rpc?: boolean): Promise<string>
}

View File

@ -1,18 +1,29 @@
const fragmentSplitFactor = 12
class Batch {
constructor () {
this.itemList = []
this.itemFilter = () => true
}
async getItemList() {}
async collectData () {}
async collectAria2 (quality, rpc) {
const json = JSON.parse(await this.collectData(quality))
interface BatchItem {
aid: number | string
cid: number | string
title: string
}
interface RawItemFragment { length: number, size: number, url: string }
interface RawItem {
fragments: RawItemFragment[]
title: string
totalSize: number
cid: number | string
referer: string
}
abstract class Batch {
itemList: BatchItem[] = []
itemFilter: (item: BatchItem) => boolean = () => true
abstract async getItemList(): Promise<BatchItem[]>
abstract async collectData(quality: number | string): Promise<string>
async collectAria2(quality: number | string, rpc: boolean) {
const json: RawItem[] = JSON.parse(await this.collectData(quality))
if (rpc) {
const option = settings.aria2RpcOption
const { sendRpc } = await import('./aria2-rpc')
for (const item of json) {
const params = item.fragments.map((fragment, index) => {
const params = item.fragments.map((fragment: { url: string }, index: number) => {
let indexNumber = ''
if (item.fragments.length > 1) {
indexNumber = ' - ' + (index + 1)
@ -43,8 +54,8 @@ class Batch {
# Generated by Bilibili Evolved Video Export
# https://github.com/the1812/Bilibili-Evolved/
${json.map(item => {
return item.fragments.map(f => {
return `
return item.fragments.map(f => {
return `
${f.url}
referer=${item.referer}
user-agent=${UserAgent}
@ -58,7 +69,7 @@ ${f.url}
}
}
class VideoEpisodeBatch extends Batch {
static async test () {
static async test() {
if (!document.URL.includes('/www.bilibili.com/video/av')) {
return false
}
@ -72,14 +83,14 @@ class VideoEpisodeBatch extends Batch {
const json = await Ajax.getJson(api)
if (json.code !== 0) {
Toast.error(`获取视频选集列表失败, message=${json.message}`, '批量下载')
return ''
return []
}
const pages = json.data.pages
if (pages === undefined) {
Toast.error(`获取视频选集列表失败, 没有找到选集信息.`, '批量下载')
return ''
return []
}
this.itemList = pages.map(page => {
this.itemList = pages.map((page: any) => {
return {
title: `P${page.page} ${page.part}`,
cid: page.cid,
@ -88,7 +99,7 @@ class VideoEpisodeBatch extends Batch {
})
return this.itemList
}
async collectData (quality) {
async collectData(quality: number | string) {
const result = []
for (const item of (await this.getItemList()).filter(this.itemFilter)) {
const url = `https://api.bilibili.com/x/player/playurl?avid=${item.aid}&cid=${item.cid}&qn=${quality}&otype=json`
@ -97,7 +108,7 @@ class VideoEpisodeBatch extends Batch {
if (data.quality !== quality) {
console.warn(`${item.title} 不支持所选画质, 已回退到较低画质. (quality=${data.quality})`)
}
const fragments = data.durl.map(it => {
const fragments: RawItemFragment[] = data.durl.map((it: any) => {
return {
length: it.length,
size: it.size,
@ -116,7 +127,7 @@ class VideoEpisodeBatch extends Batch {
}
}
class BangumiBatch extends Batch {
static async test () {
static async test() {
return document.URL.includes('/www.bilibili.com/bangumi')
}
async getItemList() {
@ -126,19 +137,19 @@ class BangumiBatch extends Batch {
const metaUrl = document.querySelector("meta[property='og:url']")
if (metaUrl === null) {
Toast.error('获取番剧数据失败: 无法找到 Season ID', '批量下载')
return ''
return []
}
const seasonId = metaUrl.getAttribute('content').match(/play\/ss(\d+)/)[1]
const seasonId = metaUrl.getAttribute('content')!.match(/play\/ss(\d+)/)![1]
if (seasonId === undefined) {
Toast.error('获取番剧数据失败: 无法解析 Season ID', '批量下载')
return ''
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 ''
return []
}
this.itemList = json.result.main_section.episodes.map((it, index) => {
this.itemList = json.result.main_section.episodes.map((it: any, index: number) => {
return {
aid: it.aid,
cid: it.cid,
@ -147,7 +158,7 @@ class BangumiBatch extends Batch {
})
return this.itemList
}
async collectData (quality) {
async collectData(quality: string | number) {
const result = []
for (const item of (await this.getItemList()).filter(this.itemFilter)) {
const url = `https://api.bilibili.com/pgc/player/web/playurl?avid=${item.aid}&cid=${item.cid}&qn=${quality}&otype=json`
@ -156,7 +167,7 @@ class BangumiBatch extends Batch {
if (data.quality !== quality) {
console.warn(`${item.title} 不支持所选画质, 已回退到较低画质. (quality=${data.quality})`)
}
const fragments = data.durl.map(it => {
const fragments: RawItemFragment[] = data.durl.map((it: any) => {
return {
length: it.length,
size: it.size,
@ -175,22 +186,19 @@ class BangumiBatch extends Batch {
}
}
const extractors = [BangumiBatch, VideoEpisodeBatch]
let ExtractorClass = null
let ExtractorClass: new() => Batch
export class BatchExtractor {
constructor() {
this.itemFilter = () => true
}
static async test () {
itemFilter: (item: BatchItem) => boolean = () => true
static async test() {
for (const e of extractors) {
if (await e.test() === true) {
ExtractorClass = e
return true
}
}
ExtractorClass = null
return false
}
getExtractor () {
getExtractor() {
if (ExtractorClass === null) {
logError('[批量下载] 未找到合适的解析模块.')
throw new Error(`[Batch Download] module not found.`)
@ -199,17 +207,17 @@ export class BatchExtractor {
extractor.itemFilter = this.itemFilter
return extractor
}
async getItemList () {
async getItemList() {
const extractor = this.getExtractor()
return await extractor.getItemList()
}
async collectData (format, toast) {
async collectData(format: { quality: number | string }, toast: Toast) {
const extractor = this.getExtractor()
const result = await extractor.collectData(format.quality)
toast.dismiss()
return result
}
async collectAria2 (format, toast, rpc) {
async collectAria2(format: { quality: number | string }, toast: Toast, rpc: boolean) {
const extractor = this.getExtractor()
const result = await extractor.collectAria2(format.quality, rpc)
toast.dismiss()

View File

@ -834,16 +834,16 @@ async function loadPanel() {
this.episodeList = []
return
}
this.batchExtractor = new BatchExtractor()
const batchExtractor = this.batchExtractor = new BatchExtractor()
this.batch = true
this.episodeList = (await this.batchExtractor.getItemList()).map((item: EpisodeItem, index: number) => {
this.episodeList = (await batchExtractor.getItemList()).map((item, index) => {
return {
aid: item.aid,
cid: item.cid,
title: item.title,
index,
checked: true,
}
} as EpisodeItem
})
},
cancelDownload() {