您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
批量下载 Iwara 视频
当前为
// ==UserScript== // @name Iwara Download Tool // @description Download videos from iwara.tv // @name:ja Iwara バッチダウンローダー // @description:ja Iwara 動画バッチをダウンロード // @name:zh-CN Iwara 批量下载工具 // @description:zh-CN 批量下载 Iwara 视频 // @icon https://iwara.tv/sites/all/themes/main/img/logo.png // @namespace https://github.com/dawn-lc/user.js // @version 2.1.70 // @author dawn-lc // @license Apache-2.0 // @copyright 2022, Dawnlc (https://dawnlc.me/) // @source https://github.com/dawn-lc/user.js // @supportURL https://github.com/dawn-lc/user.js/issues // @connect * // @match *://*.iwara.tv/* // @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 GM_cookie // @grant unsafeWindow // @require https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/react/16.13.1/umd/react.production.min.js // @require https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/react-dom/16.13.1/umd/react-dom.production.min.js // ==/UserScript== (async function () { function UUID() { let UUID = ''; for (let index = 0; index < 8; index++) { UUID += (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); } return UUID; } function getType(obj) { return Object.prototype.toString.call(obj).split(' ')[1].slice(0, -1); } function sourceRender(vdata) { let RenderDOM; for (const item in vdata) { switch (item) { case 'nodeType': RenderDOM = document.createElement(vdata.nodeType); break; case 'attribute': for (const key in vdata.attribute) { RenderDOM.setAttribute(key, vdata.attribute[key]); } break; case 'className': if (getType(vdata.className) == 'Array') { RenderDOM.className = vdata.className.join(' '); } else { RenderDOM.className = vdata.className.toString(); } break; case 'childs': switch (getType(vdata.childs)) { case 'Array': vdata.childs.forEach((child) => { if (child instanceof HTMLElement) { RenderDOM.appendChild(child); } else if (getType(child) == 'string') { RenderDOM.insertAdjacentHTML('beforeend', child); } else { RenderDOM.appendChild(sourceRender(child)); } }); break; case 'String': RenderDOM.insertAdjacentHTML('beforeend', vdata.childs); break; case 'Object': RenderDOM.appendChild(sourceRender(vdata.childs)); break; default: break; } break; case 'parent': vdata.parent.appendChild(RenderDOM); break; case 'before': vdata.before.insertBefore(RenderDOM, vdata.before.childNodes[0]); break; default: if (vdata[item] instanceof Object && RenderDOM[item]) { Object.entries(vdata[item]).forEach(([k, v]) => { RenderDOM[item][k] = v; }); } else { RenderDOM[item] = vdata[item]; } break; } } return RenderDOM; } function reactRender(vdata, index) { let VirtualDOM; if (vdata != null && vdata.nodeType != undefined) { VirtualDOM = { type: vdata.nodeType }; delete vdata.nodeType; if (vdata.childs != undefined) { if (VirtualDOM.children == undefined) VirtualDOM.children = []; if (vdata.childs instanceof Array) { VirtualDOM.children = React.Children.toArray(vdata.childs.map((item) => reactRender(item))); } else { VirtualDOM.children.push(reactRender(vdata.childs)); } delete vdata.childs; } if (VirtualDOM.props == undefined) VirtualDOM.props = {}; if (vdata.className != undefined) { VirtualDOM.props = Object.assign({ className: vdata.className }, VirtualDOM.props); delete vdata.className; } if (vdata.attribute != undefined) { VirtualDOM.props = Object.assign(vdata.attribute, VirtualDOM.props); delete vdata.attribute; } if (index != undefined) VirtualDOM.props = Object.assign({ key: index }, VirtualDOM.props); for (const key in vdata) { VirtualDOM.props[key] = vdata[key]; delete vdata[key]; } } else { return vdata; } return React.createElement(VirtualDOM.type, VirtualDOM.props, VirtualDOM.children || undefined); } async function get(url, parameter = [], referrer = window.location.hostname, headers = {}) { url += '?'; for (var key in parameter) { url += key + '=' + parameter[key] + '&'; } url = url.substring(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, */*', '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, */*', '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 function post(url, parameter, referrer = window.location.href) { if (typeof parameter == 'object') parameter = JSON.stringify(parameter); let responseData; if (url.split('//')[1].split('/')[0] == window.location.hostname) { responseData = await fetch(url, { 'headers': { 'accept': 'application/json, text/plain, */*', 'content-type': 'application/json;charset=UTF-8' }, '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(); } } else { responseData = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: url, headers: { 'Accept': 'application/json, text/plain, */*', 'Content-Type': 'application/json' }, data: parameter, onload: function (response) { resolve(response); }, onerror: function (error) { reject(error); } }); }); if (responseData.status >= 200 && responseData.status < 300) { if (responseData.responseHeaders.indexOf('json') > -1) { return JSON.parse(responseData.responseText); } else { return responseData.response; } } else { return responseData; } } } function parseDom(dom) { return new DOMParser().parseFromString(dom, 'text/html'); } function 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 null; } let DownloadType; (function (DownloadType) { DownloadType[DownloadType["aria2"] = 0] = "aria2"; DownloadType[DownloadType["default"] = 1] = "default"; DownloadType[DownloadType["others"] = 2] = "others"; })(DownloadType || (DownloadType = {})); let APIType; (function (APIType) { APIType[APIType["http"] = 0] = "http"; APIType[APIType["ws"] = 1] = "ws"; APIType[APIType["https"] = 2] = "https"; APIType[APIType["wss"] = 3] = "wss"; })(APIType || (APIType = {})); let TipsType; (function (TipsType) { TipsType[TipsType["Info"] = 0] = "Info"; TipsType[TipsType["Warning"] = 1] = "Warning"; TipsType[TipsType["Success"] = 2] = "Success"; TipsType[TipsType["Progress"] = 3] = "Progress"; })(TipsType || (TipsType = {})); class Queue { queue; push; pop; remove; length; clear; constructor() { this.queue = []; this.push = function (data) { this.queue.unshift(data); }; this.pop = function () { return this.queue.pop(); }; this.remove = function (id) { let index = this.queue.indexOf(id); if (index > -1) { this.queue.splice(index, 1); } }; this.length = function () { return this.queue.length; }; this.clear = function () { this.queue = []; }; } } class pluginTips { WaitingQueue; DownloadingQueue; static typeIcon = { Info: '<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>', Warning: '<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>', Success: '<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg>', Progress: '<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path></svg>' }; static Container = sourceRender({ nodeType: 'section', attribute: { id: 'PluginTips' }, className: 'tipsContainer', parent: document.body }); constructor() { this.DownloadingQueue = new Queue(); this.WaitingQueue = new Queue(); } downloadComplete(id, name) { this.DownloadingQueue.remove(id); pluginTips.Container.children.namedItem(id).remove(); if (this.WaitingQueue.length() > 0) { let downloadTask = this.WaitingQueue.pop(); if (GM_info.downloadMode == 'native') { this.progress(downloadTask.name + ' 下载中...', { id: downloadTask.id }); } else { this.info('下载', downloadTask.name + ' 已开始下载!'); } this.DownloadingQueue.push(downloadTask.task); GM_download(downloadTask.task); } } downloading(id, value) { let downloadTask = pluginTips.Container.children.namedItem(id).querySelector('.value'); downloadTask.setAttribute('value', value.toFixed(2)); downloadTask.style.width = value.toFixed(2) + '%'; } info(title, content, wait = false) { console.info('Iwara 批量下载工具', content); new tips(TipsType.Info, title, content, wait); } warning(title, content, wait = false) { console.warn('Iwara 批量下载工具', content); new tips(TipsType.Warning, title, content, wait); } success(title, content, wait = false) { console.log('Iwara 批量下载工具', content); new tips(TipsType.Success, title, content, wait); } progress(title, content) { new tips(TipsType.Progress, title, { nodeType: 'div', className: 'Progress', childs: [{ nodeType: 'div', className: 'value', attribute: { value: 0 } }] }, true, content.id); } } class tips { id; type; wait; constructor(type, title, content, wait = false, id = null) { this.type = type; this.id = id; this.wait = wait; sourceRender(Object.assign({ nodeType: 'div', childs: [{ nodeType: 'div', className: 'tipsIcon', innerHTML: pluginTips.typeIcon[TipsType[type]] }, { nodeType: 'div', className: 'tipsContent', childs: [{ nodeType: 'h2', childs: title }, { nodeType: 'p', childs: content }] }], parent: pluginTips.Container }, this.style(), this.attribute(), this.event())); } event() { return { onclick: (e) => { if (this.wait) { if (this.type != TipsType.Progress) { e.currentTarget.remove(); } } else { e.currentTarget.remove(); } }, onanimationend: (e) => { if (!this.wait) { e.currentTarget.remove(); } } }; } style() { let style = { className: ['tips'] }; style.className.push('tips' + TipsType[this.type]); if (this.wait) { style.className.push('tipsWait'); } else { style.className.push('tipsActive'); } return style; } attribute() { if (this.id != undefined) { return { attribute: { id: this.id } }; } else { return {}; } } } class VideoInfo { Url; ID; Page; Exist = true; Private = true; Source; getAuthor; getName; getDownloadQuality; getDownloadUrl; getSourceFileName; getComment; getLock; getFileName; constructor(videoID) { this.ID = videoID.toLowerCase(); this.Url = 'https://' + window.location.hostname + '/videos/' + this.ID; return this; } async init(cooike = {}) { try { this.Page = parseDom(await get(this.Url, [], window.location.href, cooike)); this.Exist = this.Page.querySelector('video') != null; if (this.Exist) { if (this.Page.querySelector('.well') == null) { this.Private = false; } else { if (cooike['cooike'] == undefined) await this.init({ 'cooike': PluginControlPanel.Cookies }); return; } this.Source = await get('https://' + window.location.hostname + '/api/video/' + this.ID, [], this.Url, cooike); this.getAuthor = function () { return this.Page.querySelector('.submitted').querySelector('a.username').innerText.replace(/[\\\\/:*?\"<>|.]/g, '_'); }; this.getName = function () { return this.Page.querySelector('.submitted').querySelector('h1.title').innerText.replace(/[\\\\/:*?\"<>|.]/g, '_'); }; this.getFileName = function () { return replaceVar(PluginControlPanel.state.FileName).replace('%#TITLE#%', this.getName()).replace('%#ID#%', this.ID).replace('%#AUTHOR#%', this.getAuthor()).replace('%#SOURCE_NAME#%', this.getSourceFileName()); }; this.getDownloadQuality = function () { if (this.Source.length == 0) return 'null'; return this.Source[0].resolution; }; this.getDownloadUrl = function () { return decodeURIComponent('https:' + this.Source.find(x => x.resolution == this.getDownloadQuality()).uri); }; this.getSourceFileName = function () { return getQueryVariable(this.getDownloadUrl(), 'file').split('/')[3]; }; this.getComment = function () { let commentNode; try { commentNode = Array.from(this.Page.querySelector('.node-info').querySelector('.field-type-text-with-summary.field-label-hidden').querySelectorAll('.field-item.even')); } catch (error) { return ''; } return commentNode.map((element) => element.innerText).join('\n'); }; } } catch (error) { PluginTips.warning('解析模块', '视频信息解析失败:' + error.toString(), true); } } } class pluginUI extends React.Component { showCondition; constructor(props) { super(props); this.showCondition = false; this.state = { style: { base: { cursor: 'pointer' }, disable: { display: 'none' } }, main: 'btn-group', downloadEnable: false, downloadManualParseEnable: false, downloadAllEnable: false, downloadSelectedEnable: false }; } show() { this.showCondition = true; this.setState({ main: 'btn-group open' }); this.downloadManualParseSwitch(true); if (document.querySelectorAll('.selectButton').length > 0) { this.downloadSelectedSwitch(true); } if (window.location.href.indexOf('/users/') > -1) { this.downloadAllSwitch(true); } } hide() { this.showCondition = false; this.setState({ main: 'btn-group' }); } downloadSwitch(p = undefined) { if (p != undefined) { this.setState({ downloadEnable: p }); } else { this.setState({ downloadEnable: this.state.downloadEnable ? true : false }); } this.downloadManualParseSwitch(this.state.downloadManualParseEnable); this.downloadAllSwitch(this.state.downloadAllEnable); this.downloadSelectedSwitch(this.state.downloadSelectedEnable); } downloadManualParseSwitch(p = undefined) { if (p != undefined) { this.setState({ downloadManualParseEnable: this.state.downloadEnable && p }); } else { this.setState({ downloadManualParseEnable: this.state.downloadManualParseEnable ? this.state.downloadEnable && true : false }); } } downloadAllSwitch(p = undefined) { if (p != undefined) { this.setState({ downloadAllEnable: this.state.downloadEnable && p }); } else { this.setState({ downloadAllEnable: this.state.downloadAllEnable ? this.state.downloadEnable && true : false }); } } downloadSelectedSwitch(p = undefined) { if (p != undefined) { this.setState({ downloadSelectedEnable: this.state.downloadEnable && p }); } else { this.setState({ downloadSelectedEnable: this.state.downloadSelectedEnable ? this.state.downloadEnable && true : false }); } } render() { return (reactRender({ nodeType: 'div', className: this.state.main, childs: [{ nodeType: 'button', className: 'btn btn-primary btn-sm dropdown-toggle', attribute: { type: 'button', id: 'PluginUIStartUp', title: '批量下载' }, childs: [{ nodeType: 'span', className: 'glyphicon glyphicon-download-alt' }, '批量下载'], onClick: () => { if (this.showCondition) { this.hide(); } else { this.show(); } } }, { nodeType: 'ul', className: 'dropdown-menu', attribute: { role: 'menu' }, childs: [ this.state.downloadSelectedEnable ? { nodeType: 'li', attribute: { style: this.state.style.base, dangerouslySetInnerHTML: { __html: '<a><span class="glyphicon glyphicon-check"></span>下载所选</a>' } }, onClick: () => { this.hide(); DownloadSelected(); } } : null, this.state.downloadAllEnable ? { nodeType: 'li', attribute: { style: this.state.style.base, dangerouslySetInnerHTML: { __html: '<a><span class="glyphicon glyphicon-save"></span>下载所有</a>' } }, onClick: () => { this.hide(); DownloadAll(); } } : null, this.state.downloadManualParseEnable ? { nodeType: 'li', attribute: { style: this.state.style.base, dangerouslySetInnerHTML: { __html: '<a><span class="glyphicon glyphicon-edit"></span>手动下载</a>' } }, onClick: () => { this.hide(); ManualParseDownloadAddress(); } } : null, { nodeType: 'li', attribute: { style: this.state.style.base, dangerouslySetInnerHTML: { __html: '<a><span class="glyphicon glyphicon-cog"></span>设置</a>' } }, onClick: () => { this.hide(); PluginControlPanel.show(); } } ] }] })); } } class pluginControlPanel extends React.Component { Initialize; Cookies; synclistener; Aria2WebSocket; constructor(props) { super(props); this.Cookies = GM_getValue('Cookies', document.cookie); this.Initialize = GM_getValue('Initialize', false); this.synclistener = []; this.state = { Async: GM_getValue('Async', false), AutoRefresh: GM_getValue('AutoRefresh', false), Version: GM_getValue('Version', null), DownloadType: Number(GM_getValue('DownloadType', DownloadType.others)), DownloadDir: GM_getValue('DownloadDir', 'iwara/%#AUTHOR#%'), DownloadProxy: GM_getValue('DownloadProxy', ''), Aria2Type: Number(GM_getValue('Aria2Type', APIType.ws)), Aria2Path: GM_getValue('Aria2Path', '127.0.0.1:6800'), Aria2Token: GM_getValue('Aria2Token', ''), Aria2ID: GM_getValue('Aria2ID', UUID()), FileName: GM_getValue('FileName', '%#TITLE#%[%#ID#%].mp4'), style: { radioLabel: { margin: '0px 20px 0px 0px' }, Line: { margin: '10px 0px', display: 'flex', alignItems: 'center' }, inputLabel: { marginRight: '8px', marginBottom: '0px', verticalAlign: 'middle', lineHeight: '1.2' }, input: { flex: 1, minWidth: 0, font: 'menu', verticalAlign: 'middle' }, main: { display: 'none' } } }; if (!this.Initialize) { for (const key in this.state) { if (key != 'style') { GM_setValue(key, this.state[key]); } } this.Initialize = true; GM_setValue('Initialize', this.Initialize); } if (document.querySelector('.user-btn') != null || this.Cookies == null) { this.getCookies(); } } getCookies() { try { GM_cookie('list', { domain: 'iwara.tv', httpOnly: true }, (list, error) => { let newCookies = document.cookie; if (error) { PluginTips.warning('注意', '获取账号信息失败!<br />如需下载私有(上锁)视频,请尝试使用Tampermonkey Beta载入本脚本。<br />错误:' + error.toString()); } else { for (let index = 0; index < list.length; index++) { const Cookie = list[index]; if (Cookie.httpOnly == true) newCookies += '; ' + Cookie.name + '=' + Cookie.value; } if (newCookies != this.Cookies) { this.Cookies = newCookies; GM_setValue('Cookies', this.Cookies); } } }); } catch (error) { PluginTips.warning('注意', '获取账号信息失败!<br />请检查脚本加载器是否支持获取Cookies!<br /> <a href="https://www.tampermonkey.net/documentation.php"> 参考文档 <a/> <br />错误:' + error.toString(), true); } } show() { this.setState((state) => { return { style: Object.assign(state.style, { main: { display: 'block' } }) }; }); } hide() { switch (this.state.DownloadType) { case DownloadType.aria2: this.setState((state) => { return { style: Object.assign(state.style, { main: { display: 'none' } }) }; }); this.ConnectionAria2(this.state.Aria2Type); break; default: this.setState((state) => { return { style: Object.assign(state.style, { main: { display: 'none' } }) }; }); break; } } componentDidMount() { let values = GM_listValues(); for (let index = 0; index < values.length; index++) { this.synclistener.push(GM_addValueChangeListener(values[index], (name, old_value, new_value, remote) => { if (remote) { if (this[name] != undefined && this[name] != new_value) { this[name] = new_value; } else { if (new_value != this.state[name]) { this.configChange({ name: name, value: new_value }); } } } })); } } componentWillUnmount() { this.Aria2WebSocket.close(); for (let index = 0; index < this.synclistener.length; index++) { GM_removeValueChangeListener(this.synclistener[index]); } } async ConnectionAria2(type) { let url = APIType[type] + '://' + this.state.Aria2Path + '/jsonrpc'; PluginTips.info('Aria2', '正在连接...'); try { switch (APIType[type]) { case APIType[APIType.http]: case APIType[APIType.https]: if (this.Aria2WebSocket != null) { this.Aria2WebSocket.close(); this.Aria2WebSocket = null; } let response = await post(url, JSON.stringify({ 'jsonrpc': '2.0', 'method': 'aria2.getGlobalStat', 'id': PluginControlPanel.state.Aria2ID, 'params': [ 'token:' + PluginControlPanel.state.Aria2Token ] })); if (response['result'] != null) { PluginTips.success('Aria2', '连接成功!'); PluginUI.downloadSwitch(true); } else { PluginTips.success('Aria2', '连接失败, 请检查是否授权脚本访问Aria2RPC地址, 或者是否已经启动Aria2。<br />以及Aria2配置是否正确!', true); PluginUI.downloadSwitch(false); } break; case APIType[APIType.ws]: case APIType[APIType.wss]: this.Aria2WebSocket = new WebSocket(url); this.Aria2WebSocket.onopen = wsopen; this.Aria2WebSocket.onmessage = wsmessage; this.Aria2WebSocket.onclose = wsclose; function wsopen() { PluginTips.success('Aria2', '连接成功!'); PluginUI.downloadSwitch(true); } function wsmessage() { //todo 接收信息 } function wsclose() { PluginControlPanel.Aria2WebSocket = null; PluginTips.warning('Aria2', 'Aria2 连接断开! <br />请检查Aria2 有关配置是否正确!', true); } break; default: throw new Error('未知的下载模式!'); } } catch (err) { PluginTips.warning('Aria2', '连接 Aria2 时出现错误! <br />请检查Aria2 有关配置是否正确! <br />' + err, true); } } configChange(e) { this.setState({ [e.name]: e.value }); if ((e.name == 'DownloadType' && e.value == DownloadType.aria2) || e.name == 'Aria2Type') { this.ConnectionAria2(e.value); } if (e.value != GM_getValue(e.name)) { GM_setValue(e.name, e.value); } } render() { return (reactRender({ nodeType: 'div', className: 'controlPanel', attribute: { style: this.state.style.main }, childs: [{ nodeType: 'div', className: 'controlPanel-content', childs: [{ nodeType: 'span', className: 'controlPanelClose', childs: '❌', onClick: () => { this.hide(); } }, { nodeType: 'div', childs: [{ nodeType: 'div', style: this.state.style.Line, childs: [{ nodeType: 'label', style: this.state.style.radioLabel, childs: '下载方式:' }, { nodeType: 'input', name: 'DownloadType', type: 'radio', value: DownloadType.aria2, onChange: ({ target }) => this.configChange(target) }, { nodeType: 'label', style: this.state.style.radioLabel, childs: 'Aria2' }, { nodeType: 'input', name: 'DownloadType', type: 'radio', value: DownloadType.default, onChange: ({ target }) => this.configChange(target) }, { nodeType: 'label', style: this.state.style.radioLabel, childs: '浏览器默认' }, { nodeType: 'input', name: 'DownloadType', type: 'radio', value: DownloadType.others, onChange: ({ target }) => this.configChange(target) }, { nodeType: 'label', style: this.state.style.radioLabel, childs: '其他下载器' }].map((item) => { if (item.value == this.state.DownloadType) { item.checked = true; } return item; }) }, { nodeType: 'div', style: this.state.style.Line, childs: [{ nodeType: 'label', style: this.state.style.inputLabel, childs: '解析模式[推荐同步模式]:', title: '异步解析可能会因为解析速度过快导致服务器拒绝回应' }, { nodeType: 'input', name: 'Async', type: 'button', style: this.state.style.input, attribute: { switch: this.state.Async ? 'on' : 'off' }, value: this.state.Async ? '异步' : '同步', className: 'switchButton', onClick: () => this.configChange({ name: 'Async', value: !this.state.Async }) } ] }, { nodeType: 'div', style: this.state.style.Line, childs: [{ nodeType: 'label', style: this.state.style.inputLabel, childs: '列表页未加载自动刷新:', title: '可能会因为自动刷新导致服务器拒绝回应' }, { nodeType: 'input', name: 'AutoRefresh', type: 'button', style: this.state.style.input, attribute: { switch: this.state.AutoRefresh ? 'on' : 'off' }, value: this.state.AutoRefresh ? '开启' : '关闭', className: 'switchButton', onClick: () => this.configChange({ name: 'AutoRefresh', value: !this.state.AutoRefresh }) } ] }, this.state.DownloadType != DownloadType.others ? { nodeType: 'div', style: this.state.style.Line, childs: [{ nodeType: 'label', style: this.state.style.inputLabel, childs: '重命名:' }, { nodeType: 'input', name: 'FileName', type: 'text', value: this.state.FileName, style: this.state.style.input, onChange: ({ target }) => this.configChange(target) } ] } : null, this.state.DownloadType == DownloadType.aria2 ? { nodeType: 'div', style: this.state.style.Line, childs: [{ nodeType: 'label', style: this.state.style.inputLabel, childs: '下载到:' }, { nodeType: 'input', name: 'DownloadDir', value: this.state.DownloadDir, style: this.state.style.input, onChange: ({ target }) => this.configChange(target) } ] } : null, this.state.DownloadType == DownloadType.aria2 ? { nodeType: 'div', style: this.state.style.Line, childs: [{ nodeType: 'label', style: this.state.style.inputLabel, childs: '代理服务器(可选):' }, { nodeType: 'input', name: 'DownloadProxy', value: this.state.DownloadProxy, style: this.state.style.input, onChange: ({ target }) => this.configChange(target) } ] } : null, this.state.DownloadType == DownloadType.aria2 ? { nodeType: 'div', style: this.state.style.Line, childs: [ { nodeType: 'label', style: this.state.style.radioLabel, childs: 'Aria2 RPC 连接方式:' }, { nodeType: 'input', name: 'Aria2Type', type: 'radio', value: APIType.http, onChange: ({ target }) => this.configChange(target) }, { nodeType: 'label', style: this.state.style.radioLabel, childs: 'http' }, { nodeType: 'input', name: 'Aria2Type', type: 'radio', value: APIType.https, onChange: ({ target }) => this.configChange(target) }, { nodeType: 'label', style: this.state.style.radioLabel, childs: 'https' }, { nodeType: 'input', name: 'Aria2Type', type: 'radio', value: APIType.ws, onChange: ({ target }) => this.configChange(target) }, { nodeType: 'label', style: this.state.style.radioLabel, childs: 'ws' }, { nodeType: 'input', name: 'Aria2Type', type: 'radio', value: APIType.wss, onChange: ({ target }) => this.configChange(target) }, { nodeType: 'label', style: this.state.style.radioLabel, childs: 'wss' } ].map((item) => { if (item.value == this.state.Aria2Type) { item.checked = true; } return item; }) } : null, this.state.DownloadType == DownloadType.aria2 ? { nodeType: 'div', style: this.state.style.Line, childs: [{ nodeType: 'label', style: this.state.style.inputLabel, childs: 'Aria2 地址:' }, { nodeType: 'input', name: 'Aria2Path', value: this.state.Aria2Path, style: this.state.style.input, onChange: ({ target }) => this.configChange(target) } ] } : null, this.state.DownloadType == DownloadType.aria2 ? { nodeType: 'div', style: this.state.style.Line, childs: [{ nodeType: 'label', style: this.state.style.inputLabel, childs: 'Aria2 Token(密钥):' }, { nodeType: 'input', name: 'Aria2Token', type: 'Password', value: this.state.Aria2Token, style: this.state.style.input, onChange: ({ target }) => this.configChange(target) } ] } : null, { nodeType: 'div', style: this.state.style.Line, childs: [{ nodeType: 'label', style: this.state.style.inputLabel, childs: [ '!!!必读!!!', { nodeType: 'br' }, '全局可用变量: %#Y#% (年) | %#M#% (月) | %#D#% (日) | %#h#% (时) | %#m#% (分) | %#s#% (秒)', { nodeType: 'br' }, '重命名可用变量: %#TITLE#% (标题) | %#ID#% (ID) | %#AUTHOR#% (作者) | %#SOURCE_NAME#% (原文件名)', { nodeType: 'br' }, '下载目录可用变量: %#AUTHOR#% (作者)', { nodeType: 'br' }, '例: %#Y#%-%#M#%-%#D#%_%#TITLE#%[%#ID#%].MP4', { nodeType: 'br' }, '结果: ' + replaceVar('%#Y#%-%#M#%-%#D#%_%#TITLE#%[%#ID#%].MP4').replace('%#TITLE#%', '演示标题').replace('%#ID#%', '演示ID'), { nodeType: 'br' }, '双击视频选中,再次双击取消选中。选中仅在本页面有效!', { nodeType: 'br' }, '在作者用户页面可以点击下载全部,将会搜索该用户的所有视频进行下载。', { nodeType: 'br' }, '插件下载视频前会检查视频简介,如果在简介中发现疑似第三方下载链接,将会弹窗提示,您可以手动打开视频页面选择。', { nodeType: 'br' }, '手动下载需要您提供视频ID!' ] }] }] } ] }] })); } } sourceRender({ nodeType: 'style', innerHTML: ` .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; -ms-overflow-style: none; overflow-x: hidden; overflow-y: auto; } .controlPanel::-webkit-scrollbar { display: none; } .controlPanel-content { background-color: #fefefe; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); 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; } .switchButton { background-color: #bf360c; } .switchButton[switch=off] { background-color: #43a047; } .selectButton { border-style: solid; border-color: #ff8c26; } .selectButton[checked=true] { border-color: #ff8c26; } .selectButton[checked=true]:before { z-index: 2147483640; position: absolute; display: block; width: 100%; height: 100%; left: 50%; top: 50%; transform: translate(-50%, -50%); background-color: rgba(150, 150, 150, 0.6); font-weight: 900; font-size: 36px; text-align: right; color: rgb(20, 20, 20); content: '✔'; } .tips { letter-spacing:3px; 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; } .tipsProgress { width: 100%; background-color: #ddd; } .tipsProgress .value { text-align: right; height: 24px; color: white; background-color: #2196F3; width: 0%; -webkit-transition: all 0.2s ease; -moz-transition: all 0.2s ease; -o-transition: all 0.2s ease; transition: all 0.2s ease; } .Progress :after{ content: attr(value)"%"; } .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; width: 100%; } .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%); } } h6.stitle{ white-space: nowrap; text-overflow:ellipsis; overflow: hidden; margin: 0px; } `, parent: document.head }); sourceRender({ nodeType: 'div', attribute: { id: 'PluginControlPanel' }, parent: document.body }); sourceRender({ nodeType: 'div', attribute: { id: 'PluginUI', style: 'display: inline-block;' }, parent: document.querySelector('#user-links') }); let PluginUI = ReactDOM.render(React.createElement(pluginUI), document.getElementById('PluginUI')); let PluginControlPanel = ReactDOM.render(React.createElement(pluginControlPanel), document.getElementById('PluginControlPanel')); let PluginTips = new pluginTips(); let DownloadLinkCharacteristics = [ '/s/', 'mega.nz/', 'drive.google.com', 'aliyundrive', 'uploadgig', 'katfile', 'storex', 'subyshare', 'rapidgator', 'filebe', 'filespace', 'mexa.sh', 'mexashare', 'mx-sh.net', 'uploaded', 'icerbox', 'alfafile', 'drv.ms', 'onedrive' ]; function ParseVideoID(data) { if (data.getAttribute('linkdata') != null) { return data.getAttribute('linkdata').split('?')[0].split('/')[4].toLowerCase(); } else { return data.querySelector('h3.title').querySelector('a').href.toLowerCase().split('?')[0].split('/')[4].toLowerCase(); } } async function ManualParseDownloadAddress() { let ID = prompt('请输入需要下载的视频ID', ''); if (ID.split('_')[1] != undefined) { ID = ID.split('_')[1]; } await ParseDownloadAddress(ID); } async function DownloadSelected() { PluginTips.info('Iwara批量下载工具', '开始解析...'); let videoList = document.querySelectorAll('.selectButton[checked="true"]'); if (PluginControlPanel.state.Async) { videoList.forEach(async (element, index) => { await ParseDownloadAddress(ParseVideoID(element)); if (index == videoList.length - 1) PluginTips.success('Iwara批量下载工具', '已全部解析完成!', true); }); } else { for (let index = 0; index < videoList.length; index++) { await ParseDownloadAddress(ParseVideoID(videoList[index])); } PluginTips.success('Iwara批量下载工具', '已全部解析完成!', true); } } async function DownloadAll() { PluginTips.info('Iwara批量下载工具', '正在解析...'); if (document.querySelector('#block-views-videos-block-2') != null) { if (document.querySelector('#block-views-videos-block-2').querySelector('.more-link') != null) { await GetAllData(document.querySelector('.more-link').querySelector('a').href, [], window.location.href); } else { let videoList = document.querySelector('#block-views-videos-block-2').querySelectorAll('.node-video'); if (PluginControlPanel.state.Async) { videoList.forEach(async (element, index) => { await ParseDownloadAddress(ParseVideoID(element)); if (index == videoList.length - 1) PluginTips.success('Iwara批量下载工具', '已全部解析完成!', true); }); } else { for (let index = 0; index < videoList.length; index++) { await ParseDownloadAddress(ParseVideoID(videoList[index])); } PluginTips.success('Iwara批量下载工具', '已全部解析完成!', true); } } } else { await GetAllData(window.location.href, [], window.location.href); } } async function GetAllData(videoListUrl, data, referrer) { let videoListPage = parseDom(await get(videoListUrl, data, referrer)); let videoList = videoListPage.querySelector('.view-videos').querySelectorAll('.node-video'); if (PluginControlPanel.state.Async) { videoList.forEach(async (element, index) => { await ParseDownloadAddress(ParseVideoID(element)); if (index == videoList.length - 1) { if (videoListPage.querySelectorAll('.pager-next').length != 0) { await GetAllData(videoListPage.querySelector('.pager-next').querySelector('a').href, data, referrer); } else { PluginTips.success('Iwara批量下载工具', '已全部解析完成!', true); } } }); } else { for (let i = 0; i < videoList.length; i++) { await ParseDownloadAddress(ParseVideoID(videoList[i])); } if (videoListPage.querySelectorAll('.pager-next').length != 0) { await GetAllData(videoListPage.querySelector('.pager-next').querySelector('a').href, data, referrer); } else { PluginTips.success('Iwara批量下载工具', '已全部解析完成!', true); } } } function CheckIsHaveDownloadLink(comment) { if (comment == null) return false; for (let index = 0; index < DownloadLinkCharacteristics.length; index++) { if (comment.indexOf(DownloadLinkCharacteristics[index]) != -1) return true; } return false; } async function ParseDownloadAddress(Data) { let videoInfo = new VideoInfo(Data); await videoInfo.init(); if (videoInfo.Exist) { if (videoInfo.Private) { let TipsText = '检测到无权限访问的私有(上锁)视频! <br />'; if (document.querySelector('.btn.btn-info.btn-sm.user') == null) { TipsText += '请<a href="https://' + window.location.hostname + '/user/login" target="_blank" >登录</a>或<a href="https://' + window.location.hostname + '/user/register" target="_blank">注册</a>后进入视频页面与作者成为好友获得访问权限。<br />'; } else { TipsText += '请进入视频页面与作者成为好友获得访问权限。<br />'; } PluginTips.warning('警告', TipsText + '<a href="' + videoInfo.Url + '" target="_blank" > → 点击此处,进入视频页面 ← </a>', true); } else { if (CheckIsHaveDownloadLink(videoInfo.getComment())) { PluginTips.warning('警告', '<a href="' + videoInfo.Url + '" title="' + videoInfo.getName() + '" target="_blank" >' + videoInfo.getName() + '</a> <br />发现疑似第三方高画质下载链接,请手动处理!', true); } else { if (videoInfo.getDownloadQuality() != 'Source') { PluginTips.warning('警告', '<a href="' + videoInfo.Url + '" title="' + videoInfo.getName() + '" target="_blank" >' + videoInfo.getName() + '</a> <br />没有解析到原画下载地址,请手动处理!', true); } else { SendDownloadRequest(videoInfo, PluginControlPanel.Cookies); } } } } else { PluginTips.warning('警告', '<a href="' + videoInfo.Url + '" title="' + videoInfo.getName() + '" target="_blank" >' + videoInfo.getName() + '</a> <br />未能获取到相关信息,请检查视频是否存在。', true); } } function defaultDownload(Info) { (function (ID, Name, FileName, DownloadUrl) { let Task = { url: DownloadUrl, name: FileName, saveAs: false, onload: function () { PluginTips.downloadComplete(ID); PluginTips.success('Iwara批量下载工具', Name + ' 下载完成!'); }, onerror: function (error) { PluginTips.downloadComplete(ID); PluginTips.warning('警告', Name + ' 下载失败! <br />错误报告: ' + JSON.stringify(error)); }, onprogress: function (progress) { if (progress.lengthComputable) { PluginTips.downloading(ID, progress.position / progress.totalSize * 100); } }, ontimeout: function () { PluginTips.downloadComplete(ID); PluginTips.warning('警告', Name + ' 下载超时! '); } }; if (PluginTips.DownloadingQueue.length() < 4) { PluginTips.DownloadingQueue.push({ id: ID, name: Name, task: Task }); GM_download(Task); if (GM_info.downloadMode == 'native') { PluginTips.progress(Name + ' 下载中...', { id: ID }); } else { PluginTips.info('Iwara批量下载工具', Name + ' 已开始下载!'); } } else { PluginTips.WaitingQueue.push({ id: ID, name: Name, task: Task }); } }(Info.ID, Info.getName(), Info.getFileName(), Info.getDownloadUrl())); } function aria2Download(Info, Cookies) { (function (ID, Name, FileName, Author, Cookie, DownloadUrl) { let json = JSON.stringify({ 'jsonrpc': '2.0', 'method': 'aria2.addUri', 'id': PluginControlPanel.state.Aria2ID, 'params': [ 'token:' + PluginControlPanel.state.Aria2Token, [DownloadUrl], { 'referer': 'https://ecchi.iwara.tv/', 'header': [ 'Cookie:' + Cookie ], 'out': FileName, 'dir': replaceVar(PluginControlPanel.state.DownloadDir).replace('%#AUTHOR#%', Author), 'all-proxy': PluginControlPanel.state.DownloadProxy } ] }); switch (APIType[PluginControlPanel.state.Aria2Type]) { case APIType[APIType.http]: case APIType[APIType.https]: post(APIType[PluginControlPanel.state.Aria2Type] + '://' + PluginControlPanel.state.Aria2Path + '/jsonrpc', json); break; case APIType[APIType.ws]: case APIType[APIType.wss]: PluginControlPanel.Aria2WebSocket.send(json); break; } PluginTips.info('Iwara批量下载工具', '已将 ' + Name + ' 的下载地址推送到Aria2!'); }(Info.ID, Info.getName(), Info.getFileName(), Info.getAuthor(), Cookies, Info.getDownloadUrl())); } function SendDownloadRequest(Info, Cookie) { switch (DownloadType[PluginControlPanel.state.DownloadType]) { case DownloadType[DownloadType.aria2]: aria2Download(Info, Cookie); break; case DownloadType[DownloadType.default]: defaultDownload(Info); break; case DownloadType[DownloadType.others]: default: PluginTips.info('Iwara批量下载工具', '已将下载请求提交给浏览器!'); GM_openInTab(Info.getDownloadUrl(), { active: true, insert: true, setParent: true }); break; } } function replaceVar(data) { let gVar = [ { 'Y': new Date().getFullYear() }, { 'M': new Date().getMonth() + 1 }, { 'D': new Date().getDate() }, { 'h': new Date().getHours() }, { 'm': new Date().getMinutes() }, { 's': new Date().getSeconds() } ]; for (let i = 0; i < gVar.length; i++) { for (const d in gVar[i]) { data = data.replace(new RegExp('%#' + d + '#%', 'g'), gVar[i][d]); } } return data; } if (!PluginControlPanel.Initialize) { PluginControlPanel.show(); } let videoList = document.querySelectorAll('.node-video'); for (let index = 0; index < videoList.length; index++) { const video = videoList[index]; if (!video.classList.contains('node-full')) { let videoLink = video.querySelector('.even').querySelector('a'); if (videoLink != null) { video.setAttribute('linkdata', videoLink.href); videoLink.removeAttribute('href'); if (video.querySelector('img[src*="/"]') == null) { videoLink.append(sourceRender({ nodeType: 'img', attribute: { src: "https://oreno3d.com/storage/img/noimage.png" } })); } if (video.querySelector('.title') == null) { sourceRender({ nodeType: 'h6', className: 'stitle', childs: { nodeType: 'a', attribute: { href: video.getAttribute('linkdata') }, innerText: video.getAttribute('title') == '' ? video.getAttribute('data-original-title') : video.getAttribute('title') }, parent: video }); } } if (video.querySelector('.field-name-field-video-url') != null) { PluginTips.warning('Iwara批量下载工具', '因视频非本站源跳过该视频: ' + video.getAttribute('title') == '' ? video.getAttribute('data-original-title') : video.getAttribute('title')); } else { video.ondblclick = () => { video.setAttribute('checked', video.getAttribute('checked') == 'false' ? 'true' : 'false'); }; video.setAttribute('checked', 'false'); video.classList.add('selectButton'); } } } if (document.querySelectorAll('.selectButton').length > 0) { try { switch (PluginControlPanel.state.DownloadType) { case DownloadType.aria2: PluginControlPanel.ConnectionAria2(PluginControlPanel.state.Aria2Type); break; case DownloadType.default: PluginTips.warning('Iwara批量下载工具', '该下载模式为实验性模式,无法保证下载稳定性!'); break; case DownloadType.others: break; default: throw new Error('未知的下载模式!'); } PluginUI.downloadSwitch(true); PluginTips.success('Iwara批量下载工具', '加载完成!'); } catch (error) { PluginTips.warning('Iwara批量下载工具', '加载失败! <br /> 错误信息: ' + error); } } else { if (window.location.href.indexOf('iwara.tv/videos') > -1 && PluginControlPanel.state.AutoRefresh) { PluginTips.warning('Iwara批量下载工具', '未找到可供下载的视频,10秒后尝试重新加载页面...(本功能可在设置中关闭或开启)', true); setTimeout(() => { window.location.reload(); }, 10000); } } })(); //# sourceMappingURL=IwaraDownloadTool.user.js.map