UNPKG

@whitesev/utils

Version:

一个常用的工具库

1,810 lines (1,791 loc) 154 kB
import { ColorConversion } from "./ColorConversion"; import { GBKEncoder } from "./GBKEncoder"; import { UtilsGMCookie } from "./UtilsGMCookie"; import { AjaxHooker } from "./ajaxHooker/ajaxHooker.js"; import { AjaxHooker1_2_4 } from "./ajaxHooker/ajaxHooker1.2.4"; import { GMMenu } from "./UtilsGMMenu"; import { Hooks } from "./Hooks"; import { Httpx } from "./Httpx"; import { indexedDB } from "./indexedDB"; import { LockFunction } from "./LockFunction"; import { Log } from "./Log"; import { Progress } from "./Progress"; import { TryCatch } from "./TryCatch"; import { UtilsDictionary } from "./Dictionary"; import type { DOMUtils_EventType } from "./types/Event"; import type { UtilsAjaxHookResult } from "./types/ajaxHooker"; import { GenerateUUID } from "./UtilsCommon"; import { WindowApi } from "./WindowApi"; import { Vue } from "./Vue"; import { type ArgsType, type JSTypeNames, type UtilsOwnObject, } from "./types/global"; import type { WindowApiOption } from "./types/WindowApi"; import { ModuleRaid } from "./ModuleRaid"; class Utils { private windowApi: typeof WindowApi.prototype; constructor(option?: WindowApiOption) { this.windowApi = new WindowApi(option); } /** 版本号 */ version = "2025.4.11"; /** * 在页面中增加style元素,如果html节点存在子节点,添加子节点第一个,反之,添加到html节点的子节点最后一个 * @param cssText css字符串 * @returns 返回添加的CSS标签 * @example * Utils.GM_addStyle("html{}"); * > <style type="text/css">html{}</style> */ addStyle(cssText: string): HTMLStyleElement; addStyle(cssText: string) { if (typeof cssText !== "string") { throw new Error("Utils.addStyle 参数cssText 必须为String类型"); } let cssNode = this.windowApi.document.createElement("style"); cssNode.setAttribute("type", "text/css"); cssNode.innerHTML = cssText; if (this.windowApi.document.head) { /* 插入head最后 */ this.windowApi.document.head.appendChild(cssNode); } else if (this.windowApi.document.body) { /* 插入body后 */ this.windowApi.document.body.appendChild(cssNode); } else if ( this.windowApi.document.documentElement.childNodes.length === 0 ) { /* 插入#html第一个元素后 */ this.windowApi.document.documentElement.appendChild(cssNode); } else { /* 插入head前面 */ this.windowApi.document.documentElement.insertBefore( cssNode, this.windowApi.document.documentElement.childNodes[0] ); } return cssNode; } /** * JSON数据从源端替换到目标端中,如果目标端存在该数据则替换,不添加,返回结果为目标端替换完毕的结果 * @param target 目标数据 * @param source 源数据 * @param isAdd 是否可以追加键,默认false * @example * Utils.assign({"1":1,"2":{"3":3}}, {"2":{"3":4}}); * > * { "1": 1, "2": { "3": 4 } } */ assign<T1, T2 extends object, T3 extends boolean>( target: T1, source: T2, isAdd?: T3 ): T3 extends true ? T1 & T2 : T1; assign(target = {}, source = {}, isAdd = false) { let UtilsContext = this; if (Array.isArray(source)) { let canTraverse = source.filter((item) => { return typeof item === "object"; }); if (!canTraverse.length) { return source; } } if (source == null) { return target; } if (target == null) { target = {}; } if (isAdd) { for (const sourceKeyName in source) { const targetKeyName = sourceKeyName; let targetValue = (target as any)[targetKeyName]; let sourceValue = (source as any)[sourceKeyName]; if ( typeof sourceValue === "object" && sourceValue != null && sourceKeyName in target && !UtilsContext.isDOM(sourceValue) ) { /* 源端的值是object类型,且不是元素节点 */ (target as any)[sourceKeyName] = UtilsContext.assign( targetValue, sourceValue, isAdd ); continue; } (target as any)[sourceKeyName] = sourceValue; } } else { for (const targetKeyName in target) { if (targetKeyName in source) { let targetValue = (target as any)[targetKeyName]; let sourceValue = (source as any)[targetKeyName]; if ( typeof sourceValue === "object" && sourceValue != null && !UtilsContext.isDOM(sourceValue) && Object.keys(sourceValue).length ) { /* 源端的值是object类型,且不是元素节点 */ (target as any)[targetKeyName] = UtilsContext.assign( targetValue, sourceValue, isAdd ); continue; } /* 直接赋值 */ (target as any)[targetKeyName] = sourceValue; } } } return target; } /** * 异步替换字符串 * @param string 需要被替换的目标字符串 * @param pattern 正则匹配模型 * @param asyncFn 异步获取的函数 */ asyncReplaceAll( string: string, pattern: RegExp | string, asyncFn: (item: string) => Promise<string> ): Promise<string>; async asyncReplaceAll( string: string, pattern: RegExp | string, asyncFn: (item: string) => Promise<string> ) { let UtilsContext = this; if (typeof string !== "string") { throw new TypeError("string必须是字符串"); } if (typeof asyncFn !== "function") { throw new TypeError("asyncFn必须是函数"); } let reg; if (typeof pattern === "string") { reg = new RegExp(UtilsContext.parseStringToRegExpString(pattern), "g"); } else if (pattern instanceof RegExp) { if (!pattern.global) { throw new TypeError("pattern必须是全局匹配"); } reg = new RegExp(pattern); } else { throw new TypeError("pattern必须是正则对象"); } let result = []; let match; let lastIndex = 0; while ((match = reg.exec(string)) !== null) { /* 异步获取匹配对应的字符串 */ const item = asyncFn(match[0]); /* 获取该匹配项和上一个匹配项的中间的字符串 */ const prefix = string.slice(lastIndex, match.index); lastIndex = match.index + match[0].length; result.push(item); result.push(prefix); } result.push(string.slice(lastIndex)); /* 等待所有异步完成 */ result = await Promise.all(result); return result.join(""); } /** * ajax劫持库,支持xhr和fetch劫持。 * + 来源:https://bbs.tampermonkey.net.cn/thread-3284-1-1.html * + 作者:cxxjackie * + 版本:1.4.4 * + 旧版本:1.2.4 * + 文档:https://scriptcat.org/zh-CN/script-show-page/637/ * @param useOldVersion 是否使用旧版本,默认false */ ajaxHooker = (useOldVersion: boolean = false): UtilsAjaxHookResult => { if (useOldVersion) { return AjaxHooker1_2_4(); } else { return AjaxHooker(); } }; /** * 根据坐标点击canvas元素的内部位置 * @param canvasElement 画布元素 * @param clientX X坐标,默认值0 * @param clientY Y坐标,默认值0 * @param view 触发的事件目标 */ canvasClickByPosition( canvasElement: HTMLCanvasElement, clientX?: number | string, clientY?: number | string, view?: Window & typeof globalThis ): void; canvasClickByPosition( canvasElement: HTMLCanvasElement, clientX = 0, clientY = 0, view = globalThis ) { if (!(canvasElement instanceof HTMLCanvasElement)) { throw new Error( "Utils.canvasClickByPosition 参数canvasElement必须是canvas元素" ); } clientX = parseInt(clientX.toString()); clientY = parseInt(clientY.toString()); const eventInit: MouseEventInit = { cancelBubble: true, cancelable: true, clientX: clientX, clientY: clientY, // @ts-ignore view: view, detail: 1, }; canvasElement.dispatchEvent(new MouseEvent("mousedown", eventInit)); canvasElement.dispatchEvent(new MouseEvent("mouseup", eventInit)); } /** * 【手机】检测点击的地方是否在该元素区域内 * @param element 需要检测的元素 * @returns * + true 点击在元素上 * + false 未点击在元素上 * @example * Utils.checkUserClickInNode(document.querySelector(".xxx")); * > false **/ checkUserClickInNode(element: Element | Node | HTMLElement): boolean; checkUserClickInNode(element: Element | Node | HTMLElement) { let UtilsContext = this; if (!UtilsContext.isDOM(element)) { throw new Error( "Utils.checkUserClickInNode 参数 targetNode 必须为 Element|Node 类型" ); } let clickEvent = UtilsContext.windowApi.window.event as PointerEvent; let touchEvent = UtilsContext.windowApi.window.event as TouchEvent; let $click = clickEvent?.composedPath()?.[0] as HTMLElement; // 点击的x坐标 let clickPosX = clickEvent?.clientX != null ? clickEvent.clientX : touchEvent.touches[0].clientX; // 点击的y坐标 let clickPosY = clickEvent?.clientY != null ? clickEvent.clientY : touchEvent.touches[0].clientY; let { /* 要检测的元素的相对屏幕的横坐标最左边 */ left: elementPosXLeft, /* 要检测的元素的相对屏幕的横坐标最右边 */ right: elementPosXRight, /* 要检测的元素的相对屏幕的纵坐标最上边 */ top: elementPosYTop, /* 要检测的元素的相对屏幕的纵坐标最下边 */ bottom: elementPosYBottom, } = (element as HTMLElement).getBoundingClientRect(); if ( clickPosX >= elementPosXLeft && clickPosX <= elementPosXRight && clickPosY >= elementPosYTop && clickPosY <= elementPosYBottom ) { return true; } else if (($click && element.contains($click)) || $click == element) { /* 这种情况是应对在界面中隐藏的元素,getBoundingClientRect获取的都是0 */ return true; } else { return false; } } /** * 复制formData数据 * @param formData 需要clone的数据 */ cloneFormData<T extends FormData>( formData: T, filterFn?: (key: string, value: string | Blob) => boolean ): T { let clonedFormData = new FormData() as T; for (let [key, value] of (formData as any).entries()) { let isFilter = typeof filterFn === "function" ? filterFn(key, value) : false; if (typeof isFilter === "boolean" && isFilter) { continue; } clonedFormData.append(key, value); } return clonedFormData; } /** * 函数重载实现 * @example * let getUsers = Utils.createOverload(); * getUsers.addImpl("",()=>{ * console.log("无参数"); * }); * * getUsers.addImpl("boolean",()=>{ * console.log("boolean"); * }); * * getUsers.addImpl("string",()=>{ * console.log("string"); * }); * * getUsers.addImpl("number","string",()=>{ * console.log("number string"); * }); */ createOverload(): { /** * 前面的参数都是字符串,最后一个参数是函数 */ addImpl<T extends JSTypeNames[]>( ...args: [...T, (...args: ArgsType<T>) => any] ): void; }; createOverload(): { /** * 前面的参数都是字符串,最后一个参数是函数 */ addImpl<T extends JSTypeNames[]>( ...args: [...T, (...args: ArgsType<T>) => any] ): void; } { let fnMap = new Map(); function overload(this: any, ...args: any[]) { let key = args.map((it) => typeof it).join(","); let fn = fnMap.get(key); if (!fn) { throw new TypeError("没有找到对应的实现"); } return fn.apply(this, args); } overload.addImpl = function (...args: any[]) { let fn = args.pop(); if (typeof fn !== "function") { throw new TypeError("最后一个参数必须是函数"); } let key = args.join(","); fnMap.set(key, fn); }; return overload; } /** * 颜色转换 * @returns */ ColorConversion = ColorConversion; /** * 深拷贝 * @param obj 对象 */ deepClone<T extends object | undefined | null>(obj?: T): T; deepClone<T extends object | undefined | null>(obj?: T) { let UtilsContext = this; if (obj === void 0) return void 0; if (obj === null) return null; let clone = obj instanceof Array ? [] : {}; for (const [key, value] of Object.entries(obj)) { (clone as any)[key] = typeof value === "object" ? UtilsContext.deepClone(value) : value; } return clone; } /** * 防抖函数 * @param fn 需要触发的回调 * @param delay 防抖判定时间(毫秒),默认是0ms */ debounce<A extends any[], R>( fn: (...args: A) => R, delay?: number ): (...args: A) => void; debounce<A extends any[], R>(fn: (...args: A) => R, delay = 0) { let timer: any = null as any; const context = this; return function (...args: A) { clearTimeout(timer); timer = setTimeout(function () { fn.apply(context, args); }, delay); }; } /** * 删除某个父元素,父元素可能在上层或上上层或上上上层... * @param element 当前元素 * @param targetSelector 判断是否满足父元素,参数为当前处理的父元素,满足返回true,否则false * @returns * + true 已删除 * + false 未删除 * @example * Utils.deleteParentNode(document.querySelector("a"),".xxx"); * > true **/ deleteParentNode( element: Node | HTMLElement | Element | null, targetSelector: string ): boolean; deleteParentNode( element: Node | HTMLElement | Element | null, targetSelector: string ) { let UtilsContext = this; if (element == null) { return; } if (!UtilsContext.isDOM(element)) { throw new Error( "Utils.deleteParentNode 参数 target 必须为 Node|HTMLElement 类型" ); } if (typeof targetSelector !== "string") { throw new Error( "Utils.deleteParentNode 参数 targetSelector 必须为 string 类型" ); } let result = false; let needRemoveDOM = (element as HTMLElement).closest(targetSelector); if (needRemoveDOM) { needRemoveDOM.remove(); result = true; } return result; } /** * 字典 * @example * let dictionary = new Utils.Dictionary(); * let dictionary2 = new Utils.Dictionary(); * dictionary.set("test","111"); * dictionary.get("test"); * > '111' * dictionary.has("test"); * > true * dictionary.concat(dictionary2); **/ Dictionary = UtilsDictionary; /** * 主动触发事件 * @param element 元素 * @param eventName 事件名称,可以是字符串,也可是字符串格式的列表 * @param details (可选)赋予触发的Event的额外属性 * + true 使用Proxy代理Event并设置获取isTrusted永远为True * + false (默认) 不对Event进行Proxy代理 * @example * Utils.dispatchEvent(document.querySelector("input","input")) */ dispatchEvent( element: HTMLElement | Document, eventName: DOMUtils_EventType | DOMUtils_EventType[], details?: any ): void; /** * 主动触发事件 * @param element 元素 * @param eventName 事件名称,可以是字符串,也可是字符串格式的列表 * @param details (可选)赋予触发的Event的额外属性 * + true 使用Proxy代理Event并设置获取isTrusted永远为True * + false (默认) 不对Event进行Proxy代理 * @example * Utils.dispatchEvent(document.querySelector("input","input")) */ dispatchEvent( element: HTMLElement | Document, eventName: string, details?: any ): void; dispatchEvent( element: HTMLElement | Document, eventName: DOMUtils_EventType | DOMUtils_EventType[] | string, details?: any ) { let eventNameList: string[] = []; if (typeof eventName === "string") { eventNameList = [eventName]; } if (Array.isArray(eventName)) { eventNameList = [...eventName]; } eventNameList.forEach((_eventName_) => { let event = new Event(_eventName_); if (details) { Object.assign(event, details); } element.dispatchEvent(event); }); } /** * 下载base64格式的数据 * @param base64Data 需要转换的base64数据 * @param fileName 需要保存的文件名 * @param isIFrame (可选)是否使用iframe进行下载 * @example * Utils.downloadBase64("data:image/jpeg:base64/,xxxxxx"); **/ downloadBase64( base64Data: string, fileName: string, isIFrame?: boolean ): void; downloadBase64(base64Data: string, fileName: string, isIFrame = false) { if (typeof base64Data !== "string") { throw new Error( "Utils.downloadBase64 参数 base64Data 必须为 string 类型" ); } if (typeof fileName !== "string") { throw new Error("Utils.downloadBase64 参数 fileName 必须为 string 类型"); } if (isIFrame) { /* 使用iframe */ const iframeElement = this.windowApi.document.createElement("iframe"); iframeElement.style.display = "none"; iframeElement.src = base64Data; this.windowApi.document.body.appendChild(iframeElement); setTimeout(() => { iframeElement!.contentWindow!.document.execCommand( "SaveAs", true, fileName ); this.windowApi.document.body.removeChild(iframeElement); }, 100); } else { /* 使用A标签 */ const linkElement = this.windowApi.document.createElement("a"); linkElement.setAttribute("target", "_blank"); linkElement.download = fileName; linkElement.href = base64Data; linkElement.click(); } } /** * 选中页面中的文字,类似Ctrl+F的选中 * @param str (可选)需要寻找的字符串,默认为空 * @param caseSensitive(可选)默认false * + true 区分大小写 * + false (默认) 不区分大小写 * @returns * + true 找到 * + false 未找到 * + undefined 不可使用该Api * @example * Utils.findWebPageVisibleText("xxxxx"); * > true **/ findWebPageVisibleText(str?: string, caseSensitive?: boolean): boolean | void; findWebPageVisibleText(str = "", caseSensitive = false) { let TRange = null; let strFound; if ((this.windowApi.globalThis as any).find) { /* CODE FOR BROWSERS THAT SUPPORT window.find */ let windowFind = (this.windowApi.self as any).find; strFound = windowFind(str, caseSensitive, true, true, false); if ( strFound && this.windowApi.self.getSelection && !this.windowApi.self.getSelection()!.anchorNode ) { strFound = windowFind(str, caseSensitive, true, true, false); } if (!strFound) { strFound = windowFind(str, 0, 1); while (windowFind(str, 0, 1)) continue; } } else if (navigator.appName.indexOf("Microsoft") != -1) { /* EXPLORER-SPECIFIC CODE */ if (TRange != null) { TRange = TRange as any; TRange.collapse(false); strFound = TRange.findText(str); if (strFound) TRange.select(); } if (TRange == null || strFound == 0) { TRange = (this.windowApi.self.document.body as any).createTextRange(); strFound = TRange.findText(str); if (strFound) TRange.select(); } } else if (navigator.appName == "Opera") { alert("Opera browsers not supported, sorry..."); return; } return strFound ? true : false; } /** * 定位元素上的字符串,返回一个迭代器 * @param element 目标元素 * @param text 需要定位的字符串 * @param filter (可选)过滤器函数,返回值为true是排除该元素 * @example * let textIterator = Utils.findElementsWithText(document.documentElement,"xxxx"); * textIterator.next(); * > {value: ?HTMLElement, done: boolean, next: Function} */ findElementsWithText<T extends HTMLElement | Element | Node>( element: T, text: string, filter?: (element: T) => boolean ): Generator<HTMLElement | ChildNode, void, any>; *findElementsWithText<T extends HTMLElement | Element | Node>( element: T, text: string, filter?: (element: T) => boolean ) { let that = this; if ((element as HTMLElement).outerHTML.includes(text)) { if ((element as HTMLElement).children.length === 0) { let filterResult = typeof filter === "function" ? filter(element) : false; if (!filterResult) { yield element as any; } } else { let textElement = Array.from(element.childNodes).filter( (ele) => ele.nodeType === Node.TEXT_NODE ); for (let ele of textElement) { if ((ele as any).textContent.includes(text)) { let filterResult = typeof filter === "function" ? filter(element) : false; if (!filterResult) { yield ele; } } } } } for ( let index = 0; index < (element as HTMLElement).children.length; index++ ) { let childElement = (element as HTMLElement).children[index] as any; yield* that.findElementsWithText(childElement, text, filter); } } /** * 判断该元素是否可见,如果不可见,向上找它的父元素直至找到可见的元素 * @param element * @example * let visibleElement = Utils.findVisibleElement(document.querySelector("a.xx")); * > <HTMLElement> */ findVisibleElement(element: HTMLElement | Element | Node) { let currentElement = element as HTMLElement; while (currentElement) { let elementRect = currentElement.getBoundingClientRect(); if (Boolean((elementRect as any).length)) { return currentElement; } currentElement = currentElement.parentElement as any; } return null; } /** * 格式化byte为KB、MB、GB、TB、PB、EB、ZB、YB、BB、NB、DB * @param byteSize 字节 * @param addType (可选)是否添加单位 * + true (默认) 添加单位 * + false 不添加单位 * @returns * + {string} 当addType为true时,且保留小数点末尾2位 * + {number} 当addType为false时,且保留小数点末尾2位 * @example * Utils.formatByteToSize("812304"); * > '793.27KB' * @example * Utils.formatByteToSize("812304",false); * > 793.27 **/ formatByteToSize(byteSize: number | string): number; formatByteToSize<T extends boolean>( byteSize: number | string, addType?: T ): T extends true ? string : number; formatByteToSize(byteSize: number | string, addType = true) { byteSize = parseInt(byteSize.toString()); if (isNaN(byteSize)) { throw new Error("Utils.formatByteToSize 参数 byteSize 格式不正确"); } let result = 0; let resultType = "KB"; let sizeData: UtilsOwnObject<number> = {}; sizeData.B = 1; sizeData.KB = 1024; sizeData.MB = sizeData.KB * sizeData.KB; sizeData.GB = sizeData.MB * sizeData.KB; sizeData.TB = sizeData.GB * sizeData.KB; sizeData.PB = sizeData.TB * sizeData.KB; sizeData.EB = sizeData.PB * sizeData.KB; sizeData.ZB = sizeData.EB * sizeData.KB; sizeData.YB = sizeData.ZB * sizeData.KB; sizeData.BB = sizeData.YB * sizeData.KB; sizeData.NB = sizeData.BB * sizeData.KB; sizeData.DB = sizeData.NB * sizeData.KB; for (let key in sizeData) { result = byteSize / (sizeData as any)[key]; resultType = key; if (sizeData.KB >= result) { break; } } result = result.toFixed(2) as any; result = addType ? result + resultType.toString() : (parseFloat(result.toString()) as any); return result; } /** * 应用场景: 当你想要获取数组形式的元素时,它可能是其它的选择器,那么需要按照先后顺序填入参数 * 第一个是优先级最高的,依次下降,如果都没有,返回空列表 * 支持document.querySelectorAll、$("")、()=>{return document.querySelectorAll("")} * @param NodeList * @example * Utils.getNodeListValue( * document.querySelectorAll("div.xxx"), * document.querySelectorAll("a.xxx") * ); * > [...div,div,div] * @example * Utils.getNodeListValue(divGetFunction,aGetFunction); * > [...div,div,div] */ getNodeListValue(...args: (NodeList | (() => HTMLElement))[]): HTMLElement[]; getNodeListValue(...args: (NodeList | (() => HTMLElement))[]) { let resultArray: HTMLElement[] = []; for (let arg of args) { let value = arg as any; if (typeof arg === "function") { /* 方法 */ value = arg(); } if (value.length !== 0) { resultArray = [...value]; break; } } return resultArray; } /** * 自动判断N个参数,获取非空的值,如果都是空,返回最后一个值 */ getNonNullValue(...args: any[]): any; getNonNullValue(...args: any[]) { let resultValue = args[args.length - 1]; let UtilsContext = this; for (const argValue of args) { if (UtilsContext.isNotNull(argValue)) { resultValue = argValue; break; } } return resultValue; } /** * 获取格式化后的时间 * @param text (可选)需要格式化的字符串或者时间戳,默认:new Date() * @param formatType (可选)格式化成的显示类型,默认:yyyy-MM-dd HH:mm:ss * + yyyy 年 * + MM 月 * + dd 天 * + HH 时 (24小时制) * + hh 时 (12小时制) * + mm 分 * + ss 秒 * @returns {string} 返回格式化后的时间 * @example * Utils.formatTime("2022-08-21 23:59:00","HH:mm:ss"); * > '23:59:00' * @example * Utils.formatTime(1899187424988,"HH:mm:ss"); * > '15:10:13' * @example * Utils.formatTime() * > '2023-1-1 00:00:00' **/ formatTime(text?: string | number | Date, formatType?: string): string; /** * 获取格式化后的时间 * @param text (可选)需要格式化的字符串或者时间戳,默认:new Date() * @param formatType (可选)格式化成的显示类型,默认:yyyy-MM-dd HH:mm:ss * + yyyy 年 * + MM 月 * + dd 天 * + HH 时 (24小时制) * + hh 时 (12小时制) * + mm 分 * + ss 秒 * @returns {string} 返回格式化后的时间 * @example * Utils.formatTime("2022-08-21 23:59:00","HH:mm:ss"); * > '23:59:00' * @example * Utils.formatTime(1899187424988,"HH:mm:ss"); * > '15:10:13' * @example * Utils.formatTime() * > '2023-1-1 00:00:00' **/ formatTime( text?: string | number | Date, formatType?: | "yyyy-MM-dd HH:mm:ss" | "yyyy/MM/dd HH:mm:ss" | "yyyy_MM_dd_HH_mm_ss" | "yyyy年MM月dd日 HH时mm分ss秒" | "yyyy年MM月dd日 hh:mm:ss" | "yyyy年MM月dd日 HH:mm:ss" | "yyyy-MM-dd" | "yyyyMMdd" | "HH:mm:ss" | "yyyy" | "MM" | "dd" | "HH" | "mm" | "ss" ): string; formatTime(text = new Date(), formatType = "yyyy-MM-dd HH:mm:ss") { let time = text == null ? new Date() : new Date(text); /** * 校验时间补0 * @param timeNum * @returns */ function checkTime(timeNum: number) { if (timeNum < 10) return "0" + timeNum; return timeNum; } /** * 时间制修改 24小时制转12小时制 * @param hourNum 小时 * @returns */ function timeSystemChange(hourNum: number) { return hourNum > 12 ? hourNum - 12 : hourNum; } let timeRegexp = { yyyy: time.getFullYear(), /* 年 */ MM: checkTime(time.getMonth() + 1), /* 月 */ dd: checkTime(time.getDate()), /* 日 */ HH: checkTime(time.getHours()), /* 时 (24小时制) */ hh: checkTime(timeSystemChange(time.getHours())), /* 时 (12小时制) */ mm: checkTime(time.getMinutes()), /* 分 */ ss: checkTime(time.getSeconds()), /* 秒 */ }; Object.keys(timeRegexp).forEach(function (key) { let replaecRegexp = new RegExp(key, "g"); formatType = formatType.replace(replaecRegexp, (timeRegexp as any)[key]); }); return formatType; } /** * 字符串格式的时间转时间戳 * @param text 字符串格式的时间,例如: * + 2022-11-21 00:00:00 * + 00:00:00 * @returns 返回时间戳 * @example * Utils.formatToTimeStamp("2022-11-21 00:00:00"); * > 1668960000000 **/ formatToTimeStamp(text: string): number; formatToTimeStamp(text: string) { /* 把字符串格式的时间(完整,包括日期和时间)格式化成时间 */ if (typeof text !== "string") { throw new Error("Utils.formatToTimeStamp 参数 text 必须为 string 类型"); } if (text.length === 8) { /* 该字符串只有时分秒 */ let today = new Date(); text = today.getFullYear() + "-" + (today.getMonth() + 1) + "-" + today.getDate() + " " + text; } text = text.substring(0, 19); text = text.replace(/-/g, "/"); let timestamp = new Date(text).getTime(); return timestamp; } /** * gbk格式的url编码,来自https://greasyfork.org/zh-CN/scripts/427726-gbk-url-js * @example * let gbkEncoder = new Utils.GBKEncoder(); * gbkEncoder.encode("测试"); * > '%B2%E2%CA%D4' * gbkEncoder.decode("%B2%E2%CA%D4"); * > 测试 */ GBKEncoder = GBKEncoder; /** * 获取 transitionend 的在各个浏览器的兼容名 */ getTransitionEndNameList() { return [ "webkitTransitionEnd", "mozTransitionEnd", "MSTransitionEnd", "otransitionend", "transitionend", ]; } /** * 获取 animationend 的在各个浏览器的兼容名 */ getAnimationEndNameList() { return [ "webkitAnimationEnd", "mozAnimationEnd", "MSAnimationEnd", "oanimationend", "animationend", ]; } /** * 获取NodeList或Array对象中的最后一个的值 * @param targetObj * @returns * @example * Utils.getArrayLastValue(document.querySelectorAll("div")); * > div * @example * Utils.getArrayLastValue([1,2,3,4,5]); * > 5 */ getArrayLastValue<R extends any>(targetObj: NodeList | any[]): R; getArrayLastValue(targetObj: NodeList | any[]) { return targetObj[targetObj.length - 1]; } /** * 应用场景: 当想获取的元素可能是不同的选择器的时候,按顺序优先级获取 * 参数类型可以是Element或者是Function * @returns 如果都没有的话,返回null * @example * // 如果a.aaa不存在的话,取a.bbb,这里假设a.aaa不存在 * Utils.getArrayRealValue(document.querySelector("a.aaa"),document.querySelector("a.bbb")); * > a.bbb * @example * Utils.getArrayRealValue(()=>{return document.querySelector("a.aaa").href},()=>{document.querySelector("a.bbb").getAttribute("data-href")}); * > javascript:; */ getArrayRealValue(...args: (NodeList | (() => HTMLElement))[]): any; getArrayRealValue(...args: (NodeList | (() => HTMLElement))[]) { let result = null; for (let arg of args) { if (typeof arg === "function") { /* 方法 */ (arg as any) = arg(); } if (arg != null) { result = arg; break; } } return result; } /** * 获取天数差异,如何获取某个时间与另一个时间相差的天数 * @param timestamp1 (可选)时间戳(毫秒|秒),不区分哪个更大,默认为:Date.now() * @param timestamp2 (可选)时间戳(毫秒|秒),不区分哪个更大,默认为:Date.now() * @param type (可选)返回的数字的表达的类型,比如:年、月、天、时、分、秒、auto,默认天 * @example * Utils.getDaysDifference(new Date().getTime()); * > 0 * @example * Utils.getDaysDifference(new Date().getTime(),undefined,"秒"); * > 0 */ getDaysDifference( timestamp1?: number, timestamp2?: number, type?: "auto" ): string; /** * 获取天数差异,如何获取某个时间与另一个时间相差的天数 * @param timestamp1 (可选)时间戳(毫秒|秒),不区分哪个更大,默认为:Date.now() * @param timestamp2 (可选)时间戳(毫秒|秒),不区分哪个更大,默认为:Date.now() * @param type (可选)返回的数字的表达的类型,比如:年、月、天、时、分、秒、auto,默认天 * @example * Utils.getDaysDifference(new Date().getTime()); * > 0 * @example * Utils.getDaysDifference(new Date().getTime(),undefined,"秒"); * > 0 */ getDaysDifference( timestamp1?: number, timestamp2?: number, type?: "年" | "月" | "天" | "时" | "分" | "秒" ): number; getDaysDifference( timestamp1 = Date.now(), timestamp2 = Date.now(), type = "天" ): number | string { type = type.trim(); if (timestamp1.toString().length === 10) { timestamp1 = timestamp1 * 1000; } if (timestamp2.toString().length === 10) { timestamp2 = timestamp2 * 1000; } let smallTimeStamp = timestamp1 > timestamp2 ? timestamp2 : timestamp1; let bigTimeStamp = timestamp1 > timestamp2 ? timestamp1 : timestamp2; let oneSecond = 1000; /* 一秒的毫秒数 */ let oneMinute = 60 * oneSecond; /* 一分钟的毫秒数 */ let oneHour = 60 * oneMinute; /* 一小时的毫秒数 */ let oneDay = 24 * oneHour; /* 一天的毫秒数 */ let oneMonth = 30 * oneDay; /* 一个月的毫秒数(30天) */ let oneYear = 12 * oneMonth; /* 一年的毫秒数 */ let bigDate = new Date(bigTimeStamp); let smallDate = new Date(smallTimeStamp); let remainderValue = 1; if (type === "年") { remainderValue = oneYear; } else if (type === "月") { remainderValue = oneMonth; } else if (type === "天") { remainderValue = oneDay; } else if (type === "时") { remainderValue = oneHour; } else if (type === "分") { remainderValue = oneMinute; } else if (type === "秒") { remainderValue = oneSecond; } let diffValue = Math.round( Math.abs(((bigDate as any) - (smallDate as any)) / remainderValue) ); if (type === "auto") { let timeDifference = bigTimeStamp - smallTimeStamp; diffValue = Math.floor(timeDifference / (24 * 3600 * 1000)); if (diffValue > 0) { (diffValue as any) = diffValue + "天"; } else { /* 计算出小时数 */ let leave1 = timeDifference % (24 * 3600 * 1000); /* 计算天数后剩余的毫秒数 */ let hours = Math.floor(leave1 / (3600 * 1000)); if (hours > 0) { (diffValue as any) = hours + "小时"; } else { /* 计算相差分钟数 */ let leave2 = leave1 % (3600 * 1000); /* 计算小时数后剩余的毫秒数 */ let minutes = Math.floor(leave2 / (60 * 1000)); if (minutes > 0) { (diffValue as any) = minutes + "分钟"; } else { /* 计算相差秒数 */ let leave3 = leave2 % (60 * 1000); /* 计算分钟数后剩余的毫秒数 */ let seconds = Math.round(leave3 / 1000); (diffValue as any) = seconds + "秒"; } } } } return diffValue; } /** * 获取元素的选择器字符串 * @param element * @example * Utils.getElementSelector(document.querySelector("a")) * > '.....' */ getElementSelector(element: HTMLElement): string; getElementSelector(element: HTMLElement): string { let UtilsContext = this; // @ts-ignore if (!element) return; // @ts-ignore if (!element.parentElement) return; /* 如果元素有id属性,则直接返回id选择器 */ if (element.id) return "#" + element.id; /* 递归地获取父元素的选择器 */ let selector = UtilsContext.getElementSelector(element.parentElement); if (!selector) { return element.tagName.toLowerCase(); } /* 如果有多个相同类型的兄弟元素,则需要添加索引 */ if (element.parentElement.querySelectorAll(element.tagName).length > 1) { let index = Array.prototype.indexOf.call(element.parentElement.children, element) + 1; selector += " > " + element.tagName.toLowerCase() + ":nth-child(" + index + ")"; } else { selector += " > " + element.tagName.toLowerCase(); } return selector; } /** * 获取最大值 * @example * Utils.getMaxValue(1,3,5,7,9) * > 9 */ getMaxValue(...args: number[]): number; /** * 获取最大值 * @example * Utils.getMaxValue([1,3,5]) * > 5 */ getMaxValue(val: number[]): number; /** * 获取最大值 * @example * Utils.getMaxValue({1:123,2:345,3:456},(key,value)=>{return parseInt(value)}) * > 456 */ getMaxValue( val: UtilsOwnObject<number>, handler: (key: any, value: any) => number ): number; /** * 获取最大值 * @example * Utils.getMaxValue([{1:123},{2:345},{3:456}],(index,value)=>{return parseInt(index)}) * > 2 */ getMaxValue(...args: any[]): number { let result = [...args]; let newResult: number[] = []; if (result.length === 0) { // @ts-ignore return; } if (result.length > 1) { if ( result.length === 2 && typeof result[0] === "object" && typeof result[1] === "function" ) { let data = result[0]; let handleDataFunc = result[1]; Object.keys(data).forEach((keyName) => { newResult = [...newResult, handleDataFunc(keyName, data[keyName])]; }); } else { result.forEach((item) => { if (!isNaN(parseFloat(item))) { newResult = [...newResult, parseFloat(item)]; } }); } return Math.max(...newResult); } else { result[0].forEach((item: any) => { if (!isNaN(parseFloat(item))) { newResult = [...newResult, parseFloat(item)]; } }); return Math.max(...newResult); } } /** * 获取页面中最大的z-index的元素信息 * @param deviation 获取最大的z-index值的偏移,默认是1 * @param node 进行判断的元素,默认是document * @param ignoreCallBack 执行元素处理时调用的函数,返回false可忽略不想要处理的元素 * @example * Utils.getMaxZIndexNodeInfo(); * > { * node: ..., * zIndex: 1001 * } **/ getMaxZIndexNodeInfo( deviation?: number, target?: Element | ShadowRoot | Document, ignoreCallBack?: ( $ele: Element | HTMLElement | ShadowRoot ) => boolean | void ): { node: Element; zIndex: number; }; getMaxZIndexNodeInfo( deviation = 1, target: Element | ShadowRoot | Document = this.windowApi.document, ignoreCallBack?: ( $ele: Element | HTMLElement | ShadowRoot ) => boolean | void ): { node: Element; zIndex: number; } { deviation = Number.isNaN(deviation) ? 1 : deviation; const UtilsContext = this; // 最大值 2147483647 // const maxZIndex = Math.pow(2, 31) - 1; // 比较值 2000000000 const maxZIndexCompare = 2 * Math.pow(10, 9); // 当前页面最大的z-index let zIndex = 0; // 当前的最大z-index的元素,调试使用 // @ts-ignore let maxZIndexNode: Element = null; /** * 元素是否可见 * @param $css */ function isVisibleNode($css: CSSStyleDeclaration): boolean { return $css.position !== "static" && $css.display !== "none"; } /** * 查询元素的z-index * 并比较值是否是已获取的最大值 * @param $ele */ function queryMaxZIndex($ele: Element) { if (typeof ignoreCallBack === "function") { let ignoreResult = ignoreCallBack($ele); if (typeof ignoreResult === "boolean" && !ignoreResult) { return; } } /** 元素的样式 */ const nodeStyle = UtilsContext.windowApi.window.getComputedStyle($ele); /* 不对position为static和display为none的元素进行获取它们的z-index */ if (isVisibleNode(nodeStyle)) { let nodeZIndex = parseInt(nodeStyle.zIndex); if (!isNaN(nodeZIndex)) { if (nodeZIndex > zIndex) { // 赋值到全局 zIndex = nodeZIndex; maxZIndexNode = $ele; } } // 判断shadowRoot if ($ele.shadowRoot != null && $ele instanceof ShadowRoot) { $ele.shadowRoot.querySelectorAll("*").forEach(($shadowEle) => { queryMaxZIndex($shadowEle); }); } } } target.querySelectorAll("*").forEach(($ele, index) => { queryMaxZIndex($ele); }); zIndex += deviation; if (zIndex >= maxZIndexCompare) { // 最好不要超过最大值 zIndex = maxZIndexCompare; } return { node: maxZIndexNode, zIndex: zIndex, }; } /** * 获取页面中最大的z-index * @param deviation 获取最大的z-index值的偏移,默认是1 * @param node 进行判断的元素,默认是document * @param ignoreCallBack 执行元素处理时调用的函数,返回false可忽略不想要处理的元素 * @example * Utils.getMaxZIndex(); * > 1001 **/ getMaxZIndex( deviation?: number, target?: Element | DocumentOrShadowRoot | Document, ignoreCallBack?: ( $ele: Element | HTMLElement | ShadowRoot ) => boolean | void ): number; getMaxZIndex( deviation = 1, target: Element | ShadowRoot | Document = this.windowApi.document, ignoreCallBack?: ( $ele: Element | HTMLElement | ShadowRoot ) => boolean | void ): number { return this.getMaxZIndexNodeInfo(deviation, target, ignoreCallBack).zIndex; } /** * 获取最小值 * @example * Utils.getMinValue(1,3,5,7,9) * > 1 */ getMinValue(...args: number[]): number; /** * 获取最小值 * @example * Utils.getMinValue([1,3,5]) * > 1 */ getMinValue(val: number[]): number; /** * 获取最小值 * @example * Utils.getMinValue({1:123,2:345,3:456},(key,value)=>{return parseInt(value)}) * > 123 */ getMinValue( val: UtilsOwnObject<number>, handler: (key: any, value: any) => number ): number; /** * 获取最小值 * @example * Utils.getMinValue([{1:123},{2:345},{3:456}],(index,value)=>{return parseInt(index)}) * > 0 */ getMinValue( val: UtilsOwnObject<number>[], handler: (index: number, value: any) => number ): number; getMinValue(...args: any[]): number { let result = [...args]; let newResult: number[] = []; if (result.length === 0) { // @ts-ignore return; } if (result.length > 1) { if ( result.length === 2 && typeof result[0] === "object" && typeof result[1] === "function" ) { let data = result[0]; let handleDataFunc = result[1]; Object.keys(data).forEach((keyName) => { newResult = [...newResult, handleDataFunc(keyName, data[keyName])]; }); } else { result.forEach((item) => { if (!isNaN(parseFloat(item))) { newResult = [...newResult, parseFloat(item)]; } }); } return Math.min(...newResult); } else { result[0].forEach((item: any) => { if (!isNaN(parseFloat(item))) { newResult = [...newResult, parseFloat(item)]; } }); return Math.min(...newResult); } } /** * 获取随机的安卓手机User-Agent * @example * Utils.getRandomAndroidUA(); * > 'Mozilla/5.0 (Linux; Android 10; MI 13 Build/OPR1.170623.027; wv) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.3490.40 Mobile Safari/537.36' **/ getRandomAndroidUA(): string { let UtilsContext = this; let mobileNameList = [ "LDN-LX3", "RNE-L03", "ASUS_X00ID Build/NMF26F", "WAS-LX3", "PRA-LX3", "MYA-L03", "Moto G Play", "Moto C Build/NRD90M.063", "Redmi Note 4 Build/NRD90M", "HUAWEI VNS-L21 Build/HUAWEIVNS-L21", "VTR-L09", "TRT-LX3", "M2003J15SC Build/RP1A.200720.011; wv", "MI 13 Build/OPR1.170623.027; wv", ]; /* 安卓版本 */ let androidVersion = UtilsContext.getRandomValue(12, 14); /* 手机型号 */ let randomMobile = UtilsContext.getRandomValue(mobileNameList); /* chrome大版本号 */ let chromeVersion1 = UtilsContext.getRandomValue(120, 132); let chromeVersion2 = UtilsContext.getRandomValue(0, 0); let chromeVersion3 = UtilsContext.getRandomValue(2272, 6099); let chromeVersion4 = UtilsContext.getRandomValue(1, 218); return `Mozilla/5.0 (Linux; Android ${androidVersion}; ${randomMobile}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion1}.${chromeVersion2}.${chromeVersion3}.${chromeVersion4} Mobile Safari/537.36`; } /** * 获取随机的电脑端User-Agent * + Mozilla/5.0:以前用于Netscape浏览器,目前大多数浏览器UA都会带有 * + Windows NT 13:代表Window11系统 * + Windows NT 10.0:代表Window10系统 * + Windows NT 6.1:代表windows7系统 * + WOW64:Windows-on-Windows 64-bit,32位的应用程序运行于此64位处理器上 * + Win64:64位 * + AppleWebKit/537.36:浏览器内核 * + KHTML:HTML排版引擎 * + like Gecko:这不是Geckeo 浏览器,但是运行起来像Geckeo浏览器 * + Chrome/106.0.5068.19:Chrome版本号 * + Safari/537.36:宣称自己是Safari? * @returns 返回随机字符串 * @example * Utils.getRandomPCUA(); * > 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.5068.19 Safari/537.36' **/ getRandomPCUA(): string { let UtilsContext = this; /* chrome大版本号 */ let chromeVersion1 = UtilsContext.getRandomValue(120, 132); let chromeVersion2 = UtilsContext.getRandomValue(0, 0); let chromeVersion3 = UtilsContext.getRandomValue(2272, 6099); let chromeVersion4 = UtilsContext.getRandomValue(1, 218); return `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${chromeVersion1}.${chromeVersion2}.${chromeVersion3}.${chromeVersion4} Safari/537.36`; } /** * 获取随机值 * @example * Utils.getRandomValue(1,9,6,99) * > 6 */ getRandomValue<T extends any>(...args: T[]): T; /** * 获取随机值 * @example * Utils.getRandomValue([1,2,3]) * > 3 * @example * Utils.getRandomValue({1:"结果1",2:"结果2",3:"结果3"}}) * > 结果2 */ getRandomValue<T extends any>(val: T[] | UtilsOwnObject<T>): T; /** * 获取两个数之间随机值 * @example * Utils.getRandomValue(1,9) * > 6 */ getRandomValue(val_1: number, val_2: number): number; /** * 获取随机值 * @example * Utils.getRandomValue({1:1},{2:2}) * > {1: 1} */ getRandomValue<T extends any>( val_1: UtilsOwnObject<T>, val_2: UtilsOwnObject<T> ): T; getRandomValue(...args: any[]): any { let result = [...args]; if (result.length > 1) { if ( result.length === 2 && typeof result[0] === "number" && typeof result[1] === "number" ) { let leftNumber = result[0] > result[1] ? result[1] : result[0]; let rightNumber = result[0] > result[1] ? result[0] : result[1]; return ( Math.round(Math.random() * (rightNumber - leftNumber)) + leftNumber ); } else { return result[Math.floor(Math.random() * result.length)]; } } else if (result.length === 1) { let paramData = result[0]; if (Array.isArray(paramData)) { return paramData[Math.floor(Math.random() * paramData.length)]; } else if ( typeof paramData === "object" && Object.keys(paramData).length > 0 ) { let paramObjDataKey = Object.keys(paramData)[ Math.floor(Math.random() * Object.keys(paramData).length) ]; return paramData[paramObjDataKey]; } else { return paramData; } } } /** * 获取元素上的使用React框架的实例属性,目前包括reactFiber、reactProps、reactEvents、reactEventHandlers、reactInternalInstance * @param element 需要获取的目标元素 * @returns * @example * Utils.getReactObj(document.querySelector("input"))?.reactProps?.onChange({target:{value:"123"}}); */ getReactObj(element: HTMLElement | Element): { reactFiber?: any; reactProps?: any; reactEvents?: any; reactEventHandlers?: any; reactInternalInstance?: any; reactContainer?: any; } { let result = {}; Object.keys(element).forEach((domPropsName) => { if (domPropsName.startsWith("__react")) { let propsName = domPropsName.replace(/__(.+)\$.+/i, "$1"); if (propsName in result) { new Error("重复属性 " + domPropsName); } else { (result as any)[propsName] = (element as any)[domPropsName]; } } }); return result; } /** * 获取对象上的Symbol属性,如果没设置keyName,那么返回一个对象,对象是所有遍历到的Symbol对象 * @param target 目标对象 * @param keyName (可选)Symbol名或者Symbol对象 */ getSymbol(target: any, keyName?: string | symbol) { if (typeof target !== "object") { throw new TypeError("target不是一个对象"); } let objectsSymbols = Object.getOwnPropertySymbols(target); if (typeof keyName === "string") { let findSymbol = objectsSymbols.find((key) => { return key.toString() === keyName; }); if (findSymbol) { return target[findSymbol]; } } else if (typeof keyName === "symbol") { let findSymbol = objectsSymbols.find((key) => { return key === keyName; }); if (findSymbol) { return (target as any)[findSymbol]; } } else { let result = {}; objectsSymbols.forEach((item) => { (result as any)[item] = target[item]; }); return result; } } /** * 获取文本的字符长度 * @param text * @example * Utils.getTextLength("测试文本") * > 12 */ getTextLength(text: string): number { let encoder = new TextEncoder(); let bytes = encoder.encode(text); return bytes.length; } /** * 获取文本占据的空间大小,返回自动的单位,如12 Kb,14 K,20 MB,1 GB * @param text 目标字符串 * @param addType (可选)是否添加单位 * + true (默认) 自动添加单位 * + false 不添加单位 * @example * Utils.getTextStorageSize("测试文本"); * > '12.00B' */ getTextStorageSize<T extends boolean>( text: string, addType?: T ): T extends true ? string : number; getTextStorageSize(text: string, addType = true) { let UtilsContext = this; return UtilsContext.formatByteToSize( UtilsContext.getTextLength(text), addType ); } /** * 获取迅雷协议的Url * @param url Url链接或者其它信息 */ getThunderUrl(url: string): string; getThunderUrl(url: string): string { if (url == null) { throw new TypeError("url不能为空"); } if (typeof url !== "string") { throw new TypeError("url必须是string类型"); } if (url.trim() === "") { throw new TypeError("url不能为空字符串或纯空格"); } return `thunder://${this.windowApi.globalThis.btoa("AA" + url + "ZZ")}`; } /** * 对于GM_cookie的兼容写法,当无法使用GM_cookie时可以使用这个,但是并不完全兼容,有些写不出来且限制了httponly是无法访问的 * @example let GM_cookie = new Utils.GM_Cookie(); GM_cookie.list({name:"xxx_cookie_xxx"},function(cookies,error){ if (!error) { console.log(cookies); console.log(cookies.value); } else { console.error(error); } }); GM_cookie.set({name:"xxx_cookie_test_xxx",value:"这是Cookie测试值"},function(error){ if (error) { console.error(error); } else { console.log('Cookie set successfully.'); } }) GM_cookie.delete({name:"xxx_cookie_test_xxx"},function(error){ if (error) { console.error(error); } else { console.log('Cookie set successfully.'); } }) **/ GM_Cookie = UtilsGMCookie; /** * 注册油猴菜单,要求本地存储的键名不能存在其它键名`GM_Menu_Local_Map`会冲突/覆盖 * @example let GM_Menu = new Utils.GM_Menu({ data: [ { menu_key: "menu_key", text: "测试按钮", enable: true, accessKey: "a", autoClose: false, showText(text, enable) { return "[" + (enable ? "√" : "×") + "]" + text; }, callback(data) { console.log("点击菜单,值修改为", data.enable); }, }, ], autoReload: false, GM_getValue, GM_setValue, GM_registerMenuCommand, GM_unregisterMenuCommand, }); // 获取某个菜单项的值 GM_Menu.get("menu_key"); > true // 获取某个菜单项的开启/关闭后显示的文本 GM_Menu.getShowTextValue("menu_key"); > √测试按钮 // 添加键为menu_key2的菜单项 GM_Menu.add({ key:"menu_key2", text: "测试按钮2", enable: false, showText(text,enable){ return "[" + (enable ? "√" : "×") + "]" + text; }, callback(data){ console.log("点击菜单,值修改为",data.enable); } }); // 使用数组的方式添加多个菜单,如menu_key3、menu_key4 GM_Menu.add([ { key:"menu_key3", text: "测试按钮3", enable: false, showText(text,enable){ return "[" + (enable ? "√" : "×") + "]" + text; }, callback(data){ console.log("点击菜单,值修改为",data.enable); } }, { key:"menu_key4", text: "测试按钮4", enable: false, showText(text,enable){ return "[" + (enable ? "√" : "×") + "]" + text; }, callback(data){ console.log("点击菜单,值修改为",data.enable); } } ]); // 更新键为menu_key的显示文字和点击回调 GM_Menu.update({ menu_key:{ text: "更新后的测试按钮", enable: true, showText(text,enable){ return "[" + (enable ? "√" : "×") + "]" + text; }, callback(data){ console.log("点击菜单更新后的测试按钮,新值修改为",data.enable); } } }); // 删除键为menu_key的菜单 GM_Menu.delete("menu_key"); **/ GM_Menu = GMMenu; /** * 基于Function prototype,能够勾住和释放任何函数 * * .hook * + realFunc {string} 用于保存原始函数的函数名称,用于unHook * + hookFunc {string} 替换的hook函数 * + context {object} 目标函数所在对象,用于hook非window对象下的函数,如String.protype.slice,carInstance1 * + methodName {string} 匿名函数需显式传入目标函数名eg:this.Begin = function(){....};} * * .unhook * + realFunc {string} 用于保存原始函数的函数名称,用于unHook * + funcName {string} 被Hook的函数名称 * + context {object} 目标函数所在对象,用于hook非window对象下的函数,如String.protype.slice,carInstance1 * @example let hook = new Utils.Hooks(); hook.initEnv(); function myFunction(){ console.log("我自己需要执行的函数"); } function testFunction(){ console.log("正常执行的函数"); } testFunction.hook(testFunction,myFunction,window); **/ Hooks = Hooks; /** * 为减少代码量和回调,把GM_xmlhttpRequest封装 * 文档地址: https://www.tampermonkey.net/documentation.php?ext=iikm * 其中onloadstart、onprogress、onreadystatechange是回调形式,onabort、ontimeout、onerror可以设置全局回调函数 * @param _GM_xmlHttpRequest_ 油猴中的GM_xmlhttpRequest * @example let httpx = new Utils.Httpx(GM_xmlhttpRequest); let postResp = await httpx.post({ url:url, data:JSON.stringify({ test:1 }), timeout: 5000