返回首頁 

动漫岛 视频功能增强

允许观看和下载动漫岛视频,去除部分uBlock Orgin未自动拦截的广告


Install this script?
/* 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// ==/UserScript==(function __MAIN__() {'use strict';// Polyfillsconst 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 windowconst win = (typeof(unsafeWindow) === 'object' && unsafeWindow !== null) ? unsafeWindow : window ;// Global log levels setwin.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 levelDoLog.logLevel = win.isPY_DNG ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error// Log counterDoLog.logCount === undefined && (DoLog.logCount = 0);// Get argslet 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 permitsif (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();// Constancesconst CONST = {Text: {}};// Init languagelet 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 actionscommons();// API-based actionsswitch(API[0]) {// https://www.mandao.tv/templets/player/player_php/xigua.phpcase "man_v":showPlay();dlButton();case "man":showLink();break;default:DoLog('API is {}'.replace('{}', API));}https://www.mandao.tv/templets/player/m3u8.htmlAPI[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// querySelectorfunction $() {switch(arguments.length) {case 2:return arguments[0].querySelector(arguments[1]);break;default:return document.querySelector(arguments[0]);}}// querySelectorAllfunction $All() {switch(arguments.length) {case 2:return arguments[0].querySelectorAll(arguments[1]);break;default:return document.querySelectorAll(arguments[0]);}}// createElementfunction $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 preventDefaultfunction 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 firstdealEndingEvents(request);/* DO NOT DO THIS! KEEP ITS ORIGINAL PROPERTIES!// Stop invalid requestsif (!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 eventconst oriOnload = e.onload;e.onload = function() {reqFinish(request.id);checkXHR();oriOnload ? oriOnload.apply(null, arguments) : function() {};}// onerror eventconst oriOnerror = e.onerror;e.onerror = function() {reqFinish(request.id);checkXHR();oriOnerror ? oriOnerror.apply(null, arguments) : function() {};}// ontimeout eventconst oriOntimeout = e.ontimeout;e.ontimeout = function() {reqFinish(request.id);checkXHR();oriOntimeout ? oriOntimeout.apply(null, arguments) : function() {};}// onabort eventconst oriOnabort = e.onabort;e.onabort = function() {reqFinish(request.id);checkXHR();oriOnabort ? oriOnabort.apply(null, arguments) : function() {};}}// Check if the request is invalidfunction 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 calledfunction 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 requestfunction makeAbortFunc(id) {return function() {let i;// Check if the request haven't been calledfor (i = 0; i < todoList.length; i++) {const req = todoList[i];if (req.id === id) {// found this request: haven't been calleddelItem(todoList, i);return true;}}// Check if the request is running nowfor (i = 0; i < ongoingList.length; i++) {const req = todoList[i];if (req.id === id) {// found this request: running nowreq.aborter();reqFinish(id);checkXHR();}}// Oh no, this request is already finished...return false;}}// Remove a certain request from ongoingListfunction 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 r###lt = url.match(matcher);const argv = r###lt ? dealFunc(r###lt[1]) : defaultValue;return argv;}// Append a style text to document(<head>) with a <style> elementfunction addStyle(css, id) {const style = document.createElement("style");id && (style.id = id);style.textContent = css;for (const elm of document.querySelectorAll('#'+id)) {elm.parentElement && elm.parentElement.removeChild(elm);}document.head.appendChild(style);}// Save dataURL to filefunction saveFile(dataURL, filename) {const a = document.createElement('a');a.href = dataURL;a.download = filename;a.click();}// File download function// details looks like the detail of GM_xmlhttpRequest// onload function will be called after file saved to diskfunction downloadFile(details) {if (!details.url || !details.name) {return false;};// Configure request objectconst requestObj = {url: details.url,responseType: 'blob',onload: function(e) {// Save filesaveFile(URL.createObjectURL(e.response), details.name);// onload callbackdetails.onload ? details.onload(e) : function() {};}}if (details.onloadstart       ) {requestObj.onloadstart        = details.onloadstart;};if (details.onprogress        ) {requestObj.onprogress         = details.onprogress;};if (details.onerror           ) {requestObj.onerror            = details.onerror;};if (details.onabort           ) {requestObj.onabort            = details.onabort;};if (details.onreadystatechange) {requestObj.onreadystatechange = details.onreadystatechange;};if (details.ontimeout         ) {requestObj.ontimeout          = details.ontimeout;};// Send requestGM_xmlhttpRequest(requestObj);}// get '/' splited API array from a urlfunction getAPI(url=location.href) {return url.replace(/https?:\/\/(.*?\.){1,2}.*?\//, '').replace(/\?.*/, '').match(/[^\/]+?(?=(\/|$))/g);}// get host part from a url(includes '^https://', '/$')function getHost(url=location.href) {const match = location.href.match(/https?:\/\/[^\/]+\//);return match ? match[0] : match;}function AsyncManager() {const AM = this;// Ongoing xhr countthis.taskCount = 0;// Whether generate finish eventslet finishEvent = false;Object.defineProperty(this, 'finishEvent', {configurable: true,enumerable: true,get: () => (finishEvent),set: (b) => {finishEvent = b;b && AM.taskCount === 0 && AM.onfinish && AM.onfinish();}});// Add one taskthis.add = () => (++AM.taskCount);// Finish one taskthis.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount));}// NMonkey By PY-DNG, 2021.07.18 - 2022.02.18, License GPL-3// NMonkey: Provides GM_Polyfills and make your userscript compatible with non-script-manager environment// Description:/*Simulates a script-manager environment("NMonkey Environment") for non-script-manager browser, load @require & @resource, provides some GM_functions(listed below), and also compatible with script-manager environment.Provides GM_setValue, GM_getValue, GM_deleteValue, GM_listValues, GM_xmlhttpRequest, GM_openInTab, GM_setClipboard, GM_getResourceText, GM_getResourceURL, GM_addStyle, GM_addElement, GM_log, unsafeWindow(object), GM_info(object)Also provides an object called GM_POLYFILLED which has the following properties that shows you which GM_functions are actually polyfilled.Returns true if polyfilled is environment is ready, false for not. Don't worry, just follow the usage below.*/// Note: DO NOT DEFINE GM-FUNCTION-NAMES IN YOUR CODE. DO NOT DEFINE GM_POLYFILLED AS WELL.// Note: NMonkey is an advanced version of GM_PolyFill (and BypassXB), it includes more functions than GM_PolyFill, and provides better stability and compatibility. Do NOT use NMonkey and GM_PolyFill (and BypassXB) together in one script.// Usage:/*// ==UserScript==// @name      xxx// @namespace xxx// @version   1.0// ...// @require   https://.../xxx.js// @require   ...// ...// @resource  https://.../xxx// @resource  ...// ...// ==/UserScript==// Use a closure to wrap your code. Make sure you have it a name.(function YOUR_MAIN_FUNCTION() {'use strict';// Strict mode is optional. You can use strict mode or not as you want.// Polyfill first. Do NOT do anything before Polyfill.var NMonkey_Ready = NMonkey({mainFunc: YOUR_MAIN_FUNCTION,name: "script-storage-key, aims to separate different scripts' storage area. Use your script's @namespace value if you don't how to fill this field.",requires: [{name: "", // Optional, used to display loading error messages if anything went wrong while loading this itemsrc: "https://.../xxx.js",loaded: function() {return boolean_value_shows_whether_this_js_has_already_loaded;}execmode: "'eval' for eval code in current scope or 'function' for Function(code)() in global scope or 'script' for inserting a <script> element to document.head"},...],resources: [{src: "https://.../xxx"name: "@resource name. Will try to get it from @resource using this name before fetch it from src",},...],GM_info: {// You can get GM_info object, if you provide this argument(and there is no GM_info provided by the script-manager).// You can provide any object here, what you provide will be what you get.// Additionally, two property of NMonkey itself will be attached to GM_info if polyfilled:// {//     scriptHandler: "NMonkey"//     version: "NMonkey's version, it should look like '0.1'"// }// The following is just an example.script: {name: 'my first userscript for non-scriptmanager browsers!',description: 'this script works well both in my PC and my mobile!',version: '1.0',released: true,version_num: 1,authors: ['Johnson', 'Leecy', 'War Mars']update_history: {'0.9': 'First beta version','1.0': 'Finally released!'}}surprise: 'if you check GM_info.surprise and you will read this!'// And property "scriptHandler" & "version" will be attached here}});if (!NMonkey_Ready) {// Stop executing of polyfilled environment not ready.// Don't worry, during polyfill progress YOUR_MAIN_FUNCTION will be called twice, and on the second call the polyfilled environment will be ready.return;}// Your code here...// Make sure your code is written after NMonkey be calledif// ...// Just place NMonkey function code herefunction NMonkey(details) {...}}) ();// Oh you want to write something here? Fine. But code you write here cannot get into the simulated script-manager-environment.*/function NMonkey(details) {// Constancesconst CONST = {Text: {Require_Load_Failed: '动态加载依赖js库失败(自动重试也都失败了),请刷新页面后再试:(\n一共尝试了{I}个备用加载源\n加载项目:{N}',Resource_Load_Failed: '动态加载依赖resource资源失败(自动重试也都失败了),请刷新页面后再试:(\n一共尝试了{I}个备用加载源\n加载项目:{N}',UnkownItem: '未知项目',}};// Init DoLogDoLog();// Get argumentconst mainFunc = details.mainFunc;const name = details.name || 'default';const requires = details.requires || [];const resources = details.resources || [];details.GM_info = details.GM_info || {};details.GM_info.scriptHandler = 'NMonkey';details.GM_info.version = '1.0';// Run in variable-name-polifilled environmentif (InNPEnvironment()) {// Already in polifilled environment === polyfill has alredy done, just returnreturn true;}// Polyfill functions and dataconst GM_POLYFILL_KEY_STORAGE = 'GM_STORAGE_POLYFILL';let GM_POLYFILL_storage;const Supports = {GetStorage: function() {let gstorage = localStorage.getItem(GM_POLYFILL_KEY_STORAGE);gstorage = gstorage ? JSON.parse(gstorage) : {};let storage = gstorage[name] ? gstorage[name] : {};return storage;},SaveStorage: function() {let gstorage = localStorage.getItem(GM_POLYFILL_KEY_STORAGE);gstorage = gstorage ? JSON.parse(gstorage) : {};gstorage[name] = GM_POLYFILL_storage;localStorage.setItem(GM_POLYFILL_KEY_STORAGE, JSON.stringify(gstorage));},};const Provides = {// GM_setValueGM_setValue: function(name, value) {GM_POLYFILL_storage = Supports.GetStorage();name = String(name);GM_POLYFILL_storage[name] = value;Supports.SaveStorage();},// GM_getValueGM_getValue: function(name, defaultValue) {GM_POLYFILL_storage = Supports.GetStorage();name = String(name);if (GM_POLYFILL_storage.hasOwnProperty(name)) {return GM_POLYFILL_storage[name];} else {return defaultValue;}},// GM_deleteValueGM_deleteValue: function(name) {GM_POLYFILL_storage = Supports.GetStorage();name = String(name);if (GM_POLYFILL_storage.hasOwnProperty(name)) {delete GM_POLYFILL_storage[name];Supports.SaveStorage();}},// GM_listValuesGM_listValues: function() {GM_POLYFILL_storage = Supports.GetStorage();return Object.keys(GM_POLYFILL_storage);},// unsafeWindowunsafeWindow: window,// GM_xmlhttpRequest// not supported properties of details: synchronous binary nocache revalidate context fetch// not supported properties of response(onload arguments[0]): finalUrl// ---!IMPORTANT!--- DOES NOT SUPPORT CROSS-ORIGIN REQUESTS!!!!! ---!IMPORTANT!---// details.synchronous is not supported as TampermonkeyGM_xmlhttpRequest: function(details) {const xhr = new XMLHttpRequest();// open requestconst openArgs = [details.method, details.url, true];if (details.user && details.password) {openArgs.push(details.user);openArgs.push(details.password);}xhr.open.apply(xhr, openArgs);// set headersif (details.headers) {for (const key of Object.keys(details.headers)) {xhr.setRequestHeader(key, details.headers[key]);}}details.cookie ? xhr.setRequestHeader('cookie', details.cookie) : function () {};details.anonymous ? xhr.setRequestHeader('cookie', '') : function () {};// propertiesxhr.timeout = details.timeout;xhr.responseType = details.responseType;details.overrideMimeType ? xhr.overrideMimeType(details.overrideMimeType) : function () {};// eventsxhr.onabort = details.onabort;xhr.onerror = details.onerror;xhr.onloadstart = details.onloadstart;xhr.onprogress = details.onprogress;xhr.onreadystatechange = details.onreadystatechange;xhr.ontimeout = details.ontimeout;xhr.onload = function (e) {const response = {readyState: xhr.readyState,status: xhr.status,statusText: xhr.statusText,responseHeaders: xhr.getAllResponseHeaders(),response: xhr.response};(details.responseType === '' || details.responseType === 'text') ? (response.responseText = xhr.responseText) : function () {};(details.responseType === '' || details.responseType === 'document') ? (response.responseXML = xhr.responseXML) : function () {};details.onload(response);}// send requestdetails.data ? xhr.send(details.data) : xhr.send();return {abort: xhr.abort};},// NOTE: options(arg2) is NOT SUPPORTED! if provided, then will just be skipped.GM_openInTab: function(url) {window.open(url);},// NOTE: needs to be called in an event handler function, and info(arg2) is NOT SUPPORTED!GM_setClipboard: function(text) {// Create a new textarea for copyingconst newInput = document.createElement('textarea');document.body.appendChild(newInput);newInput.value = text;newInput.select();document.execCommand('copy');document.body.removeChild(newInput);},GM_getResourceText: function(name) {const _get = typeof(GM_getResourceText) === 'function' ? GM_getResourceText : () => (null);let text = _get(name);if (text) {return text;}for (const resource of resources) {if (resource.name === name) {return resource.content ? resource.content : null;}}return null;},GM_getResourceURL: function(name) {const _get = typeof(GM_getResourceURL) === 'function' ? GM_getResourceURL : () => (null);let url = _get(name);if (url) {return url;}for (const resource of resources) {if (resource.name === name) {return resource.src ? btoa(resource.src) : null;}}return null;},GM_addStyle: function(css) {const style = document.createElement('style');style.innerHTML = css;document.head.appendChild(style);},GM_addElement: function() {let parent_node, tag_name, attributes;const head_elements = ['title', 'base', 'link', 'style', 'meta', 'script', 'noscript'/*, 'template'*/];if (arguments.length === 2) {tag_name = arguments[0];attributes = arguments[1];parent_node = head_elements.includes(tag_name.toLowerCase()) ? document.head : document.body;} else if (arguments.length === 3) {parent_node = arguments[0];tag_name = arguments[1];attributes = arguments[2];}const element = document.createElement(tag_name);for (const [prop, value] of Object.entries(attributes)) {element[prop] = value;}parent_node.appendChild(element);},GM_log: function() {const args = [];for (let i = 0; i < arguments.length; i++) {args[i] = arguments[i];}console.log.apply(null, args);},GM_info: details.GM_info,GM: {info: details.GM_info}};const _GM_POLYFILLED = Provides.GM_POLYFILLED = {};for (const pname of Object.keys(Provides)) {_GM_POLYFILLED[pname] = true;}// Not in polifilled environment, then polyfill functions and create & move into the environment// Bypass xbrowser's useless GM_functionsbypassXB();// Create & move into polifilled environmentExecInNPEnv();return false;// Bypass xbrowser's useless GM_functionsfunction bypassXB() {if (typeof(mbrowser) === 'object' || (typeof(GM_info) === 'object' && GM_info.scriptHandler === 'XMonkey')) {// Useless functions in XMonkey 1.0const GM_funcs = ['unsafeWindow','GM_getValue','GM_setValue','GM_listValues','GM_deleteValue',//'GM_xmlhttpRequest',];for (const GM_func of GM_funcs) {window[GM_func] = undefined;eval('typeof({F}) === "function" && ({F} = Provides.{F});'.replaceAll('{F}', GM_func));}// Delete dirty data saved by these stupid functions beforefor (let i = 0; i < localStorage.length; i++) {const key = localStorage.key(i);const value = localStorage.getItem(key);value === '[object Object]' && localStorage.removeItem(key);}}}// Check if already in name-predefined environment// I think there won't be anyone else wants to use this ####ing variable name...function InNPEnvironment() {return (typeof(GM_POLYFILLED) === 'object' && GM_POLYFILLED !== null && GM_POLYFILLED !== window.GM_POLYFILLED) ? true : false;}function ExecInNPEnv() {const NG = new NameGenerator();// Init namesconst tnames = ['context', 'fapply', 'CDATA', 'uneval', 'define', 'module', 'exports', 'window', 'globalThis', 'console', 'cloneInto', 'exportFunction', 'createObjectIn', 'GM', 'GM_info'];const pnames = Object.keys(Provides);const fnames = tnames.slice();const argvlist = [];const argvs = [];// Add providesfor (const pname of pnames) {!fnames.includes(pname) && fnames.push(pname);}// Add grantsif (typeof(GM_info) === 'object' && GM_info.script && GM_info.script.grant) {for (const gname of GM_info.script.grant) {!fnames.includes(gname) && fnames.push(gname);}}// Make name codefor (let i = 0; i < fnames.length; i++) {const fname = fnames[i];const exist = eval('typeof ' + fname + ' !== "undefined"') && fname !== 'GM_POLYFILLED';argvlist[i] = exist ? fname : (Provides.hasOwnProperty(fname) ? 'Provides.'+fname : '');argvs[i] = exist ? eval(fname) : (Provides.hasOwnProperty(fname) ? Provides[name] : undefined);pnames.includes(fname) && (_GM_POLYFILLED[fname] = !exist);}// Load all @require and @resourceloadRequires(requires, resources, function(requires, resources) {// Join requirecodelet requirecode = '';for (const require of requires) {const mode = require.execmode ? require.execmode : 'eval';const content = require.content;if (!content) {continue;}switch(mode) {case 'eval':requirecode += content + '\n';break;case 'function': {const func = Function.apply(null, fnames.concat(content));func.apply(null, argvs);break;}case 'script': {const s = document.createElement('script');s.innerHTML = content;document.head.appendChild(s);break;}}}// Make final code & evalconst varnames = ['NG', 'tnames', 'pnames', 'fnames', 'argvist', 'argvs', 'code', 'finalcode', 'wrapper', 'ExecInNPEnv', 'GM_POLYFILL_KEY_STORAGE', 'GM_POLYFILL_storage', 'InNPEnvironment', 'NameGenerator', 'LocalCDN', 'loadRequires', 'requestText', 'Provides', 'Supports', 'bypassXB', 'details', 'mainFunc', 'name', 'requires', 'resources', '_GM_POLYFILLED', 'CONST', 'NMonkey', 'polyfill_status'];const code = requirecode + 'let ' + varnames.join(', ') + ';\n(' + mainFunc.toString() + ') ();';const wrapper = Function.apply(null, fnames.concat(code));const finalcode = '(' + wrapper.toString() + ').apply(this, [' + argvlist.join(', ') + ']);';eval(finalcode);});function NameGenerator() {const NG = this;const letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';let index = [0];NG.generate = function() {const chars = [];indexIncrease();for (let i = 0; i < index.length; i++) {chars[i] = letters.charAt(index[i]);}return chars.join('');}NG.randtext = function(len=32) {const chars = [];for (let i = 0; i < len; i++) {chars[i] = letters[randint(0, letter.length-1)];}return chars.join('');}function indexIncrease(i=0) {index[i] === undefined && (index[i] = -1);++index[i] >= letters.length && (index[i] = 0, indexIncrease(i+1));}function randint(min, max) {return Math.floor(Math.random() * (max - min + 1)) + min;}}}// Load all @require and @resource for non-GM/TM environments (such as Alook javascript extension)// Requirements: function AsyncManager(){...}, function LocalCDN(){...}function loadRequires(requires, resoures, callback, args=[]) {// LocalCDNconst LCDN = new LocalCDN();// AsyncManagerconst AM = new AsyncManager();AM.onfinish = function() {callback.apply(null, [requires, resoures].concat(args));}// Load jsfor (const js of requires) {!js.loaded() && loadinJs(js);}// Load resourcefor (const resource of resoures) {loadinResource(resource);}AM.finishEvent = true;function loadinJs(js) {AM.add();const srclist = js.srcset ? LCDN.sort(js.srcset).srclist : [];let i = -1;LCDN.get(js.src, onload, [], onfail);function onload(content) {js.content = content;AM.finish();}function onfail() {i++;if (i < srclist.length) {LCDN.get(srclist[i], onload, [], onfail);} else {alert(CONST.Text.Require_Load_Failed.replace('{I}', i.toString()).replace('{N}', js.name ? js.name : CONST.Text.UnkownItem));}}}function loadinResource(resource) {let content;if (typeof GM_getResourceText === 'function' && (content = GM_getResourceText(resource.name))) {resource.content = content;} else {AM.add();let i = -1;LCDN.get(resource.src, onload, [], onfail);function onload(content) {resource.content = content;AM.finish();}function onfail(content) {i++;if (resource.srcset && i < resource.srcset.length) {LCDN.get(resource.srcset[i], onload, [], onfail);} else {debugger;alert(CONST.Text.Resource_Load_Failed.replace('{I}', i.toString()).replace('{N}', js.name ? js.name : CONST.Text.UnkownItem));}}}}}// Loads web resources and saves them to GM-storage// Tries to load web resources from GM-storage in subsequent calls// Updates resources every $(this.expire) hours, or use $(this.refresh) function to update all resources instantly// Dependencies: GM_getValue(), GM_setValue(), requestText(), AsyncManager(), KEY_LOCALCDNfunction LocalCDN() {const LC = this;const _GM_getValue = typeof(GM_getValue) === 'function' ? GM_getValue : Provides.GM_getValue;const _GM_setValue = typeof(GM_setValue) === 'function' ? GM_setValue : Provides.GM_setValue;const KEY_LOCALCDN = 'LOCAL-CDN';const KEY_LOCALCDN_VERSION = 'version';const VALUE_LOCALCDN_VERSION = '0.3';// Default expire time (by hour)LC.expire = 72;// Try to get resource content from loaclCDN first, if failed/timeout, request from web && save to LocalCDN// Accepts callback only: onload & onfail(optional)// Returns true if got from LocalCDN, false if got from webLC.get = function(url, onload, args=[], onfail=function(){}) {const CDN = _GM_getValue(KEY_LOCALCDN, {});const resource = CDN[url];const time = (new Date()).getTime();if (resource && resource.content !== null && !expired(time, resource.time)) {onload.apply(null, [resource.content].concat(args));return true;} else {LC.request(url, _onload, [], onfail);return false;}function _onload(content) {onload.apply(null, [content].concat(args));}}// Generate resource obj and set to CDN[url]// Returns resource obj// Provide content means load success, provide null as content means load failedLC.set = function(url, content) {const CDN = _GM_getValue(KEY_LOCALCDN, {});const time = (new Date()).getTime();const resource = {url: url,time: time,content: content,success: content !== null ? (CDN[url] ? CDN[url].success + 1 : 1) : (CDN[url] ? CDN[url].success : 0),fail: content === null ? (CDN[url] ? CDN[url].fail + 1 : 1) : (CDN[url] ? CDN[url].fail : 0),};CDN[url] = resource;_GM_setValue(KEY_LOCALCDN, CDN);return resource;}// Delete one resource from LocalCDNLC.delete = function(url) {const CDN = _GM_getValue(KEY_LOCALCDN, {});if (!CDN[url]) {return false;} else {delete CDN[url];_GM_setValue(KEY_LOCALCDN, CDN);return true;}}// Delete all resources in LocalCDNLC.clear = function() {_GM_setValue(KEY_LOCALCDN, {});upgradeConfig();}// List all resource saved in LocalCDNLC.list = function() {const CDN = _GM_getValue(KEY_LOCALCDN, {});const urls = LC.listurls();return LC.listurls().map((url) => (CDN[url]));}// List all resource's url saved in LocalCDNLC.listurls = function() {return Object.keys(_GM_getValue(KEY_LOCALCDN, {})).filter((url) => (url !== KEY_LOCALCDN_VERSION));}// Request content from web and save it to CDN[url]// Accepts callbacks only: onload & onfail(optional)LC.request = function(url, onload, args=[], onfail=function(){}) {const CDN = _GM_getValue(KEY_LOCALCDN, {});requestText(url, _onload, [], _onfail);function _onload(content) {LC.set(url, content);onload.apply(null, [content].concat(args));}function _onfail() {LC.set(url, null);onfail();}}// Re-request all resources in CDN instantly, ignoring LC.expireLC.refresh = function(callback, args=[]) {const urls = LC.listurls();const AM = new AsyncManager();AM.onfinish = function() {callback.apply(null, [].concat(args))};for (const url of urls) {AM.add();LC.request(url, function() {AM.finish();});}AM.finishEvent = true;}// Sort src && srcset, to get a best request sortingLC.sort = function(srcset) {const CDN = _GM_getValue(KEY_LOCALCDN, {});const r###lt = {srclist: [], lists: []};const lists = r###lt.lists;const srclist = r###lt.srclist;const suc_rec = lists[0] = []; // Recent successes take second (not expired yet)const suc_old = lists[1] = []; // Old successes take thirdconst fails   = lists[2] = []; // Fails & unused take the last placeconst time = (new Date()).getTime();// Make listsfor (const s of srcset) {const resource = CDN[s];if (resource && resource.content !== null) {if (!expired(resource.time, time)) {suc_rec.push(s);} else {suc_old.push(s);}} else {fails.push(s);}}// Sort lists// Recently successed: Choose most recent onessuc_rec.sort((res1, res2) => (res2.time - res1.time));// Successed long ago or failed: Sort by success rate & tried time[suc_old, fails].forEach((arr) => (arr.sort(sorting)));// Push all resources into seclist[suc_rec, suc_old, fails].forEach((arr) => (arr.forEach((res) => (srclist.push(res)))));DoLog(['LocalCDN: sorted', r###lt]);return r###lt;function sorting(res1, res2) {const sucRate1 = (res1.success+1) / (res1.fail+1);const sucRate2 = (res2.success+1) / (res2.fail+1);if (sucRate1 !== sucRate2) {// Success rate: high to lowreturn sucRate2 - sucRate1;} else {// Tried time: less to more// Less tried time means newer added sourcereturn (res1.success+res1.fail) - (res2.success+res2.fail);}}}function upgradeConfig() {const CDN = _GM_getValue(KEY_LOCALCDN, {});switch(CDN[KEY_LOCALCDN_VERSION]) {case undefined:init();break;case '0.1':v01_To_v02();logUpgrade();break;case '0.2':v01_To_v02();v02_To_v03();logUpgrade();break;case VALUE_LOCALCDN_VERSION:DoLog('LocalCDN is in latest version.');break;default:DoLog(LogLevel.Error, 'LocalCDN.upgradeConfig: Invalid config version({V}) for LocalCDN. '.replace('{V}', CDN[KEY_LOCALCDN_VERSION]));}CDN[KEY_LOCALCDN_VERSION] = VALUE_LOCALCDN_VERSION;_GM_setValue(KEY_LOCALCDN, CDN);function logUpgrade() {DoLog(LogLevel.Success, 'LocalCDN successfully upgraded From v{V1} to v{V2}. '.replaceAll('{V1}', CDN[KEY_LOCALCDN_VERSION]).replaceAll('{V2}', VALUE_LOCALCDN_VERSION));}function init() {// Nothing to do here}function v01_To_v02() {const urls = LC.listurls();for (const url of urls) {if (url === KEY_LOCALCDN_VERSION) {continue;}CDN[url] = {url: url,time: 0,content: CDN[url]};}}function v02_To_v03() {const urls = LC.listurls();for (const url of urls) {CDN[url].success = CDN[url].fail = 0;}}}function clearExpired() {const resources = LC.list();const time = (new Date()).getTime();for (const resource of resources) {expired(resource.time, time) && LC.delete(resource.url);}}function expired(t1, t2) {return (t1 - t2) > (LC.expire * 60 * 60 * 1000);}upgradeConfig();clearExpired();}function requestText(url, callback, args=[], onfail=function(){}) {const req = typeof(GM_xmlhttpRequest) === 'function' ? GM_xmlhttpRequest : Provides.GM_xmlhttpRequest;req({method:       'GET',url:          url,responseType: 'text',timeout:      45*1000,onload:       function(response) {const text = response.responseText;const argvs = [text].concat(args);callback.apply(null, argvs);},onerror:      onfail,ontimeout:    onfail,onabort:      onfail,})}function AsyncManager() {const AM = this;// Ongoing xhr countthis.taskCount = 0;// Whether generate finish eventslet finishEvent = false;Object.defineProperty(this, 'finishEvent', {configurable: true,enumerable: true,get: () => (finishEvent),set: (b) => {finishEvent = b;b && AM.taskCount === 0 && AM.onfinish && AM.onfinish();}});// Add one taskthis.add = () => (++AM.taskCount);// Finish one taskthis.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount));}// Arguments: level=LogLevel.Info, logContent, asObject=false// Needs one call "DoLog();" to get it initialized before using it!function DoLog() {const win = typeof(unsafeWindow) !== 'undefined' ? unsafeWindow : window;// Global log levels setwin.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 levelDoLog.logLevel = win.isPY_DNG ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error// Log counterDoLog.logCount === undefined && (DoLog.logCount = 0);if (++DoLog.logCount > 512) {console.clear();DoLog.logCount = 0;}// Get argslet 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 permitsif (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;}}console.log(msg, subst, logContent);}}}// Polyfill String.prototype.replaceAll// replaceValue does NOT support regexp match groups($1, $2, etc.)function polyfill_replaceAll() {String.prototype.replaceAll = String.prototype.replaceAll ? String.prototype.replaceAll : PF_replaceAll;function PF_replaceAll(searchValue, replaceValue) {const str = String(this);if (searchValue instanceof RegExp) {const global = RegExp(searchValue, 'g');if (/\$/.test(replaceValue)) {console.error('Error: Polyfilled String.protopype.replaceAll does support regexp groups');};return str.replace(global, replaceValue);} else {return str.split(searchValue).join(replaceValue);}}}function randint(min, max) {return Math.floor(Math.random() * (max - min + 1)) + min;}// Del a item from an array using its index. Returns the array but can NOT modify the original array directly!!function delItem(arr, delIndex) {arr = arr.slice(0, delIndex).concat(arr.slice(delIndex+1));return arr;}})();