Iwara Download Tool

Download videos from iwara.tv

Versión del día 10/12/2021. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==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: '&times;',
                        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();
    };
})();