/* eslint-disable no-multi-spaces */ // ==UserScript== // @name Userscript App Core // @name:zh-CN 用户脚本应用核心 // @name:en Userscript App Core // @namespace Userscript-App // @version 0.1 // @description Userscript App Core For Userscript Web Apps // @description:zh-CN 用户脚本网页应用核心 // @description:en Userscript App Core For Userscript Web Apps // @author PY-DNG // @license GPL-v3 // @match http*://*/* // @connect * // @grant GM_info // @grant GM_addStyle // @grant GM_addElement // @grant GM_deleteValue // @grant GM_listValues // @grant GM_addValueChangeListener // @grant GM_removeValueChangeListener // @grant GM_setValue // @grant GM_getValue // @grant GM_log // @grant GM_getResourceText // @grant GM_getResourceURL // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_openInTab // @grant GM_xmlhttpRequest // @grant GM_download // @grant GM_getTab // @grant GM_saveTab // @grant GM_getTabs // @grant GM_notification // @grant GM_setClipboard // @grant GM_info // @grant unsafeWindow // @downloadURL none // ==/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() { unsafeWindow.GM_grant = GM_grant; } function GM_grant(name) { const GMFuncs = { // Tampermonkey provides GM_addStyle: GM_addStyle, GM_addElement: GM_addElement, GM_deleteValue: GM_deleteValue, GM_listValues: GM_listValues, GM_addValueChangeListener: GM_addValueChangeListener, GM_removeValueChangeListener: GM_removeValueChangeListener, GM_setValue: GM_setValue, GM_getValue: GM_getValue, GM_log: GM_log, GM_getResourceText: GM_getResourceText, GM_getResourceURL: GM_getResourceURL, GM_registerMenuCommand: GM_registerMenuCommand, GM_unregisterMenuCommand: GM_unregisterMenuCommand, GM_openInTab: GM_openInTab, GM_xmlhttpRequest: GM_xmlhttpRequest, GM_download: GM_download, GM_getTab: GM_getTab, GM_saveTab: GM_saveTab, GM_getTabs: GM_getTabs, GM_notification: GM_notification, GM_setClipboard: GM_setClipboard }; if (Object.keys(GMFuncs).includes(name)) { return GMFuncs[name]; } else { return null; } } // 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)));} // 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