import UserScriptEngine from "../../enum/UserScriptEngine"; import WuhuBase from "../WuhuBase"; import Log from "../Log"; import Device from "../../enum/Device"; import AjaxFetchOption from "../../interface/AjaxFetchOption"; import LOADING_IMG_HTML from "../../static/html/loading_img.html"; import Timer from "./Timer"; import FetchUtils from "./FetchUtils"; import TornStyleSwitch from "./TornStyleSwitch"; import WuhuConfig from "../WuhuConfig"; import { MenuItemConfig } from "../ZhongIcon"; export default class CommonUtils extends WuhuBase { className = 'CommonUtils'; static getScriptEngine() { let glob = CommonUtils.glob; return glob.GM_xmlhttpRequest ? UserScriptEngine.GM : glob.isPDA ? UserScriptEngine.PDA : UserScriptEngine.RAW; } static COFetch(url: URL | string, method: 'get' | 'post' = 'get', body: any = null): Promise { const engine = this.getScriptEngine(); let start = performance.now(); Log.info('跨域获取数据开始, 脚本引擎: ' + engine); return new Promise((resolve, reject) => { switch (engine) { case UserScriptEngine.RAW: { Log.error(`跨域请求错误:${ UserScriptEngine.RAW }环境下无法进行跨域请求`); reject(`错误:${ UserScriptEngine.RAW }环境下无法进行跨域请求`); break; } case UserScriptEngine.PDA: { const { PDA_httpGet, PDA_httpPost } = window; // get if (method === 'get') { if (typeof PDA_httpGet !== 'function') { Log.error('COFetch网络错误:PDA版本不支持'); reject('COFetch网络错误:PDA版本不支持'); } PDA_httpGet(url) .then(res => { Log.info('跨域获取数据成功, 耗时' + (performance.now() - start | 0) + 'ms'); resolve(res.responseText); }) .catch(e => { Log.error('COFetch网络错误', e); reject(`COFetch网络错误 ${ e }`); }) } // post else { if (typeof PDA_httpPost !== 'function') { Log.error('COFetch网络错误:PDA版本不支持'); reject('COFetch网络错误:PDA版本不支持'); } PDA_httpPost(url, { 'content-type': 'application/json' }, body) .then(res => resolve(res.responseText)) .catch(e => { Log.error('COFetch网络错误', e); reject(`COFetch网络错误 ${ e }`); }); } break; } case UserScriptEngine.GM: { let { GM_xmlhttpRequest } = CommonUtils.glob; if (typeof GM_xmlhttpRequest !== 'function') { Log.error('COFetch网络错误:用户脚本扩展API错误'); reject('错误:用户脚本扩展API错误'); } GM_xmlhttpRequest({ method: method, url: url, data: method === 'get' ? null : body, headers: method === 'get' ? null : { 'content-type': 'application/json' }, onload: res => { Log.info('跨域获取数据成功,耗时' + (performance.now() - start | 0) + 'ms'); resolve(res.response); }, onerror: res => reject(`连接错误 ${ JSON.stringify(res) }`), ontimeout: res => reject(`连接超时 ${ JSON.stringify(res) }`), }); } } }); } // /** // * 返回玩家信息的对象 { playername: string, userID: number } // * @return {PlayerInfo} rs // */ // static getPlayerInfo(): PlayerInfo { // const node = document.querySelector('script[uid]'); // if (node) { // return { // playername: node.getAttribute('name'), // userID: node.getAttribute('uid') as unknown as number, // } // } else { // new Alert('严重错误:芜湖助手无法获取用户数据,已退出'); // throw '芜湖助手无法获取用户数据'; // } // } // 用户设备类型 对应PC MOBILE TABLET public static getDeviceType(): Device { return window.innerWidth >= 1000 ? Device.PC : window.innerWidth <= 600 ? Device.MOBILE : Device.TABLET; } public static getYaoCD(): string { if (document.querySelector("#icon49-sidebar")) { // 0-10min return '<10分' } else if (document.querySelector("#icon50-sidebar")) { // 10min-1h return '<1时' } else if (document.querySelector("#icon51-sidebar")) { // 1h-2h return '1~2时' } else if (document.querySelector("#icon52-sidebar")) { // 2h-5h return '2~5时' } else if (document.querySelector("#icon53-sidebar")) { // 5h+ return '>5时' } else { return '无效' } } public static ajaxFetch(opt: AjaxFetchOption) { let { url, referrer = '/', method, body = null } = opt; let req_params: RequestInit = { headers: { 'X-Requested-With': 'XMLHttpRequest' }, referrer, method, }; if (method === 'POST') { req_params.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; req_params.body = body; } return window.fetch(url, req_params); } /** * 通过 mutation.observe 方法异步返回元素 * @param {String} selectors - CSS规则的HTML元素选择器 * @param {Document} content - 上下文 * @param {number} timeout - 超时毫秒数 * @returns {Promise} */ public static elementReady(selectors: string, content: Document = document, timeout: number = 30000): Promise { Log.info('等待元素:' + selectors); let timer = new Timer(); return new Promise((resolve, reject) => { let el = content.querySelector(selectors) as HTMLElement; if (el) { Log.info('已获取元素, 耗时' + timer.getTimeMs(), el); resolve(el); return; } let observer = new MutationObserver((_, observer) => { content.querySelectorAll(selectors).forEach((element) => { Log.info({ innerHTML: element.innerHTML, element }); observer.disconnect(); Log.info('已获取元素, 耗时' + timer.getTimeMs(), element); resolve(element as HTMLElement); }); }); setTimeout(() => { observer.disconnect(); Log.error(`等待元素超时! [${ selectors }]\n${ content.documentElement.tagName }, 耗时` + timer.getTimeMs()); reject(`等待元素超时! [${ selectors }]\n${ content.documentElement.tagName }, 耗时` + timer.getTimeMs()); }, timeout); observer.observe(content.documentElement, { childList: true, subtree: true }); }); } /** * 通过 mutation.observe 方法异步返回元素 * @param selectors * @param content * @param timeout */ public static querySelector(selectors: string, content: Document = document, timeout: number = 30000): Promise { return CommonUtils.elementReady(selectors, content, timeout); } public static addStyle(rules: string): void { let element = document.querySelector('style#wh-trans-gStyle'); if (element) { element.innerHTML += rules; } else { element = document.createElement("style"); element.id = 'wh-trans-gStyle'; element.innerHTML = rules; document.head.appendChild(element); } Log.info('CSS规则已添加', element); } public static loading_gif_html(): string { return LOADING_IMG_HTML; } /** * 播放音频 * @param {string} url 播放的音频URL * @returns {undefined} */ public audioPlay(url: string = 'https://www.torn.com/js/chat/sounds/Warble_1.mp3') { const audio = new Audio(url); audio.addEventListener("canplaythrough", () => { audio.play() .catch(err => Log.error(err)) .then(); }); } /** * 与给定日期对比,现在是否是新的一天 * @param target * @param offsetHours 比对时间的偏移小时数 */ public isNewDay(target: number | Date, offsetHours: number = 0): boolean { let tar: Date = typeof target === "number" ? new Date(target) : target; Log.info(tar.toLocaleString()); let today = new Date(); let utcNewDay = new Date(Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate())); utcNewDay.setHours(utcNewDay.getHours() + offsetHours); return utcNewDay > tar; } public jQueryReady(): Promise { Log.info('等待jQuery加载中...'); FetchUtils.getInstance().fetchText('/js/script/lib/jquery-1.8.2.js?v=f9128651g') .then(res => window.eval(res)); let intervalId = window.setInterval(() => { Log.info('仍在等待jQuery加载中...'); }, 1000); return new Promise(async resolve => { while (true) { if (typeof window.$ === 'function') break; await this.sleep(100); } window.clearInterval(intervalId); Log.info('jQuery已加载'); resolve(null); }); } /** * 等待毫秒数 * @param {Number} ms 毫秒 * @returns {Promise} */ public sleep(ms: number): Promise { let time = Math.max(ms, 10); return new Promise(resolve => setTimeout(() => resolve(null), time)); } public elemGenerator(setting: MenuItemConfig, root_node: Element): HTMLElement { let { tip, domType } = setting; let new_node = null; switch (domType) { case 'checkbox': { new_node = document.createElement('div'); let { domId, dictName, domText, changeEv } = setting; let switcher = new TornStyleSwitch(domText); let _input = switcher.getInput(); switcher.getBase().id = domId; (tip) && (switcher.getBase().setAttribute('title', tip)); _input.checked = WuhuConfig.get(dictName); _input.onchange = e => { WuhuConfig.set(dictName, _input.checked, true); if (changeEv) changeEv(e); }; new_node.appendChild(switcher.getBase()); break; } case 'button': { new_node = document.createElement('div'); let { domId, domText, clickFunc } = setting; let btn = document.createElement('button'); (tip) && (btn.setAttribute('title', tip)); btn.id = domId; btn.innerHTML = domText; btn.addEventListener('click', clickFunc); new_node.appendChild(btn); break; } case 'select': { new_node = document.createElement('div'); let { domSelectOpt, dictName, domId, domText } = setting; let label = document.createElement('label'); (tip) && (label.setAttribute('title', tip)); let text = document.createTextNode(domText); let select = document.createElement('select'); select.id = domId; domSelectOpt.forEach((opt, i) => { let { domVal, domText } = opt; let option = document.createElement('option'); option.value = domVal; option.innerHTML = domText; option.selected = i === WuhuConfig.get(dictName); option.innerHTML = domText; select.appendChild(option); }); select.onchange = e => WuhuConfig.set(dictName, (e.target).selectedIndex); label.appendChild(text); label.appendChild(select); new_node.appendChild(label); break; } case 'plain': { let tag = setting.tagName || 'div'; new_node = document.createElement(tag); if (setting.domId) new_node.id = setting.domId; new_node.innerHTML += setting['domHTML']; break; } } // 移动节点 return root_node.appendChild(new_node); } }