您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
批量下载 Iwara 视频
当前为
// ==UserScript== // @name Iwara Download Tool // @name:zh-HK Iwara 批量下載工具 // @name:zh-CN Iwara 批量下载工具 // @name:ja Iwara バッチダウンローダー // @description Download videos from iwara.tv // @description:zh-HK 批量下載 Iwara 影片 // @description:zh-CN 批量下载 Iwara 视频 // @description:ja Iwara 動画バッチをダウンロード // @namespace https://github.com/dawn-lc/user.js // @icon https://iwara.tv/sites/all/themes/main/img/logo.png // @version 1.2.17 // @author dawn-lc // @license Apache-2.0 // @connect iwara.tv // @match *://*.iwara.tv/users/* // @match *://*.iwara.tv/videos // @match *://*.iwara.tv/videos*page=* // @match *://*.iwara.tv/subscriptions* // @exclude *://*.iwara.tv/videos/* // @grant GM_getValue // @grant GM_setValue // @grant GM_listValues // @grant GM_deleteValue // @grant GM_addValueChangeListener // @grant GM_removeValueChangeListener // @grant GM_addStyle // @grant GM_getResourceText // @grant GM_download // @grant GM_xmlhttpRequest // @grant GM_openInTab // @grant unsafeWindow // ==/UserScript== // @ts-nocheck (async function () { const library = { Net: { async get(url, parameter = [], referrer, headers = {}) { referrer = referrer || url; parameter = parameter || []; headers = headers || {}; if (parameter.length != 0) { url += '?'; for (var key in parameter) { url += key + '=' + parameter[key] + '&'; }; url = url.substr(0, url.length - 1); } let responseData; if (url.split('//')[1].split('/')[0] == window.location.hostname) { responseData = await fetch(url, { 'headers': Object.assign({ 'accept': 'application/json, text/plain, */*', 'cache-control': 'no-cache', 'content-type': 'application/x-www-form-urlencoded', }, headers), 'referrer': referrer, 'method': 'GET', 'mode': 'cors', 'redirect': 'follow', 'credentials': 'include' }); if (responseData.status >= 200 && responseData.status < 300) { const contentType = responseData.headers.get('Content-Type'); if (contentType != null) { if (contentType.indexOf('text') > -1) { return await responseData.text(); } if (contentType.indexOf('form') > -1) { return await responseData.formData(); } if (contentType.indexOf('video') > -1) { return await responseData.blob(); } if (contentType.indexOf('json') > -1) { return await responseData.json(); } } return responseData; }; } else { responseData = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, headers: Object.assign({ 'accept': 'application/json, text/plain, */*', 'cache-control': 'no-cache', 'content-type': 'application/x-www-form-urlencoded', }, headers), onload: function (response) { resolve(response); }, onerror: function (error) { reject(error); } }); }); if (responseData.status >= 200 && responseData.status < 300) { let headers = new Map(); responseData.responseHeaders.split('\r\n').forEach(element => { element = element.split(': '); headers.set(element[0], element[1]); }); responseData.headers = headers; return responseData; }; } }, async post(url, parameter, referrer) { referrer = referrer || window.location.href; if (typeof parameter == 'object') parameter = JSON.stringify(parameter); let responseData = await fetch(url, { 'headers': { 'accept': 'application/json, text/plain, */*', 'cache-control': 'no-cache', 'content-type': 'application/x-www-form-urlencoded', }, 'referrer': referrer, 'body': parameter, 'method': 'POST', 'mode': 'cors', 'redirect': 'follow', 'credentials': 'include' }); if (responseData.status >= 200 && responseData.status < 300) { const contentType = responseData.headers.get('Content-Type'); if (contentType != null) { if (contentType.indexOf('text') > -1) { return await responseData.text(); } if (contentType.indexOf('form') > -1) { return await responseData.formData(); } if (contentType.indexOf('video') > -1) { return await responseData.blob(); } if (contentType.indexOf('json') > -1) { return await responseData.json(); } } return responseData.text(); }; }, getQueryVariable(query, variable) { let vars = query.split('&'); for (let i = 0; i < vars.length; i++) { let pair = vars[i].split('='); if (pair[0] == variable) { return pair[1]; }; }; return ''; } }, UUID: { new() { let UUID = ''; for (let index = 0; index < 8; index++) { UUID += (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); } return UUID; } }, Dom: { createElement(detailedList) { if (detailedList instanceof Array) { return detailedList.map(item => this.createElement(item)); } else { return this.generateElement(document.createElement(detailedList.nodeType), detailedList); }; }, generateElement(item, detailedList) { for (const i in detailedList) { if (i == 'nodeType') continue; if (i == 'childs' && detailedList.childs instanceof Array) { detailedList.childs.forEach(child => { if (child instanceof HTMLElement) item.appendChild(child); else if (typeof (child) == 'string') item.insertAdjacentHTML('beforeend', child); else item.appendChild(this.createElement(child)); }); } else if (i == 'attribute') { for (const key in detailedList.attribute) { item.setAttribute(key, detailedList.attribute[key]); }; } else if (i == 'parent') { detailedList.parent.appendChild(item); } else if (i == 'before') { switch (typeof detailedList.before) { case 'object': if (detailedList.before instanceof HTMLElement) { detailedList.before.insertBefore(item, detailedList.before.childNodes[0]); } else { console.error('before节点异常!'); }; break; case 'string': try { if (eval(detailedList.before) instanceof HTMLElement) { eval(detailedList.before).insertBefore(item, eval(detailedList.before).childNodes[0]); } else { if (document.querySelector(detailedList.before)) { document.querySelector(detailedList.before).insertBefore(item, document.querySelector(detailedList.before).childNodes[0]); }; }; } catch (error) { console.error('计算before节点失败' + error); }; break; default: console.error('未知的before节点类型'); break; } } else if (detailedList[i] instanceof Object && item[i]) { Object.entries(detailedList[i]).forEach(([k, v]) => { item[i][k] = v; }); } else { item[i] = detailedList[i]; }; }; return item; }, moveElement(newElement, oldElement, isMovechildNode = false) { if (isMovechildNode) { for (let index = 0; index < oldElement.childNodes.length; index++) { newElement.appendChild(oldElement.childNodes[index].cloneNode(true)); }; }; oldElement.parentNode.replaceChild(newElement, oldElement); }, parseDom(dom) { return new DOMParser().parseFromString(dom, 'text/html'); }, addClass(node, className) { if (!node.classList.contains(className)) { node.classList.add(className); node.offsetWidth = node.offsetWidth; }; }, removeClass(node, className) { if (node.classList.contains(className)) { node.classList.remove(className); node.offsetWidth = node.offsetWidth; }; }, clearClass(node) { node.classList = null; node.offsetWidth = node.offsetWidth; } }, Class: { Queue() { let list = []; this.push = function (data) { if (data == null) { return false; } list.unshift(data); return true; }; this.pop = function () { return list.pop(); }; this.size = function () { return list.length; }; this.quere = function () { return list; }; } } }; const config = { synclistener: [], Type: { Download: { //aria2 aria2: 0, //默认 default: 1, //其他 others: 2 } }, Initialize: GM_getValue('Initialize', false), DownloadType: GM_getValue('DownloadType', 1), DownloadDir: GM_getValue('DownloadDir', ''), DownloadProxy: GM_getValue('DownloadProxy', ''), WebSocketAddress: GM_getValue('WebSocketAddress', 'ws://127.0.0.1:6800/'), WebSocketToken: GM_getValue('WebSocketToken', ''), WebSocketID: GM_getValue('WebSocketID', library.UUID.new()), sync() { let values = GM_listValues(); for (let index = 0; index < values.length; index++) { let key = values[index]; this.synclistener.push(GM_addValueChangeListener(key, function (name, old_value, new_value, remote) { if (remote) { config[name] = new_value; }; })); }; }, stopSync() { for (let index = 0; index < this.synclistener.length; index++) { GM_removeValueChangeListener(this.synclistener[index]); }; }, setInitialize: function (value) { this.Initialize = value; GM_setValue('Initialize', this.Initialize); }, setDownloadType: function (value) { this.DownloadType = Number(value); GM_setValue('DownloadType', this.DownloadType); }, setDownloadDir: function (value) { this.DownloadDir = value; GM_setValue('DownloadDir', this.DownloadDir); }, setDownloadProxy: function (value) { this.DownloadProxy = value; GM_setValue('DownloadProxy', this.DownloadProxy); }, setWebSocketAddress: function (value) { this.WebSocketAddress = value; GM_setValue('WebSocketAddress', this.WebSocketAddress); }, setWebSocketToken: function (value) { this.WebSocketToken = value; GM_setValue('WebSocketToken', this.WebSocketToken); }, setWebSocketID: function (value) { this.WebSocketID = value; GM_setValue('WebSocketID', this.WebSocketID); } }; const resources = { PluginStyle: { nodeType: 'style', innerHTML: ` .selectButton { user-select: none; position: relative; width: 100%; height: 100%; } .selectButton[checked=true]:before { position: absolute; display: block; width: 100%; height: 100%; left: 50%; top: 50%; transform: translate(-50%, -50%); background-color: rgba(150, 150, 150, 0.6); content: ''; } .selectButton[checked=true]:after { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%) scale(1.32, 0.96); font-weight: 900; font-size: 36px; color: rgb(20, 20, 20); content: '√'; } .controlPanel { display: none; position: fixed; z-index: 2147483646; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0, 0, 0, 0.4); scrollbar-width: none; /* firefox */ -ms-overflow-style: none; /* IE 10+ */ overflow-x: hidden; overflow-y: auto; } .controlPanel::-webkit-scrollbar { display: none; /* Chrome Safari */ } .controlPanel-content { background-color: #fefefe; margin: 15% auto; padding: 20px; border: 1px solid #888; width: 60%; max-width: 720px; } .controlPanelClose { color: #aaa; float: right; font-size: 28px; font-weight: bold; } .controlPanelClose:hover, .controlPanelClose:focus { color: black; text-decoration: none; cursor: pointer; } .tips { letter-spacing:3px cursor: pointer; box-sizing: border-box; display: none; width: 100%; max-width: 640px; font-size: 0.825em; border-top-right-radius: 5px; border-top-left-radius: 5px; background: #ffffff; box-shadow: 0 2.8px 2.2px rgba(0, 0, 0, 0.02), 0 6.7px 5.3px rgba(0, 0, 0, 0.028), 0 12.5px 10px rgba(0, 0, 0, 0.035), 0 22.3px 17.9px rgba(0, 0, 0, 0.042), 0 41.8px 33.4px rgba(0, 0, 0, 0.05), 0 100px 80px rgba(0, 0, 0, 0.07); -webkit-transition: 0.2s ease-in; transition: 0.2s ease-in; } @media (min-width: 640px) { .tips { border-radius: 5px; margin-bottom: 0.5em; } } .tipsActive { display: -webkit-box; display: flex; -webkit-animation: slideinBottom 5s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards; animation: slideinBottom 5s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards; } .tipsWait { display: -webkit-box; display: flex; -webkit-animation: slidein 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards; animation: slidein 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards; } .tipsWarning { background: #bf360c; color: white; } .tipsSuccess { background: #43a047; color: white; } .tipsActions { width: 100%; max-width: 768px; margin: 0 auto; display: -webkit-box; display: flex; -webkit-box-orient: vertical; -webkit-box-direction: normal; flex-flow: column; } @media (min-width: 640px) { .tipsActions { -webkit-box-orient: horizontal; -webkit-box-direction: normal; flex-flow: row; } } .tipsContainer { z-index: 2147483647; box-sizing: border-box; padding: 0em 1em; position: fixed; width: 100%; max-width: 640px; margin: 0 auto; display: -webkit-box; display: flex; -webkit-box-orient: vertical; -webkit-box-direction: normal; flex-flow: column; bottom: 0; left: 0; right: 0; -webkit-box-align: center; align-items: center; -webkit-box-pack: center; justify-content: center; } @media (min-width: 640px) { .tipsContainer { padding: 0 1em; } } @media (min-width: 1024px) { .tipsContainer { left: initial; right: 0; } } .tipsIcon { height: 60px; width: 60px; box-sizing: border-box; padding: 1em; display: none; -webkit-box-align: center; align-items: center; -webkit-box-pack: center; justify-content: center; } .tipsIcon svg { height: 100%; } @media (min-width: 640px) { .tipsIcon { display: -webkit-box; display: flex; } } .tipsIcon ~ .tipsContent { padding: 1em; } @media (min-width: 640px) { .tipsIcon ~ .tipsContent { padding: 1em 1em 1em 0; } } .tipsContent { box-sizing: border-box; padding: 1em; } .tipsContent h2 { margin: 0 0 0.25em 0; padding: 0; font-size: 1.2em; } .tipsContent p { margin: 0; padding: 0; font-size: 1em; } @-webkit-keyframes slidein { 0% { opacity: 0; -webkit-transform: translateY(100%); transform: translateY(100%); } 100% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } } @keyframes slidein { 0% { opacity: 0; -webkit-transform: translateY(100%); transform: translateY(100%); } 100% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } } @-webkit-keyframes slideinBottom { 0% { opacity: 0; -webkit-transform: translateY(100%); transform: translateY(100%); } 15% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } 85% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(100%); transform: translateY(100%); } } @keyframes slideinBottom { 0% { opacity: 0; -webkit-transform: translateY(100%); transform: translateY(100%); } 15% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } 85% { opacity: 1; -webkit-transform: translateY(0); transform: translateY(0); } 100% { opacity: 0; -webkit-transform: translateY(100%); transform: translateY(100%); } } `, parent: document.head }, PluginUI: { nodeType: 'div', id: 'PluginUI', className: 'btn-group', childs: [{ nodeType: 'button', type: 'button', id: 'PluginUIStartUp', title: '下载助手', className: 'btn btn-primary btn-sm dropdown-toggle', childs: [{ nodeType: 'span', className: 'glyphicon glyphicon-download-alt' }, { nodeType: 'text', innerHTML: '下载助手' }], onclick: function () { if (this.parentNode.classList.contains('open')) { this.parentNode.classList.remove('open'); } else { this.parentNode.classList.add('open'); }; } }, { nodeType: 'ul', className: 'dropdown-menu', attribute: { role: 'menu' }, childs: [{ nodeType: 'li', style: 'cursor: pointer;', id: 'DownloadSelected', innerHTML: '<a><span class="glyphicon glyphicon-check"></span>下载所选</a>', onclick: function () { main.DownloadSelected(); document.getElementById('PluginUIStartUp').click(); } }, { nodeType: 'li', style: 'display: none;cursor: pointer;', id: 'DownloadAll', innerHTML: '<a><span class="glyphicon glyphicon-save"></span>下载所有</a>', onclick: function () { main.DownloadAll(); document.getElementById('PluginUIStartUp').click(); } }, { nodeType: 'li', style: 'cursor: pointer;', id: 'ManualDownload', innerHTML: '<a><span class="glyphicon glyphicon-edit"></span>手动下载</a>', onclick: function () { main.ManualParseDownloadAddress(); document.getElementById('PluginUIStartUp').click(); } }, { nodeType: 'li', style: 'cursor: pointer;', id: 'pluginSet', innerHTML: '<a><span class="glyphicon glyphicon-cog"></span>设置</a>', onclick: function () { let ControlPanel = document.getElementById('PluginControlPanel'); for (let index = 0; index < ControlPanel.querySelectorAll('input[name=DownloadType]').length; index++) { const element = ControlPanel.querySelectorAll('input[name=DownloadType]')[index]; if (Number(element.value) == config.DownloadType) { element.setAttribute('checked', ''); break; }; }; ControlPanel.querySelector("#DownloadDir").value = config.DownloadDir; ControlPanel.querySelector("#DownloadProxy").value = config.DownloadProxy; ControlPanel.querySelector("#WebSocketAddress").value = config.WebSocketAddress; ControlPanel.querySelector("#WebSocketToken").value = config.WebSocketToken; ControlPanel.style.display = 'block'; document.getElementById('PluginUIStartUp').click(); } } ] } ], parent: document.getElementById('user-links') }, PluginControlPanel: { nodeType: 'div', id: 'PluginControlPanel', className: 'controlPanel', childs: [{ nodeType: 'div', className: 'controlPanel-content', childs: [{ nodeType: 'span', className: 'controlPanelClose', innerHTML: '×', onclick: function () { config.setDownloadType(config.DownloadType); config.setDownloadDir(config.DownloadDir); config.setDownloadProxy(config.DownloadProxy); config.setWebSocketAddress(config.WebSocketAddress); config.setWebSocketToken(config.WebSocketToken); config.setWebSocketID(config.WebSocketID); config.setInitialize(true); this.parentNode.parentNode.style.display = 'none'; main.run(); } }, { nodeType: 'div', id: 'controlPanelItem', childs: [{ nodeType: 'div', style: 'margin: 10px 0;', childs: [{ nodeType: 'label', style: 'margin: 0px 10px 0px 0px;', innerHTML: '下载方式:' }, { nodeType: 'input', name: 'DownloadType', type: 'radio', value: config.Type.Download.aria2, onchange: ({ target }) => config.setDownloadType(target.value) }, { nodeType: 'label', style: 'margin: 0px 20px 0px 0px;', innerHTML: 'Aria2' }, { nodeType: 'input', name: 'DownloadType', type: 'radio', disabled: true, value: config.Type.Download.default, onchange: ({ target }) => config.setDownloadType(target.value) }, { nodeType: 'label', style: 'margin: 0px 20px 0px 0px;', innerHTML: '浏览器默认' }, { nodeType: 'input', name: 'DownloadType', type: 'radio', value: config.Type.Download.others, onchange: ({ target }) => config.setDownloadType(target.value) }, { nodeType: 'label', style: 'margin: 0px 20px 0px 0px;', innerHTML: '其他下载器' } ] }, { nodeType: 'div', style: 'margin: 10px 0;', childs: [{ nodeType: 'label', style: 'margin-right: 5px;', innerHTML: '下载到:', for: 'DownloadDir' }, { nodeType: 'input', id: 'DownloadDir', type: 'text', value: config.DownloadDir, onchange: ({ target }) => config.setDownloadDir(target.value), style: 'width:100%;' } ] }, { nodeType: 'div', style: 'margin: 10px 0;', childs: [{ nodeType: 'label', style: 'margin-right: 5px;', innerHTML: '代理服务器:', for: 'DownloadProxy' }, { nodeType: 'input', id: 'DownloadProxy', type: 'text', value: config.DownloadProxy, onchange: ({ target }) => config.setDownloadProxy(target.value), style: 'width:100%;' } ] }, { nodeType: 'div', style: 'margin: 10px 0;', childs: [{ nodeType: 'label', style: 'margin-right: 5px;', innerHTML: 'Aria2 RPC WebSocket 地址:', for: 'WebSocketAddress' }, { nodeType: 'input', id: 'WebSocketAddress', type: 'text', value: config.WebSocketAddress, onchange: ({ target }) => config.setWebSocketAddress(target.value), style: 'width:100%;' } ] }, { nodeType: 'div', style: 'margin: 10px 0;', childs: [{ nodeType: 'label', style: 'margin-right: 5px;', innerHTML: 'Aria2 RPC Token(密钥):', for: 'WebSocketToken' }, { nodeType: 'input', id: 'WebSocketToken', type: 'password', value: config.WebSocketToken, onchange: ({ target }) => config.setWebSocketToken(target.value), style: 'width:100%;' } ] }, { nodeType: 'div', style: 'margin: 10px 0;', childs: [{ nodeType: 'label', style: 'margin-right: 5px;', innerHTML: '双击视频选中,再次双击取消选中。选中仅在本页面有效!<br />在作者用户页面可以点击下载全部,将会搜索该用户的所有视频进行下载。<br />插件下载视频前会检查视频简介,如果在简介中发现疑似第三方下载链接,将会弹窗提示,您可以手动打开视频页面选择。<br />手动下载需要您提供视频ID!' }] }] } ] }], parent: document.body }, PluginTips: { nodeType: 'section', id: 'PluginTips', className: 'tipsContainer', childs: [], parent: document.body }, Tips: { Info: { nodeType: 'div', className: 'tips', childs: [{ nodeType: 'div', className: 'tipsIcon', innerHTML: '<svg focusable="false" data-prefix="fas" data-icon="info-circle" class="svg-inline--fa fa-info-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"></path></svg>' }, { nodeType: 'div', className: 'tipsContent', childs: [{ nodeType: 'h2', }, { nodeType: 'p', }] }], onclick: function () { this.remove(); }, onwebkitanimationend: function () { if (!this.classList.contains('tipsWait')) { this.remove(); } } }, Warning: { nodeType: 'div', className: 'tips tipsWarning', childs: [{ nodeType: 'div', className: 'tipsIcon', innerHTML: '<svg focusable="false" data-prefix="fas" data-icon="exclamation-circle" class="svg-inline--fa fa-exclamation-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"></path></svg>' }, { nodeType: 'div', className: 'tipsContent', childs: [{ nodeType: 'h2', }, { nodeType: 'p', }] }], onclick: function () { this.remove(); }, onwebkitanimationend: function () { if (!this.classList.contains('tipsWait')) { this.remove(); } } }, Success: { nodeType: 'div', className: 'tips tipsSuccess', childs: [{ nodeType: 'div', className: 'tipsIcon', innerHTML: '<svg focusable="false" data-prefix="fas" data-icon="check-circle" class="svg-inline--fa fa-check-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"></path></svg>' }, { nodeType: 'div', className: 'tipsContent', childs: [{ nodeType: 'h2', }, { nodeType: 'p', }] }], onclick: function () { this.remove(); }, onwebkitanimationend: function () { if (!this.classList.contains('tipsWait')) { this.remove(); } } } } }; const main = { Aria2WebSocket: null, PluginControlPanel: null, PluginTips: null, PluginDownloadList: {}, Runing: false, start() { //创建并注入UI library.Dom.createElement(resources.PluginStyle); library.Dom.createElement(resources.PluginUI); main.PluginTips = library.Dom.createElement(resources.PluginTips); main.PluginControlPanel = library.Dom.createElement(resources.PluginControlPanel); window.onclick = function (event) { if (!event.path.includes(document.getElementById('PluginUI'))) { if (document.getElementById('PluginUI').classList.contains('open')) { document.getElementById('PluginUI').classList.remove('open'); }; }; }; main.Info('Iwara批量下载助手', '正在启动...'); if (!config.Initialize) { //首次启动 document.getElementById('pluginSet').click(); } else { //正常启动 main.run(); }; }, run() { if (!main.Runing) { config.sync(); for (let index = 0; index < document.querySelectorAll('.node-video').length; index++) { const element = document.querySelectorAll('.node-video')[index]; if (!element.classList.contains('node-full')) { let selectButton = document.createElement('div'); selectButton.classList.add('selectButton'); selectButton.setAttribute('checked', 'false'); selectButton.setAttribute('linkData', element.querySelector('a').href); library.Dom.moveElement(selectButton, element.querySelector('a'), true); let clickTimer = null; selectButton.ondblclick = function (event) { if (clickTimer) { window.clearTimeout(clickTimer); clickTimer = null; }; if (selectButton.getAttribute('checked') === 'true') { selectButton.setAttribute('checked', 'false'); } else { selectButton.setAttribute('checked', 'true'); }; }; selectButton.onclick = function () { if (clickTimer) { window.clearTimeout(clickTimer); clickTimer = null; }; clickTimer = window.setTimeout(function () { GM_openInTab(selectButton.getAttribute('linkData'), { active: true, insert: true, setParent: true }); }, 250); }; }; }; if (window.location.href.split('/')[3] == 'users') { document.getElementById('DownloadAll').style.display = 'inline'; }; //main.updata(); main.Runing = true; main.Success('Iwara批量下载助手', '已启动!'); } switch (config.DownloadType) { case config.Type.Download.aria2: if (main.Aria2WebSocket != null) main.Aria2WebSocket.close(); main.ConnectionWebSocket(); break; case config.Type.Download.default: break; case config.Type.Download.others: break; default: console.log('未知的下载模式!'); break; }; }, Info(title, content, wait = false) { let tips = library.Dom.createElement(resources.Tips.Info); tips.querySelector('h2').innerText = title; tips.querySelector('p').innerHTML = content; main.PluginTips.appendChild(tips); if (wait) { tips.classList.add('tipsWait'); } else { tips.classList.add('tipsActive'); } }, Success(title, content, wait = false) { let tips = library.Dom.createElement(resources.Tips.Success); tips.querySelector('h2').innerText = title; tips.querySelector('p').innerHTML = content; main.PluginTips.appendChild(tips); if (wait) { tips.classList.add('tipsWait'); } else { tips.classList.add('tipsActive'); } }, Warning(title, content, wait = false) { let tips = library.Dom.createElement(resources.Tips.Warning); tips.querySelector('h2').innerText = title; tips.querySelector('p').innerHTML = content; main.PluginTips.appendChild(tips); if (wait) { tips.classList.add('tipsWait'); } else { tips.classList.add('tipsActive'); } }, ConnectionWebSocket() { try { main.Info('Aria2 RPC', '正在连接...'); main.Aria2WebSocket = new WebSocket(config.WebSocketAddress + 'jsonrpc'); main.Aria2WebSocket.onopen = wsopen; main.Aria2WebSocket.onmessage = wsmessage; main.Aria2WebSocket.onclose = wsclose; } catch (err) { config.Initialize = false; main.Aria2WebSocket.close(); main.Warning('Aria2 RPC', '连接 Aria2 RPC 时出现错误! <br />请检查Aria2 RPC WebSocket地址是否正确(尽量使用wss而非ws) <br />' + err); } function wsopen() { main.Success('Aria2 RPC', '连接成功!'); }; function wsmessage() { //todo }; function wsclose() { main.Warning('Aria2 RPC', '已断开连接!'); }; }, async ManualParseDownloadAddress() { let ID = prompt('请输入需要下载的视频ID', ''); if (ID.split('_')[1] != undefined) { ID = ID.split('_')[1]; }; await main.ParseDownloadAddress(ID); main.Success('下载', '解析完成!'); }, async DownloadSelected() { main.Info('下载', '开始解析...'); for (let index = 0; index < document.getElementsByClassName('node-video').length; index++) { const element = document.getElementsByClassName('node-video')[index]; if (!element.classList.contains('node-full')) { if (element.getElementsByClassName('selectButton')[0].getAttribute('checked') === 'true') { await main.ParseDownloadAddress(element); }; }; }; main.Success('下载', '已全部解析完成!'); }, async DownloadAll() { main.Info('下载', '开始解析...'); if (document.getElementById('block-views-videos-block-2').getElementsByClassName('more-link').length == 0) { let videoListPage = library.Dom.parseDom(await library.Net.get(window.location.href, undefined, window.location.href)); let videosList = videoListPage.querySelector('#block-views-videos-block-2').querySelectorAll('.node-video'); for (let index = 0; index < videosList.length; index++) { const element = videosList[index]; await main.ParseDownloadAddress(element); }; main.Success('下载', '已全部解析完成!'); } else { await main.GetAllData(document.querySelector('div.more-link').querySelector('a').href, [], window.location.href); }; }, async GetAllData(videoListUrl, data, referrer) { let videoListPage = library.Dom.parseDom(await library.Net.get(videoListUrl, data, referrer)); let videosList = videoListPage.querySelector('.view-videos').querySelectorAll('.node-video'); for (let index = 0; index < videosList.length; index++) { const element = videosList[index]; await main.ParseDownloadAddress(element); }; if (videoListPage.getElementsByClassName('pager-next').length != 0) { await main.GetAllData(videoListPage.getElementsByClassName('pager-next')[0].querySelector('a').href, data, referrer); }; main.Success('下载', '已全部解析完成!'); }, VideoInfo: { createNew: async function (Data) { switch (typeof Data) { case 'object': this.Element = Data; if (this.Element.querySelector('.selectButton') != null) { this.Url = this.Element.querySelector('.selectButton').getAttribute('linkData'); } else { this.Url = this.Element.querySelector('a').href; }; this.ID = this.Url.split('/')[4]; break; case 'string': this.ID = Data; this.Url = 'https://ecchi.iwara.tv/videos/' + this.ID; break; default: main.Warning('警告', '错误的类型!'); return this; } this.getID = function () { return this.ID; }; this.getUrl = function () { return this.Url; }; this.Page = library.Dom.parseDom(await library.Net.get(this.getUrl(), null, window.location.href)); this.getLock = function () { if (this.Page.querySelector('.well') != null) { return true; } return false; }; this.getAuthor = function () { return this.Page.querySelector('.submitted').querySelector('a.username').innerText; }; this.getName = function () { return this.Page.querySelector('.submitted').querySelector('h1.title').innerText; }; this.Source = await library.Net.get('https://ecchi.iwara.tv/api/video/' + this.getID(), null, this.getUrl()); this.getDownloadQuality = function () { if (this.Source.length != 0) { return this.Source[0].resolution; }; return null; }; this.getDownloadUrl = function () { return decodeURIComponent('https:' + this.Source[0].uri); }; this.getDownloadFileName = function () { return library.Net.getQueryVariable(this.getDownloadUrl, 'file').split('/')[3]; }; this.getComment = function () { let comment = ''; try { let commentArea = this.Page.getElementsByClassName('node-info')[0].getElementsByClassName('field-type-text-with-summary field-label-hidden')[0].getElementsByClassName('field-item even'); for (let index = 0; index < commentArea.length; index++) { const element = commentArea[index]; comment += element.innerText.toLowerCase(); }; } catch (error) { comment = ''; }; return comment; }; return this; } }, DownloadLinkCharacteristics: [ '/s/', 'mega.nz/file/', 'drive.google.com', '高画質' ], CheckIsHaveDownloadLink(comment) { if (comment != null) return false; for (let index = 0; index < main.DownloadLinkCharacteristics.length; index++) { if (comment.indexOf(main.DownloadLinkCharacteristics[index]) != -1) return true; }; return false; }, async ParseDownloadAddress(Data) { let videoInfo = await main.VideoInfo.createNew(Data); if (videoInfo.getLock()) { main.Warning('警告', '该视频已锁定!'); } else { if (main.CheckIsHaveDownloadLink(videoInfo.getComment())) { main.Warning('警告', '<a href="' + videoInfo.getUrl() + '" title="' + videoInfo.getName() + '" target="_blank" >' + videoInfo.getName() + '</a> 发现疑似第三方高画质下载链接,请手动处理!', true); } else { if (videoInfo.getDownloadQuality() == 'Source') { main.SendDownloadRequest(videoInfo, document.cookie); } else { main.Warning('警告', '<a href="' + videoInfo.getUrl() + '" title="' + videoInfo.getName() + '" target="_blank" >' + videoInfo.getName() + '</a> 没有解析到原画下载地址,请手动处理!', true); } }; }; }, aria2Download(Info, Cookie) { let Action = { 'jsonrpc': '2.0', 'method': 'aria2.addUri', 'id': config.WebSocketID, 'params': [ 'token:' + config.WebSocketToken, [Info.getDownloadUrl()], { 'referer': 'https://ecchi.iwara.tv/', 'header': [ 'Cookie:' + Cookie ], 'out': '![' + Info.getID() + ']' + Info.getName().replace(/[\\\\/:*?\"<>|]/g, '') + '.mp4', 'dir': config.DownloadDir + Info.getAuthor().replace(/[\\\\/:*?\"<>|.]/g, '') } ] }; if (config.DownloadProxy != '') { Action.params[Action.params.length - 1]['all-proxy'] = config.DownloadProxy; }; main.Aria2WebSocket.send(JSON.stringify(Action)); main.Info('提示', '已将 ' + Info.getName() + ' 的下载地址推送到Aria2!'); }, async SendDownloadRequest(Info, Cookie) { switch (config.DownloadType) { case config.Type.Download.aria2: main.aria2Download(Info, Cookie); break; case config.Type.Download.default: main.Warning('警告', '默认下载方式存在问题,暂时停止使用。<br>已调用其他方式下载!'); //break; case config.Type.Download.others: main.Info('提示', '已将下载请求提交给浏览器!'); GM_openInTab(Info.getDownloadUrl(), { active: true, insert: true, setParent: true }); break; default: main.Warning('配置错误', '未知的下载模式!'); break; } } }; main.start(); window.onbeforeunload = function () { config.stopSync(); }; })();