// ==UserScript==
// @name Iwara批量下载助手
// @namespace https://github.com/dawn-lc/user.js
// @version 1.2.10
// @description 批量下载Iwara视频
// @author dawn-lc
// @connect iwara.tv
// @match https://ecchi.iwara.tv/users/*
// @match https://ecchi.iwara.tv/videos
// @match https://ecchi.iwara.tv/videos*page=*
// @match https://ecchi.iwara.tv/subscriptions*
// @exclude https://ecchi.iwara.tv/videos/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_download
// @grant GM_xmlhttpRequest
// @grant GM_openInTab
// ==/UserScript==
(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 = {
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()),
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 {
-moz-user-select: none;
/*火狐*/
-webkit-user-select: none;
/*webkit浏览器*/
-ms-user-select: none;
/*IE10*/
-khtml-user-select: none;
/*早期浏览器*/
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: 9999;
/* 设置在顶层 */
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 {
z-index: 10000;
/* 设置在顶层 */
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 {
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', null);
break;
};
};
ControlPanel.querySelector("#DownloadDir").value = config.DownloadDir;
ControlPanel.querySelector("#DownloadProxy").value = config.DownloadProxy;
ControlPanel.querySelector("#WebSocketAddress").value = config.WebSocketAddress;
ControlPanel.querySelector("#WebSocketToken").value = config.WebSocketToken;
ControlPanel.style.display = 'block';
document.getElementById('PluginUIStartUp').click();
}
}
]
}
],
parent: document.getElementById('user-links')
},
PluginControlPanel: {
nodeType: 'div',
id: 'PluginControlPanel',
className: 'controlPanel',
childs: [{
nodeType: 'div',
className: 'controlPanel-content',
childs: [{
nodeType: 'span',
className: 'controlPanelClose',
innerHTML: '×',
onclick: function () {
config.setDownloadType(config.DownloadType);
config.setDownloadDir(config.DownloadDir);
config.setDownloadProxy(config.DownloadProxy);
config.setWebSocketAddress(config.WebSocketAddress);
config.setWebSocketToken(config.WebSocketToken);
config.setWebSocketID(config.WebSocketID);
config.setInitialize(true);
this.parentNode.parentNode.style.display = 'none';
main.run();
}
},
{
nodeType: 'div',
id: 'controlPanelItem',
childs: [{
nodeType: 'div',
style: 'margin: 10px 0;',
childs: [{
nodeType: 'label',
style: 'margin: 0px 10px 0px 0px;',
innerHTML: '下载方式:'
},
{
nodeType: 'input',
name: 'DownloadType',
type: 'radio',
value: config.Type.Download.aria2,
onchange: ({
target
}) => config.setDownloadType(target.value)
},
{
nodeType: 'label',
style: 'margin: 0px 20px 0px 0px;',
innerHTML: 'Aria2'
},
{
nodeType: 'input',
name: 'DownloadType',
type: 'radio',
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 />插件下载视频前会检查视频简介,如果在简介中发现MEGA链接或百度网盘链接,将会打开视频页面,供您手动下载.<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) {
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() {};
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('下载', '开始解析...');
let select = document.createElement('div');
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, null, window.location.href));
let videosList = videoListPage.getElementById('block-views-videos-block-2').getElementsByClassName('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, null, window.location.href);
};
},
async GetAllData(videoListUrl, data, referrer) {
let videoListPage = library.Dom.parseDom(await library.Net.get(videoListUrl, data, referrer));
let videosList = videoListPage.getElementById('block-views-videos-block-2').getElementsByClassName('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;
this.Url = this.Element.querySelector('.selectButton').getAttribute('linkData');
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.Source = await library.Net.get('https://ecchi.iwara.tv/api/video/' + this.getID(), null, this.getUrl());
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.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) {
for (let index = 0; index < main.DownloadLinkCharacteristics.length; index++) {
const Characteristics = main.DownloadLinkCharacteristics[index];
if (comment.indexOf(Characteristics) != -1) {
return true;
};
};
return false;
},
async ParseDownloadAddress(Data) {
let videoInfo = await main.VideoInfo.createNew(Data);
if (main.CheckIsHaveDownloadLink(videoInfo.getComment())) {
main.Warning('警告', '<a href="' + videoInfo.getUrl() + '" title="' + videoInfo.getName() + '" target="_blank" >' + videoInfo.getName() + '</a> 发现疑似第三方高画质下载链接,请手动处理!', true);
} else {
for (let index = 0; index < videoInfo.Source.length; index++) {
if (videoInfo.Source[index].resolution == 'Source') {
let Url = decodeURIComponent('https:' + videoInfo.Source[index].uri);
let FlieName = library.Net.getQueryVariable(Url, 'file').split('/')[3];
main.SendDownloadRequest(videoInfo.getName(), Url, FlieName, videoInfo.getAuthor(), document.cookie);
break;
}
if (index + 1 == videoInfo.Source.length) {
main.Warning('警告', '<a href="' + videoInfo.getUrl() + '" title="' + videoInfo.getName() + '" target="_blank" >' + videoInfo.getName() + '</a> 没有解析到原画下载地址,请手动处理!', true);
break;
};
};
};
},
gmDownload(Name, Url, FlieName, Author, Cookie) {
main.Info('下载', '开始下载:' + FlieName);
(function (Name, Url, FlieName) {
GM_download({
name: '[' + Name + ']' + FlieName,
url: Url,
saveAs: false,
onload: function () {
main.Success('下载', Name + ' 下载完成!');
},
onerror: function (error) {
main.Warning('下载错误', Name + ' 下载失败! ' + error);
}
});
})(Name, Url, FlieName);
},
aria2Download(Name, Url, FlieName, Author, Cookie) {
let Action = {
'jsonrpc': '2.0',
'method': 'aria2.addUri',
'id': config.WebSocketID,
'params': [
'token:' + config.WebSocketToken,
[Url],
{
'referer': 'https://ecchi.iwara.tv/',
'header': [
'Cookie:' + Cookie
],
'out': '![' + Name + ']' + FlieName,
'dir': config.DownloadDir + Author.replace('.', '')
}
]
};
if (config.DownloadProxy != '') {
Action.params[Action.params.length - 1]['all-proxy'] = config.DownloadProxy;
};
main.Aria2WebSocket.send(JSON.stringify(Action));
main.Info('提示', '已将 ' + Name + ' 的下载地址推送到Aria2!');
},
async SendDownloadRequest(Name, Url, FlieName, Author, Cookie) {
switch (config.DownloadType) {
case config.Type.Download.aria2:
main.aria2Download(Name, Url, FlieName, Author, Cookie);
break;
case config.Type.Download.default:
main.gmDownload(Name, Url, FlieName, Author, Cookie);
break;
case config.Type.Download.others:
main.Info('提示', '已将下载请求提交给浏览器!');
GM_openInTab(Url, {
active: true,
insert: true,
setParent: true
});
break;
default:
main.Warning('配置错误', '未知的下载模式!');
break;
}
}
};
main.start();
})();