356 lines
14 KiB
TypeScript
356 lines
14 KiB
TypeScript
import UserScriptEngine from "../../enum/UserScriptEngine";
|
||
import WuhuBase from "../WuhuBase";
|
||
import Log from "../Log";
|
||
import Device from "../../enum/Device";
|
||
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";
|
||
import TRAVEL_STATE from "../../enum/TravelState";
|
||
|
||
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<string> {
|
||
const engine = this.getScriptEngine();
|
||
let start = performance.now();
|
||
Log.info('跨域获取数据开始, 脚本引擎: ' + engine);
|
||
return new Promise<string>((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<HTMLElement|null>}
|
||
*/
|
||
public static elementReady(selectors: string, content: Document = document, timeout: number = 30000): Promise<HTMLElement> {
|
||
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<HTMLElement> {
|
||
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;
|
||
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<null> {
|
||
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<void>}
|
||
*/
|
||
public sleep(ms: number): Promise<null> {
|
||
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, (<HTMLSelectElement>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);
|
||
}
|
||
|
||
public exportTextFile(filename, content) {
|
||
const tmp = document.createElement('a');
|
||
tmp.href = URL.createObjectURL(new Blob(content, { type: 'text/plain', endings: 'transparent' }));
|
||
tmp.download = filename;
|
||
tmp.click();
|
||
tmp.remove();
|
||
URL.revokeObjectURL(tmp.href);
|
||
}
|
||
|
||
public isValidUid(id: string | number): boolean {
|
||
if (typeof id === 'string') {
|
||
return /^[0-9]{1,7}$/.test(id);
|
||
} else {
|
||
return /^[0-9]{1,7}$/.test(id.toString());
|
||
}
|
||
}
|
||
|
||
public getTravelStage(): TRAVEL_STATE {
|
||
let global = CommonUtils.glob;
|
||
if (global.bodyAttrs["data-abroad"] === 'false') {
|
||
return TRAVEL_STATE.IN_TORN;
|
||
}
|
||
// 海外落地:abroad
|
||
else if (global.bodyAttrs["data-traveling"] === 'false') {
|
||
return TRAVEL_STATE.ABROAD;
|
||
}
|
||
// 飞行中:traveling+abroad
|
||
else {
|
||
return TRAVEL_STATE.FLYING;
|
||
}
|
||
}
|
||
}
|