常用函数

自用函数 For wenku8++

目前为 2022-08-24 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/449412/1085085/Basic%20Functions.js

/* eslint-disable no-multi-spaces */

// ==UserScript==
// @name               Basic Functions
// @name:zh-CN         常用函数
// @name:en            Basic Functions
// @namespace          Wenku8++
// @version            0.3
// @description        自用函数 For wenku8++
// @description:zh-CN  自用函数 For wenku8++
// @description:en     Useful functions for myself
// @author             PY-DNG
// @license            GPL-license
// @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
// ==/UserScript==

const LogLevel = {
	None: 0,
	Error: 1,
	Success: 2,
	Warning: 3,
	Info: 4,
}

// 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
	LogLevel = {
		None: 0,
		Error: 1,
		Success: 2,
		Warning: 3,
		Info: 4,
	}
	const LogLevelMap = {};
	LogLevelMap[LogLevel.None] = {
		prefix: '',
		color: 'color:#ffffff'
	}
	LogLevelMap[LogLevel.Error] = {
		prefix: '[Error]',
		color: 'color:#ff0000'
	}
	LogLevelMap[LogLevel.Success] = {
		prefix: '[Success]',
		color: 'color:#00aa00'
	}
	LogLevelMap[LogLevel.Warning] = {
		prefix: '[Warning]',
		color: 'color:#ffa500'
	}
	LogLevelMap[LogLevel.Info] = {
		prefix: '[Info]',
		color: 'color:#888888'
	}
	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 + (typeof MODULE_DATA === 'object' ? '[' + MODULE_DATA.name + ']' : '');
		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();

// 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.hasOwnProperty(prop) && (obj2[prop] = obj1[prop]);
}
function copyProps(obj1, obj2, props) {
	props.forEach((prop) => (copyProp(obj1, obj2, prop)));
}

function clearChildNodes(elm) {
	for (const el of elm.childNodes) {
		elm.removeChild(el);
	}
}

// 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(<head>) with a <style> element
function 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 file
function 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 disk
function downloadFile(details) {
	if (!details.url || !details.name) {
		return false;
	};

	// Configure request object
	const requestObj = {
		url: details.url,
		responseType: 'blob',
		onload: function(e) {
			// Save file
			saveFile(URL.createObjectURL(e.response), details.name);

			// onload callback
			details.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 request
	GM_xmlhttpRequest(requestObj);
}

// get '/' splited API array from a url
function 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 count
	this.taskCount = 0;

	// Whether generate finish events
	let 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 task
	this.add = () => (++AM.taskCount);

	// Finish one task
	this.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount));
}

// 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;
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址