Iwara Download Tool

Download videos from iwara.tv

09.12.2021 itibariyledir. En son verisyonu görün.

// ==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                 https://*.iwara.tv/users/*
// @match                 https://*.iwara.tv/videos
// @match                 https://*.iwara.tv/videos*page=*
// @match                 https://*.iwara.tv/subscriptions*
// @exclude               https://*.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();
    };
})();