您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Use javlibrary as your video download control panel
当前为
// ==UserScript== // @name jav download control panel // @name:zh-CN Jav 下载控制台 // @description Use javlibrary as your video download control panel // @description:zh-CN 把 javlibrary 作为下载控制台 // @version 0.2.0 // @author jferroal // @license GPL-3.0 // @include http://www.javlibrary.com/* // @grant GM_xmlhttpRequest // @run-at document-end // @namespace https://greasyfork.org/users/34556 // ==/UserScript== // local request client class LocalRequest { static get baseUri() { return 'http://localhost:5000/downloader/api/v0.1.0/task'; } static request(method, url, data, onload) { return new Promise((resolve, reject) => { const payload = { url, headers: { "Content-Type": "application/json" }, onerror: function (response) { console.log('something wrong while start download task. '); reject(response); }, ontimeout: function (response) { console.log('request timeout! '); reject(response.response); }, onabort: function (response) { console.log('request aborted. '); reject(response.response); } }; if (method) { payload.method = method; } else { payload.method = 'GET'; } if (data && method === 'POST' || method === 'PUT') { payload.data = data; } if (onload) { payload.onload = onload; } else { payload.onload = (response) => { resolve(response); }; } return GM_xmlhttpRequest(payload); }); } static startTask(task) { return LocalRequest.request('POST', LocalRequest.baseUri, JSON.stringify(task)); } static listTask(tasks) { const queryStr = tasks.join(';'); return LocalRequest.request('GET', LocalRequest.baseUri + '?names=' + queryStr); } } // parse page while page load class PageParser { constructor() { const currentHref = window.location.href; if (/http:\/\/www\.javlibrary\.com\/cn\/\?v=.*/.test(currentHref)) { this.type = 'single'; } else if (/http:\/\/www\.javlibrary\.com\/cn\/vl_.*/.test(currentHref)) { this.type = 'video'; } else { this.type = 'home'; } if (this.type === 'single') { this.targetElements = [document.getElementById('video_id')]; } else if (this.type === 'video') { this.targetElements = document.getElementsByClassName('video') || []; } else { this.targetElements = document.getElementsByClassName('post-headline') || []; } } toTasks() { if (this.tasks && this.tasks.length) { return this.tasks; } this.tasks = []; for (let i = 0; i < this.targetElements.length; i += 1) { const elem = this.targetElements[i]; if (this.type === 'single') { this.tasks.push(elem.children[0].children[0].children[0].children[1].textContent); } else if (this.type === 'video') { this.tasks.push(elem.children[0].children[0].textContent); } else { this.tasks.push(elem.children[0].textContent); } } return this.tasks; } get nameElemMap() { if (this._nameElemMap) { return this._nameElemMap; } this._nameElemMap = {}; for (let i = 0; i < this.targetElements.length; i += 1) { const elem = this.targetElements[i]; if (this.type === 'single') { const name = elem.children[0].children[0].children[0].children[1].textContent; this._nameElemMap[name] = { progressBarParent: elem, statusBarParent: elem.children[0].children[0].children[0].children[1], }; } else if (this.type === 'video') { const name = elem.children[0].children[0].textContent; this._nameElemMap[name] = { progressBarParent: elem, statusBarParent: elem.children[0].children[0], }; } else { const name = elem.children[0].textContent; this._nameElemMap[name] = { progressBarParent: elem, statusBarParent: elem.children[0], }; } } return this._nameElemMap; } } // Search result from tokyotosho class TokyoToShoRequest { static get headers() { return { ':authority': 'www.tokyotosho.info', ':method': 'GET', ':path': `/search.php?terms=${this.target}`, ':scheme': 'https', 'accept': 'text / html, application/xhtml+xml,application/xml;q=0.9, image/webp,*/*;q=0.8', 'accept-encoding;': 'gzip, deflate, sdch, br', 'accept-language': 'zh-CN, en-US;q=0.8, en;q=0.6, zh;q=0.4', 'cache-control': 'no-cache', }; } static get baseUri() { return 'https://www.tokyotosho.info/search.php?terms='; } static search(target) { return new Promise((resolve, reject) => { return GM_xmlhttpRequest({ method: "GET", url: TokyoToShoRequest.baseUri + target, headers: TokyoToShoRequest.headers, onload: function (response) { resolve(response.response); }, onerror: function (response) { console.log('something wrong while searching. '); reject(response.response); }, ontimeout: function (response) { console.log('request timeout! '); reject(response.response); }, onabort: function (response) { console.log('request aborted. '); reject(response.response); } }); }) } } class TokyoToShoMatcher { constructor(pageContent) { this.pageContent = pageContent; this.magnetLinkPattern = /<a href="(magnet:\?xt=urn:btih:.*?)">/gi; this.seederCountPattern = /S: <span style="color: .*?">(\d+)<\/span>/gi; this.leederCountPattern = /L: <span style="color: .*?">(\d+)<\/span>/gi; this.completedCountPattern = /C: <span style="color: .*?">(\d+)<\/span>/gi; this.sizePattern = /\| Size: (.*?) \|/gi; } matchAll() { const result = []; let [mlMatch, scMatch, lcMatch, ccMatch, szMatch] = [undefined, undefined, undefined, undefined, undefined]; do { [mlMatch, scMatch, lcMatch, ccMatch, szMatch] = [ this.magnetLinkPattern.exec(this.pageContent), this.seederCountPattern.exec(this.pageContent), this.leederCountPattern.exec(this.pageContent), this.completedCountPattern.exec(this.pageContent), this.sizePattern.exec(this.pageContent), ]; if (mlMatch) { result.push({ link: mlMatch[1].trim(), sCount: scMatch[1], lCount: lcMatch[1], cCount: ccMatch[1], size: (szMatch && szMatch[1]) || '0MB', }); } } while (mlMatch); this.magnetLinkPattern.index = this.seederCountPattern.index = this.leederCountPattern.index = this.completedCountPattern.index = this.sizePattern.index = 0; return result; } } class SearchResultFilter { constructor(magnets) { if (!magnets) { this.magnets = []; } else { this.magnets = magnets; } this.bestOne = this.magnets.reduce((best, magnet) => { const current = { link: magnet.link, score: (magnet.sCount || 0) * 10 + (magnet.cCount || 0) * 5 + (magnet.lCount || 0) * 2, size: parseInt(magnet.size.slice(0, -2), 10) * (magnet.size.indexOf('GB') > -1 ? 1000 : 1), }; if (current.score < best.score) return best; const size = parseInt(magnet.size.slice(0, -2), 10) * (magnet.size.indexOf('GB') > -1 ? 1000 : 1); if (current.score > best.score) return current; if (current.size < best.size) return best; return current; }, { link: '', score: 0, size: 0 }); } best() { return this.bestOne; } } // Status and progress bar class DownloadOperationBtnStyle { static basic(elementStyle) { elementStyle.width = '22px'; elementStyle.height = '22px'; elementStyle.boxSizing = 'border-box'; elementStyle.marginLeft = '4px'; elementStyle.cursor = 'pointer'; } static unknown(elementStyle) { DownloadOperationBtnStyle.basic(elementStyle); elementStyle.color = 'white'; elementStyle.backgroundColor = 'grey'; elementStyle.borderRadius = '50%'; elementStyle.padding } static active(elementStyle) { DownloadOperationBtnStyle.basic(elementStyle); // elementStyle.backgroundColor = 'green'; } static waiting(elementStyle) { DownloadOperationBtnStyle.basic(elementStyle); // elementStyle.backgroundColor = 'green'; } static paused(elementStyle) { DownloadOperationBtnStyle.basic(elementStyle); // elementStyle.backgroundColor = 'orange'; } static removed(elementStyle) { DownloadOperationBtnStyle.basic(elementStyle); // elementStyle.backgroundColor = 'red'; } static complete(elementStyle) { DownloadOperationBtnStyle.basic(elementStyle); // elementStyle.backgroundColor = 'red'; elementStyle.pointer = 'default'; } static error(elementStyle) { DownloadOperationBtnStyle.basic(elementStyle); // elementStyle.backgroundColor = 'red'; } }; const DownloadOperationBtnText = { unknown: '⸮', active: '⏩', waiting: '•', paused: '⏸', removed: '⌦', complete: '?', error: '❌', }; const TaskStatusBtnCandidates = { 'unknown': ['unknown'], 'active': ['paused', 'removed'], 'waiting': ['removed'], 'paused': ['active', 'removed'], 'removed': ['active'], 'complete': ['active', 'complete'], 'error': ['error'], }; const TaskOperation = { unknown: (task) => { return (event) => { event.preventDefault(); TokyoToShoRequest.search(task.name).then((response) => { const magnets = (new TokyoToShoMatcher(response)).matchAll(); if (magnets && magnets.length) { const best = (new SearchResultFilter(magnets)).best(); LocalRequest.startTask({ name: task.name, uri: best.link }); } else { alert('无可用资源'); } }); } }, active: (task) => { return (event) => { console.log('resume'); } }, waiting: (task) => { return (event) => { event.preventDefault(); console.log('error'); } }, paused: (task) => { return (event) => { event.preventDefault(); console.log('error'); } }, removed: (task) => { return (event) => { event.preventDefault(); console.log('error'); } }, error: (task) => { return (event) => { event.preventDefault(); console.log('error'); } }, complete: (task) => { return (event) => { event.preventDefault(); console.log('error'); } }, } class DownloadOperationBtn { constructor(status) { this.btn = document.createElement('div'); console.log(status, DownloadOperationBtnStyle[status]); DownloadOperationBtnStyle[status](this.btn.style); this.btn.textContent = DownloadOperationBtnText[status]; } bind(action, fn) { this.btn.addEventListener(action, fn); return this; } appendTo(parent) { parent.appendChild(this.btn); } } class TaskProgressBar { constructor(task) { console.log(task); const percentage = (task.completedLength / task.totalLength) * 100; this.progressBar = document.createElement('div'); this.progressBar.style.position = 'absolute'; this.progressBar.style.top = this.progressBar.style.left = '0'; this.progressBar.style.width = '100%'; this.progressBar.style.height = '4px'; this.progressBar.style.backgroundColor = 'grey'; const alreadyProgress = document.createElement('div'); alreadyProgress.style.width = percentage + '%'; alreadyProgress.style.height = 'inherit'; alreadyProgress.style.backgroundColor = 'green'; this.progressBar.appendChild(alreadyProgress); } appendTo(parent) { parent.appendChild(this.progressBar); } } class TaskStatusBar { constructor(task) { this.statusBar = document.createElement('section'); this.statusBar.style.display = 'flex'; this.statusBar.style.margin = '-4px 0'; for (const cand of TaskStatusBtnCandidates[task.status]) { const btn = new DownloadOperationBtn(cand); btn.bind('click', TaskOperation[cand](task)).appendTo(this.statusBar); } } appendTo(parent) { parent.style.display = 'flex'; parent.style.margin = '4px 15%'; parent.appendChild(this.statusBar); } } class AE86 { constructor() { } run() { this.loadTasks().then((res) => { const taskNameMap = JSON.parse(res.responseText); this.initTaskStatElem(taskNameMap); }); } initTaskStatElem(taskNameMap) { for (const name in taskNameMap) { if (taskNameMap.hasOwnProperty(name)) { const task = taskNameMap[name]; const relatedElem = this.pageParser.nameElemMap[name]; const statusBar = new TaskStatusBar(task); statusBar.appendTo(relatedElem.statusBarParent); const progressBar = new TaskProgressBar(task); progressBar.appendTo(relatedElem.progressBarParent); } } } loadTasks() { this.pageParser = new PageParser(); return LocalRequest.listTask(this.pageParser.toTasks()); } } const ae86 = new AE86(); ae86.run();