/* eslint-disable no-multi-spaces */ // ==UserScript== // @name 动漫岛 视频功能增强 // @namespace Mandao-Video // @version 0.6 // @description 允许观看和下载动漫岛视频,去除部分uBlock Orgin未自动拦截的广告 // @author PY-DNG // @license GPL-license // @match http*://www.mandao.tv/* // @icon https://www.mandao.tv/favicon.ico // @grant none // @run-at document-start // @downloadURL https://update.greasyfork.icu/scripts/448664/%E5%8A%A8%E6%BC%AB%E5%B2%9B%20%E8%A7%86%E9%A2%91%E5%8A%9F%E8%83%BD%E5%A2%9E%E5%BC%BA.user.js // @updateURL https://update.greasyfork.icu/scripts/448664/%E5%8A%A8%E6%BC%AB%E5%B2%9B%20%E8%A7%86%E9%A2%91%E5%8A%9F%E8%83%BD%E5%A2%9E%E5%BC%BA.meta.js // ==/UserScript== (function __MAIN__() { 'use strict'; // Polyfills const script_name = '新的用户脚本'; const script_version = '0.1'; const NMonkey_Info = { GM_info: { script: { name: script_name, author: 'PY-DNG', version: script_version } }, mainFunc: __MAIN__ }; const NMonkey_Ready = NMonkey(NMonkey_Info); if (!NMonkey_Ready) {return false;} polyfill_replaceAll(); // Arguments: level=LogLevel.Info, logContent, asObject=false // Needs one call "DoLog();" to get it initialized before using it! function DoLog() { // Get window const win = (typeof(unsafeWindow) === 'object' && unsafeWindow !== null) ? unsafeWindow : window ; // Global log levels set win.LogLevel = { None: 0, Error: 1, Success: 2, Warning: 3, Info: 4, } win.LogLevelMap = {}; win.LogLevelMap[LogLevel.None] = {prefix: '' , color: 'color:#ffffff'} win.LogLevelMap[LogLevel.Error] = {prefix: '[Error]' , color: 'color:#ff0000'} win.LogLevelMap[LogLevel.Success] = {prefix: '[Success]' , color: 'color:#00aa00'} win.LogLevelMap[LogLevel.Warning] = {prefix: '[Warning]' , color: 'color:#ffa500'} win.LogLevelMap[LogLevel.Info] = {prefix: '[Info]' , color: 'color:#888888'} win.LogLevelMap[LogLevel.Elements] = {prefix: '[Elements]', color: 'color:#000000'} // Current log level DoLog.logLevel = win.isPY_DNG ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error // Log counter DoLog.logCount === undefined && (DoLog.logCount = 0); // Get args let level, logContent, asObject; switch (arguments.length) { case 1: level = LogLevel.Info; logContent = arguments[0]; asObject = false; break; case 2: level = arguments[0]; logContent = arguments[1]; asObject = false; break; case 3: level = arguments[0]; logContent = arguments[1]; asObject = arguments[2]; break; default: level = LogLevel.Info; logContent = 'DoLog initialized.'; asObject = false; break; } // Log when log level permits if (level <= DoLog.logLevel) { let msg = '%c' + LogLevelMap[level].prefix; let subst = LogLevelMap[level].color; if (asObject) { msg += ' %o'; } else { switch(typeof(logContent)) { case 'string': msg += ' %s'; break; case 'number': msg += ' %d'; break; case 'object': msg += ' %o'; break; } } if (++DoLog.logCount > 512) { console.clear(); DoLog.logCount = 0; } console.log(msg, subst, logContent); } } DoLog(); // Constances const CONST = {Text: {}}; // Init language let i18n = navigator.language; let i18n_default = 'en'; if (!Object.keys(CONST.Text).includes(i18n)) {i18n = i18n_default;} main(); function main() { const HOST = getHost(); const API = getAPI(); // Common actions commons(); // API-based actions switch(API[0]) { // https://www.mandao.tv/templets/player/player_php/xigua.php case "man_v": showPlay(); dlButton(); case "man": showLink(); break; default: DoLog('API is {}'.replace('{}', API)); } https://www.mandao.tv/templets/player/m3u8.html API[0] === 'templets' && API[1] === 'player' && hideInlineAd(); } function commons() { // Your common actions here... } function showLink() { DoLog('尝试拦截'); let count = 0, hooked = []; const protects = ['.urlli>div']; const evts = ['DOMSubtreeModified', 'DOMNodeInserted', 'DOMNodeRemoved']; evts.forEach((evtName) => (document.addEventListener(evtName, hook))); function hook(e) { if (!e.target || !e.target.parentElement || !e.target.parentElement.classList) {return false;} if (hooked.includes(e.target)) {return false;} if (protects.some((selector) => (Array.from($All(selector)).includes(e.target)))/* && e.target.style.display === 'none'*/) { count++; const div = e.target; const ul = $(e.target, 'ul'); const parent = div.parentElement; bringback(parent, div, ul, count); DoLog(LogLevel.Success, '拦截到入口[' + count + ']更改'); hooked.push(div); count >= countElms() && evts.forEach((evtName) => (document.removeEventListener(evtName, hook))); } } function bringback(parent, div, ul, n) { const interval = setInterval(work, 100); function work() { if (div.parentElement !== parent) { clearChildnodes(parent); parent.appendChild(div); div.style.display = ''; clearInterval(interval); DoLog(LogLevel.Success, '成功恢复入口[' + n + ']'); } } } function countElms() { let count = 0; for (const selector of protects) { count += $All(selector).length; } return count; } } function showPlay() { if (document.readyState !== 'complete') { window.addEventListener('load', showPlay); return; } const selectors = ['#stab_1_71', '.play_left'/*, '.play_right'*/, '.player', '.playding', '.pfromd', '.footer']; const elms = selectors.map(s => $(s)); for (const elm of elms) { if (elm) { elm.style.display = ''; Object.defineProperty(elm.style, 'display', { get: () => '', set: () => true, enumerable: true, configurable: true, }); } } } function hideInlineAd() { if (document.readyState !== 'complete') { window.addEventListener('load', hideInlineAd); return; } const elm = $('.dplayer-logo'); if (elm) { elm.style.display = 'none'; elm && Object.defineProperty(elm.style, 'display', { get: () => 'none', set: (v) => true, enumerable: true, configurable: false, }); const img = $(elm, 'img'); img.src = ''; img.style.display = 'none'; } } function dlButton() { const src = getCurVideoSrc(); if (!src) { !dlButton.count && (dlButton.count = 0); ++dlButton.count < 60 ? setTimeout(dlButton, 500) : makeBtn('javascript: void(0);', '不受支持', '该视频页面类型不受支持,无法获取下载链接'); return; } makeBtn(src, '下载', '如果直接点击无法下载,可以[右键>另存为],[右键>在新标签页打开],或者[右键>复制链接地址]'); function makeBtn(src, text, title) { const li = $('.playding>ul>.aa'); const dli = li.cloneNode(true); const a = $(dli, 'a'); a.removeAttribute('onclick'); a.href = src; a.download = getCurVideoTitle(); a.target = '_blank'; a.title = title; a.childNodes.forEach((node) => (node.nodeType === 3 && (node.nodeValue = text))); a.removeChild($(a, 'i')); li.insertAdjacentElement('afterend', dli); } } function getCurVideoSrc() { try { return $($($('#cciframe').contentDocument,'#player>iframe').contentDocument,'video[src]').src; } catch(e) { //console.trace([e]) return false; } } function getCurVideoTitle() { return Array.from($All('.urlli>div>ul>li>a')).find((a) => (a.href === location.href)).title; } // Basic functions // querySelector function $() { switch(arguments.length) { case 2: return arguments[0].querySelector(arguments[1]); break; default: return document.querySelector(arguments[0]); } } // querySelectorAll function $All() { switch(arguments.length) { case 2: return arguments[0].querySelectorAll(arguments[1]); break; default: return document.querySelectorAll(arguments[0]); } } // createElement function $CrE() { switch(arguments.length) { case 2: return arguments[0].createElement(arguments[1]); break; default: return document.createElement(arguments[0]); } } // Object1[prop] ==> Object2[prop] function copyProp(obj1, obj2, prop) {obj1[prop] !== undefined && (obj2[prop] = obj1[prop]);} function copyProps(obj1, obj2, props) {props.forEach((prop) => (copyProp(obj1, obj2, prop)));} function clearChildnodes(element) { Array.from(element.childNodes).forEach((child) => (element.removeChild(child))); } // Just stopPropagation and preventDefault function destroyEvent(e) { if (!e) {return false;}; if (!e instanceof Event) {return false;}; e.stopPropagation(); e.preventDefault(); } // GM_XHR HOOK: The number of running GM_XHRs in a time must under maxXHR // Returns the abort function to stop the request anyway(no matter it's still waiting, or requesting) // (If the request is invalid, such as url === '', will return false and will NOT make this request) // If the abort function called on a request that is not running(still waiting or finished), there will be NO onabort event // Requires: function delItem(){...} & function uniqueIDMaker(){...} function GMXHRHook(maxXHR=5) { const GM_XHR = GM_xmlhttpRequest; const getID = uniqueIDMaker(); let todoList = [], ongoingList = []; GM_xmlhttpRequest = safeGMxhr; function safeGMxhr() { // Get an id for this request, arrange a request object for it. const id = getID(); const request = {id: id, args: arguments, aborter: null}; // Deal onload function first dealEndingEvents(request); /* DO NOT DO THIS! KEEP ITS ORIGINAL PROPERTIES! // Stop invalid requests if (!validCheck(request)) { return false; } */ // Judge if we could start the request now or later? todoList.push(request); checkXHR(); return makeAbortFunc(id); // Decrease activeXHRCount while GM_XHR onload; function dealEndingEvents(request) { const e = request.args[0]; // onload event const oriOnload = e.onload; e.onload = function() { reqFinish(request.id); checkXHR(); oriOnload ? oriOnload.apply(null, arguments) : function() {}; } // onerror event const oriOnerror = e.onerror; e.onerror = function() { reqFinish(request.id); checkXHR(); oriOnerror ? oriOnerror.apply(null, arguments) : function() {}; } // ontimeout event const oriOntimeout = e.ontimeout; e.ontimeout = function() { reqFinish(request.id); checkXHR(); oriOntimeout ? oriOntimeout.apply(null, arguments) : function() {}; } // onabort event const oriOnabort = e.onabort; e.onabort = function() { reqFinish(request.id); checkXHR(); oriOnabort ? oriOnabort.apply(null, arguments) : function() {}; } } // Check if the request is invalid function validCheck(request) { const e = request.args[0]; if (!e.url) { return false; } return true; } // Call a XHR from todoList and push the request object to ongoingList if called function checkXHR() { if (ongoingList.length >= maxXHR) {return false;}; if (todoList.length === 0) {return false;}; const req = todoList.shift(); const reqArgs = req.args; const aborter = GM_XHR.apply(null, reqArgs); req.aborter = aborter; ongoingList.push(req); return req; } // Make a function that aborts a certain request function makeAbortFunc(id) { return function() { let i; // Check if the request haven't been called for (i = 0; i < todoList.length; i++) { const req = todoList[i]; if (req.id === id) { // found this request: haven't been called delItem(todoList, i); return true; } } // Check if the request is running now for (i = 0; i < ongoingList.length; i++) { const req = todoList[i]; if (req.id === id) { // found this request: running now req.aborter(); reqFinish(id); checkXHR(); } } // Oh no, this request is already finished... return false; } } // Remove a certain request from ongoingList function reqFinish(id) { let i; for (i = 0; i < ongoingList.length; i++) { const req = ongoingList[i]; if (req.id === id) { ongoingList = delItem(ongoingList, i); return true; } } return false; } } } // Get a url argument from lacation.href // also recieve a function to deal the matched string // returns defaultValue if name not found // Args: {url=location.href, name, dealFunc=((a)=>{return a;}), defaultValue=null} or 'name' function getUrlArgv(details) { typeof(details) === 'string' && (details = {name: details}); typeof(details) === 'undefined' && (details = {}); if (!details.name) {return null;}; const url = details.url ? details.url : location.href; const name = details.name ? details.name : ''; const dealFunc = details.dealFunc ? details.dealFunc : ((a)=>{return a;}); const defaultValue = details.defaultValue ? details.defaultValue : null; const matcher = new RegExp('[\\?&]' + name + '=([^]+)'); const result = url.match(matcher); const argv = result ? dealFunc(result[1]) : defaultValue; return argv; } // Append a style text to document(
) with a