This commit is contained in:
Liwanyi 2022-09-09 18:03:47 +08:00
parent d1c5d22f29
commit a5f0a82211
24 changed files with 727 additions and 507 deletions

42
global.d.ts vendored Normal file
View File

@ -0,0 +1,42 @@
declare interface String {
contains(keywords: RegExp | string): boolean;
}
declare interface Window {
unsafeWindow?: Window & typeof globalThis;
$?: JQueryStatic;
jQuery?: JQueryStatic;
WHPARAMS?: any;
ReactDOM?: any;
addRFC(url: URL | string): string;
PDA_httpGet(url: URL | string): Promise<PDA_Response>;
PDA_httpPost(url: URL | string, init: any, body: any): Promise<PDA_Response>;
GM_xmlhttpRequest(init: GM_RequestParams);
}
declare interface GM_RequestParams {
method?: string,
url?: URL | string,
data?: any,
headers?: any,
onload?: Function,
onerror?: Function,
ontimeout?: Function,
}
declare interface PDA_Response {
responseText: string
}
declare interface Element {
innerText?: string;
src?: string;
}
declare interface Notification {
id?: number;
}

View File

@ -1,3 +0,0 @@
export default interface Global {
startTimestamp: number;
}

View File

@ -2696,44 +2696,6 @@ export const calDict = {
} }
// 中文字符集正则 // 中文字符集正则
export const CC_set = /[\u4e00-\u9fa5]/ export const CC_set = /[\u4e00-\u9fa5]/
// const transDict = {};
// transDict.titleDict = titleDict;
// transDict.titleLinksDict = titleLinksDict;
// transDict.sidebarDict = sidebarDict;
// transDict.tooltipDict = tooltipDict;
// transDict.statusDict = statusDict;
// transDict.miniProfileDict = miniProfileDict;
// transDict.homeDict = homeDict;
// transDict.attackDict = attackDict;
// transDict.newspaperDict = newspaperDict;
// transDict.propertyDict = propertyDict;
// transDict.travelingDict = travelingDict;
// transDict.tipsDict = tipsDict;
// transDict.cityDict = cityDict;
// transDict.gymDict = gymDict;
// transDict.gymList = gymList;
// transDict.eduDict = eduDict;
// transDict.headerDict = headerDict;
// transDict.eventsDict = eventsDict;
// transDict.chatDict = chatDict;
// transDict.hosDict = hosDict;
// transDict.awDict = awDict;
// transDict.playerTitleList = playerTitleList;
// transDict.ocList = ocList;
// transDict.profileDict = profileDict;
// transDict.sendCashDict = sendCashDict;
// transDict.stockDict = stockDict;
// transDict.itemPageDict = itemPageDict;
// transDict.itemNameDict = itemNameDict;
// transDict.itemDescDict = itemDescDict;
// transDict.itemEffectDict = itemEffectDict;
// transDict.itemTypeDict = itemTypeDict;
// transDict.itemReqDict = itemReqDict;
// transDict.tornSettingsDict = tornSettingsDict;
// transDict.missionDict = missionDict;
// transDict.pcDict = pcDict;
// transDict.npcShopDict = npcShopDict;
// transDict.calDict = calDict;
// if (!localStorage.getItem('wh_trans_transDict')) localStorage.setItem('wh_trans_transDict', JSON.stringify(transDict)) // if (!localStorage.getItem('wh_trans_transDict')) localStorage.setItem('wh_trans_transDict', JSON.stringify(transDict))
export * from './translation' export * from './translation'

View File

@ -1,5 +1,5 @@
import {eventsDict, ocList, gymList} from "../dictionary/translation"; import {eventsDict, ocList, gymList} from "../../dictionary/translation";
import log from "./utils/log"; import log from "../utils/log";
export default function eventsTrans(events: JQuery = $('span.mail-link')) { export default function eventsTrans(events: JQuery = $('span.mail-link')) {
const index = window.location.href.indexOf('events.php#/step=received') >= 0 ? 1 : 0; const index = window.location.href.indexOf('events.php#/step=received') >= 0 ? 1 : 0;

92
src/func/utils/BuyBeer.ts Normal file
View File

@ -0,0 +1,92 @@
import getWhSettingObj from "./getWhSettingObj";
import log from "./log";
import WHNotify from "./WHNotify";
// 啤酒
export default function BuyBeer() {
// 正在通知
let is_notified = false;
let time: number = getWhSettingObj()['_15AlarmTime'] || 50;
let loop: BeerMonitorLoop = {};
// 循环id
let started = null;
loop.start = () => {
if (started) {
log.info('啤酒助手已在运行');
return;
}
started = setInterval(() => {
// 海外取消提醒
let {isTravelling, isAbroad} = getUserState();
if (isTravelling || isAbroad) {
loop.stop();
return;
}
let dt = new Date();
// 已选当天不提醒
const now = [dt.getUTCFullYear(), dt.getUTCMonth(), dt.getUTCDate()];
const ignore_date = getWhSettingObj()['_15_alarm_ignore'] || '{}';
if (JSON.stringify(now) === JSON.stringify(ignore_date)) return;
// 正常提醒
let m = 14 - (dt.getMinutes() % 15);
let s = 60 - dt.getSeconds();
if (m === 0 && s < time) {
// 如从通知点开,则本次通知跳过
if (location.href.includes('#clickfromnotify')) {
is_notified = true;
location.hash = '';
return;
}
// 本次已通知
if (is_notified) return;
is_notified = true;
// 发送通知
const notify = WHNotify(notify_html, {
timeout: 30,
sysNotify: true,
});
notify.querySelector('.wh-notify-msg button').addEventListener('click', ()=>loop.skip_today);
notify.addEventListener('click', ev => {
if ((ev.target as HTMLElement).tagName.toLowerCase() === 'a') {
notify.sys_notify.close();
notify.close();
}
});
window.setTimeout(audioPlay, 800);
window.setTimeout(audioPlay, 800 * 2);
window.setTimeout(audioPlay, 800 * 3);
} else {
is_notified = false;
}
}, 1000);
};
loop.stop = () => {
if (started) {
clearInterval(started);
started = null;
}
};
loop.set_time = (t) => time = t;
loop.status = () => started ? '已启动' : '未启动';
loop.is_running = () => !!started;
let notify_html = `<span style="background-color:green;color:white;border-radius:3px;font-size:14px;line-height:21px;padding:2px 4px;">啤酒小助手</span><br/>提醒您:还有不到 50 秒 NPC 的商品就要刷新了,啤酒血包要抢的可以准备咯。<button id="wh-rd-btn-${getRandomInt(0, 100)}">【今日不再提醒】</button><br/><a href="/shops.php?step=bitsnbobs#clickfromnotify" target="_blank">【啤酒店】</a> <a href="/shops.php?step=pharmacy#clickfromnotify" target="_blank">【血包店】</a>`
loop.skip_today = () => {
const date = new Date();
setWhSetting('_15_alarm_ignore', [date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()], false);
// 通知
const notify = WHNotify(`明早8点前将不再提醒 <button id="wh-rd-btn-${getRandomInt(0, 100)}">取消</button>`);
// 通知中的取消按钮
notify.querySelector('.wh-notify-msg button').addEventListener('click', () => setWhSetting('_15_alarm_ignore', undefined));
};
return loop;
}
interface BeerMonitorLoop {
start?: Function;
stop?: Function;
set_time?: Function;
status?: Function;
is_running?: Function;
skip_today?: Function;
}

View File

@ -1,7 +1,8 @@
// 跨域get请求 返回text
import UserScriptEngine from "../../enum/UserScriptEngine"; import UserScriptEngine from "../../enum/UserScriptEngine";
import getScriptEngine from "./getScriptEngine";
function COFetch(url, method = 'get', body = null) { // 跨域get请求 返回text
function COFetch(url:URL|string, method:string = 'get', body:any = null):Promise<string> {
const engine = getScriptEngine(); const engine = getScriptEngine();
switch (engine) { switch (engine) {
case UserScriptEngine.RAW: { case UserScriptEngine.RAW: {
@ -20,11 +21,11 @@ function COFetch(url, method = 'get', body = null) {
reject('错误PDA版本不支持'); reject('错误PDA版本不支持');
} }
PDA_httpGet(url) PDA_httpGet(url)
.then(res => resolve(res.responseText))
.catch(e => { .catch(e => {
console.error('[wh] 网络错误', e); console.error('[wh] 网络错误', e);
reject(`[wh] 网络错误 ${e}`); reject(`[wh] 网络错误 ${e}`);
}) });
.then(res => resolve(res.responseText));
}) : }) :
// post // post
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
@ -33,14 +34,15 @@ function COFetch(url, method = 'get', body = null) {
reject('错误PDA版本不支持'); reject('错误PDA版本不支持');
} }
PDA_httpPost(url, {'content-type': 'application/json'}, body) PDA_httpPost(url, {'content-type': 'application/json'}, body)
.then(res => resolve(res.responseText))
.catch(e => { .catch(e => {
console.error('[wh] 网络错误', e); console.error('[wh] 网络错误', e);
reject(`[wh] 网络错误 ${e}`); reject(`[wh] 网络错误 ${e}`);
}) });
.then(res => resolve(res.responseText));
}); });
} }
case UserScriptEngine.GM: { case UserScriptEngine.GM: {
let {GM_xmlhttpRequest} = window;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (typeof GM_xmlhttpRequest !== 'function') { if (typeof GM_xmlhttpRequest !== 'function') {
console.error('[wh] 跨域请求错误用户脚本扩展API错误'); console.error('[wh] 跨域请求错误用户脚本扩展API错误');

148
src/func/utils/WHNotify.ts Normal file
View File

@ -0,0 +1,148 @@
import getRandomInt from "./getRandomInt";
import addStyle from "./addStyle";
/**
*
* @param {string} msg -
* @param {Object} [options] -
* @param {number} [options.timeout] -
* @param {function} [options.callback] -
* @param {boolean} [options.sysNotify] -
* @param {string} [options.sysNotifyTag] -
* @param {function} [options.sysNotifyClick] -
* @return {HTMLElement}
*/
export default function WHNotify(msg, options: WHNotifyOpt = {}): MyHTMLElement {
let glob = window.WHPARAMS;
let {isIframe, isWindowActive, notifies} = glob;
let {
timeout = 3,
callback = function () {
},
sysNotify = false,
sysNotifyTag = '芜湖助手',
sysNotifyClick = () => window.focus()
} = options;
if (!isWindowActive() || isIframe) return null;
const date = new Date();
// 通知的唯一id
const uid = `${date.getHours()}${date.getSeconds()}${date.getMilliseconds()}${getRandomInt(1000, 9999)}`;
// 通知容器id
const node_id = 'wh-notify';
// 通知的容器
let notify_contain: MyHTMLElement = document.querySelector(`#${node_id}`);
// 添加通知到容器
const add_notify = () => {
// 每条通知
const new_node: MyHTMLElement = document.createElement('div');
new_node.id = `wh-notify-${uid}`;
new_node.classList.add('wh-notify-item');
new_node.innerHTML = `<div class="wh-notify-bar"></div>
<div class="wh-notify-cont">
<div class="wh-notify-close"></div>
<div class="wh-notify-msg"><p>${msg}</p></div>
</div>`;
notify_contain.append(new_node);
notify_contain['msgInnerText'] = new_node.querySelector('.wh-notify-msg').innerText;
// 进度条node
const progressBar: HTMLElement = new_node.querySelector('.wh-notify-bar');
// 是否hover
let mouse_enter = false;
new_node.addEventListener('mouseenter', () => mouse_enter = true, true);
new_node.addEventListener('mouseleave', () => mouse_enter = false);
// 通知进度条
let progressCount = 101;
// 删除通知
new_node.close = () => {
clearInterval(intervalID);
new_node.remove();
callback();
};
// 计时器
let intervalID = window.setInterval(() => {
if (mouse_enter) {
progressCount = 101;
progressBar.style.width = '100%';
return;
}
progressCount--;
progressBar.style.width = `${progressCount}%`;
if (progressCount === 0) new_node.remove();
}, timeout * 1000 / 100);
new_node.querySelector('.wh-notify-close').addEventListener('click', new_node.close);
return new_node;
};
// 不存在容器 创建
if (!notify_contain) {
notify_contain = document.createElement('div');
notify_contain.id = node_id;
addStyle(`
#${node_id} {
display: inline-block;
position: fixed;
top: 0;
left: calc(50% - 180px);
width: 360px;
z-index: 9999990;
color:#333;
}
#${node_id} a{
color:red;
text-decoration:none;
}
#${node_id} .wh-notify-item {
/*height: 50px;*/
background: rgb(239 249 255 / 90%);
border-radius: 2px;
margin: 0.5em 0 0 0;
box-shadow: 0 0 5px 0px #959595;
}
#${node_id} .wh-notify-item:hover {
background: rgb(239 249 255 / 98%);
}
#${node_id} .wh-notify-item .wh-notify-bar {
height:2px;
background:#2196f3;
}
#${node_id} .wh-notify-item .wh-notify-close {
float:right;
padding:0;
width:16px;height:16px;
background:url('data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%201024%201024%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M923%20571H130.7c-27.6%200-50-22.4-50-50s22.4-50%2050-50H923c27.6%200%2050%2022.4%2050%2050s-22.4%2050-50%2050z%22%20fill%3D%22%232196f3%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E') no-repeat center;
background-size:100%;
margin: 6px 6px 0 0;
cursor: pointer;
}
#${node_id} .wh-notify-item .wh-notify-msg {
padding:12px;
}
`);
document.body.append(notify_contain);
}
const notify_obj = add_notify();
// 浏览器通知
if (window.Notification && Notification.permission === 'granted' && sysNotify) {
const date_local_string = `[${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}]\r`;
notify_obj.sys_notify = new Notification('芜湖助手', {
body: date_local_string + notify_contain.msgInnerText,
requireInteraction: true,
renotify: true,
tag: sysNotifyTag + getRandomInt(0, 99),
});
notify_obj.sys_notify.addEventListener('close', () => sysNotifyClick());
notify_obj.sys_notify.onshow = () => setTimeout(() => notify_obj.sys_notify.close(), timeout * 1000);
notify_obj.sys_notify.id = notifies.count++;
notifies[notify_obj.sys_notify.id] = notify_obj.sys_notify;
notify_obj.sys_notify.addEventListener('close', () => notifies[notify_obj.sys_notify.id] = null);
}
return notify_obj;
}
interface WHNotifyOpt {
timeout?: number;
callback?: Function;
sysNotify?: boolean;
sysNotifyTag?: string;
sysNotifyClick?: Function;
}

View File

@ -0,0 +1,18 @@
import log from "./log";
/**
* style
* @param {string} css CSS规则
*/
export default function addStyle(css: string) {
let wh_gStyle = document.querySelector('style#wh-trans-gStyle');
if (wh_gStyle) {
wh_gStyle.innerHTML += css;
} else {
wh_gStyle = document.createElement("style");
wh_gStyle.id = 'wh-trans-gStyle';
wh_gStyle.innerHTML = css;
document.head.append(wh_gStyle);
}
log.info('CSS规则已添加', wh_gStyle);
}

View File

@ -1,3 +1,5 @@
import COFetch from "./COFetch";
/** /**
* json对象 * json对象
* @param dest * @param dest

View File

@ -3,18 +3,11 @@
* @return {PlayerInfo} rs * @return {PlayerInfo} rs
*/ */
function getPlayerInfo(): PlayerInfo { function getPlayerInfo(): PlayerInfo {
let rs = new PlayerInfo();
const node = document.querySelector('script[uid]'); const node = document.querySelector('script[uid]');
if (node) { if (node) return {
rs.playername = node.getAttribute('name'); playername: node.getAttribute('name'),
rs.userID = node.getAttribute('uid') as unknown as number; userID: node.getAttribute('uid') as unknown as number,
} }
return rs;
}
class PlayerInfo {
playername: string
userID: number
} }
export default getPlayerInfo export default getPlayerInfo

View File

@ -0,0 +1,7 @@
// 得到一个两数之间的随机整数
export default function getRandomInt(min:number, max:number):number {
min = Math.ceil(min);
max = Math.floor(max);
//不含最大值,含最小值
return Math.floor(Math.random() * (max - min)) + min;
}

View File

@ -0,0 +1,9 @@
import UserScriptEngine from "../../enum/UserScriptEngine";
import Global from "../../interface/GlobalVars";
// 用户脚本平台类型
export default function () {
let glob = window.WHPARAMS;
return glob.UWCopy ? UserScriptEngine.GM : glob.isPDA
? UserScriptEngine.PDA : UserScriptEngine.RAW;
}

View File

@ -0,0 +1,7 @@
// 玩家状态
export default function getUserState(): {} | any {
let obj = {};
let hdd = sessionStorage['headerData'];
if (hdd) obj = JSON.parse(hdd)['user']['state'];
return obj;
}

View File

@ -0,0 +1,77 @@
// 价格监视handle
import getWhSettingObj from "./getWhSettingObj";
import log from "./log";
import toThousands from "./toThousands";
import WHNotify from "./WHNotify";
let glob = window.WHPARAMS;
let {isPDA, PDA_APIKey, priceWatcher} = glob;
export default function priceWatcherHandle() {
setInterval(() => {
const price_conf = getWhSettingObj()['priceWatcher'];
const apikey = isPDA ? PDA_APIKey : localStorage.getItem('APIKey');
if (!apikey) {
log.info('无法获取APIKey')
return;
}
if (price_conf['pt'] !== -1) priceWatcherPt(apikey, price_conf['pt']).then();
if (price_conf['xan'] !== -1) priceWatcherXan(apikey, price_conf['xan']).then();
}, 10000)
return {status: true};
}
// pt价格监视
async function priceWatcherPt(apikey, lower_price) {
if (!priceWatcher['watch-pt-lower-id']) priceWatcher['watch-pt-lower-id'] = [];
const res = await fetch('https://api.torn.com/market/?selections=pointsmarket&key=' + apikey);
const obj = await res.json();
if (obj['pointsmarket']) {
// 过滤低于价格的物品出售id
const lower_arr = [];
let low = Infinity;
Object.keys(obj['pointsmarket']).forEach(key => {
if (obj['pointsmarket'][key]['cost'] <= lower_price) {
lower_arr.push(key);
if (obj['pointsmarket'][key]['cost'] < low) low = obj['pointsmarket'][key]['cost'];
}
});
if (lower_arr.length === 0) return;
// 将id与之前存在的比较不相同时发送通知
if (JSON.stringify(priceWatcher['watch-pt-lower-id']) !== JSON.stringify(lower_arr)) {
priceWatcher['watch-pt-lower-id'] = lower_arr;
WHNotify(`PT新低价$${toThousands(low)}( < $${toThousands(lower_price)}) - <a href="/pmarket.php" target="_blank">点击转跳</a>`, {
timeout: 6,
sysNotify: true,
sysNotifyClick: () => window.open('https://www.torn.com/pmarket.php'),
});
}
} else {
// 查询出错了
log('pt查询出错了')
}
}
// xan价格监视
async function priceWatcherXan(apikey, lower_price) {
// 初始化记录上一个条目的id避免重复发送通知
if (!priceWatcher['watch-xan-lower-id']) priceWatcher['watch-xan-lower-id'] = '';
const res = await fetch('https://api.torn.com/market/206?selections=bazaar&key=' + apikey);
const obj = await res.json();
if (obj['bazaar']) {
const lowest_item = obj['bazaar'][0]
if (lowest_item['cost'] <= lower_price) {
if (priceWatcher['watch-xan-lower-id'] !== lowest_item['ID']) {
priceWatcher['watch-xan-lower-id'] = lowest_item['ID'];
WHNotify(`XAN新低价$${toThousands(lowest_item['cost'])}( < $${toThousands(lower_price)}) - <a href="/imarket.php#/p=shop&step=shop&type=&searchname=Xanax" target="_blank">点击转跳</a>`, {
timeout: 6,
sysNotify: true,
sysNotifyClick: () => window.open('https://www.torn.com/imarket.php#/p=shop&step=shop&type=&searchname=Xanax')
});
}
}
} else {
// 查询出错了
log('xan查询出错了')
}
}

View File

@ -0,0 +1,13 @@
// 格式化金额数字
export default function toThousands(num: string|number):string {
num = (num || 0).toString();
let result = '';
while (num.length > 3) {
result = ',' + num.slice(-3) + result;
num = num.slice(0, num.length - 3);
}
if (num) {
result = num + result;
}
return result;
}

View File

@ -1,53 +1,55 @@
import './global' import '../global'
import log from "./func/utils/log"; import log from "./func/utils/log";
import getWhSettingObj from "./func/utils/getWhSettingObj"; import getWhSettingObj from "./func/utils/getWhSettingObj";
import miniprofTrans from "./func/translate/miniprofTrans"; import miniprofTrans from "./func/translate/miniprofTrans";
import Global from "./interface/GlobalVars";
import Device from "./enum/Device";
import getPlayerInfo from "./func/utils/getPlayerInfo";
import autoFetchJSON from "./func/utils/autoFetchJSON";
import priceWatcherHandle from "./func/utils/priceWatcherHandle";
export default function init() { // 初始化方法,获取必要全局参数
const UWCopy: Window & typeof globalThis = window["unsafeWindow"]; export default function init(glob: Global) {
try { glob.window = window;
window = UWCopy || window; window.WHPARAMS = glob;
} catch { let UWCopy = null;
if (window.hasOwnProperty('unsafeWindow')) {
UWCopy = window.unsafeWindow;
try {
window = UWCopy;
} catch {
}
} }
// 防止脚本重复运行 glob.UWCopy = UWCopy;
if (window["WHTRANS"]) throw new DOMException('已在运行');
window["WHTRANS"] = true;
// 脚本版本 // 脚本版本
const version = '$$WUHU_DEV_VERSION$$'; glob.version = '$$WUHU_DEV_VERSION$$';
// iframe运行 // iframe运行
const isIframe = self !== top; glob.isIframe = self !== top;
const $ = window['jQuery'];
// PDA
const PDA_APIKey = '###PDA-APIKEY###';
const isPDA = PDA_APIKey.slice(-1) !== '#';
// 通知权限 // PDA
glob.PDA_APIKey = '###PDA-APIKEY###';
glob.isPDA = glob.PDA_APIKey.slice(-1) !== '#';
// 请求通知权限
if (window.Notification) { if (window.Notification) {
Notification.requestPermission().then(status => { Notification.requestPermission().then();
// 这将使我们能在 Chrome/Safari 中使用 Notification.permission
if (Notification.permission !== status) {
// @ts-ignore
Notification['permission'] = status;
}
})
} }
// regexp test // 扩展String正则方法
String.prototype.contains = function (keywords: RegExp) { String.prototype.contains = function (keywords) {
let that: String = this; let that: string = this;
if ('string' === typeof keywords) { if ('string' === typeof keywords) {
return new RegExp(keywords).test(that); return new RegExp(keywords).test(that);
} } else {
if (keywords.test) {
return keywords.test(that); return keywords.test(that);
} }
}; };
// region 监听fetch // 监听fetch
const ori_fetch = window.fetch; const ori_fetch = window.fetch;
window.fetch = async (url, init) => { window.fetch = async (url: string, init: RequestInit) => {
if (url.contains('newsTickers')) { if (url.contains('newsTickers')) {
// 阻止获取新闻横幅 // 阻止获取新闻横幅
return new Response('{}'); return new Response('{}');
@ -59,9 +61,36 @@ export default function init() {
} }
let clone = res.clone(); let clone = res.clone();
let text = await res.text(); let text = await res.text();
log({url, init, text}); log.info({url, init, text});
return clone; return clone;
}; };
// endregion
return {version, isIframe, $, PDA_APIKey, isPDA}; glob.device = window.innerWidth >= 1000
? Device.PC : window.innerWidth <= 600 ? Device.MOBILE : Device.TABLET;
// 玩家信息
glob.player_info = getPlayerInfo();
// 海外库存对象
glob.fstock = autoFetchJSON('https://yata.yt/api/v1/travel/export/');
// 价格监视实例对象
glob.priceWatcher = glob.isIframe ? null : priceWatcherHandle();
// 抢啤酒
glob.beer = buyBeer();
glob.popup_node = null;
// 当窗口关闭时关闭所有还存在的通知
glob.notifies = {count: 0};
window.addEventListener(
'beforeunload',
() => {
if (glob.notifies.count !== 0) {
for (let i = 0; i < glob.notifies.count; i++) {
(glob.notifies[i] !== null) && (glob.notifies[i].close())
}
}
}
);
} }

View File

@ -0,0 +1,16 @@
import Device from "../enum/Device";
export default interface Global {
notifies?: NotifyWrapper;
priceWatcher?: { status: boolean };
fstock?: { get: () => Promise<any> };
player_info?: PlayerInfo;
device?: Device;
isPDA?: boolean;
PDA_APIKey?: string;
isIframe?: boolean;
version?: string;
window?: Window;
UWCopy?: Window & typeof globalThis;
startTimestamp: number;
}

View File

@ -0,0 +1,5 @@
interface MyHTMLElement extends HTMLElement {
sys_notify?: Notification;
msgInnerText?: string;
close?: () => void;
}

View File

@ -0,0 +1,5 @@
interface NotifyWrapper {
count: number;
[notifyId: number]: Notification;
}

View File

@ -0,0 +1,4 @@
interface PlayerInfo {
playername: string
userID: number
}

5
src/main.d.ts vendored
View File

@ -1,5 +0,0 @@
declare interface String {
contains(keywords: RegExp|String): boolean
}

View File

@ -1,7 +1,8 @@
import userscript from "./userscript"; import userscript from "./userscript";
import Global from "./global"; import Global from "./interface/GlobalVars";
const glob: Global = { const glob: Global = {
startTimestamp: -1, startTimestamp: -1,
}; };
userscript(glob); userscript(glob);

View File

@ -43,46 +43,21 @@ import * as DICTION from './dictionary/translation'
import Device from "./enum/Device"; import Device from "./enum/Device";
import UserScriptEngine from "./enum/UserScriptEngine"; import UserScriptEngine from "./enum/UserScriptEngine";
import getPlayerInfo from "./func/utils/getPlayerInfo"; import getPlayerInfo from "./func/utils/getPlayerInfo";
import autoFetchJSON from "./func/utils/autoFetchJSON"; import Global from "./interface/GlobalVars";
import Global from "./global";
export default function userscript(glob: Global): void { export default function userscript(glob: Global): void {
glob.startTimestamp = Date.now(); glob.startTimestamp = Date.now();
if (document.title.toLowerCase().includes('just a moment')) return; if (document.title.toLowerCase().includes('just a moment')) return;
let {version, isIframe, $, PDA_APIKey, isPDA} = init(); init(glob);
let {version, isIframe, PDA_APIKey, isPDA, player_info, fstock, notifies} = glob;
const date = new Date(); const date = new Date();
const device = window.innerWidth >= 1000
? Device.PC : window.innerWidth <= 600 ? Device.MOBILE : Device.TABLET;
// 玩家信息
const player_info = getPlayerInfo();
// 海外库存对象
const fstock = autoFetchJSON('https://yata.yt/api/v1/travel/export/');
// 价格监视实例对象
const priceWatcher = isIframe ? null : priceWatcherHandle();
// 返回一个加载中gif图形HTML // 返回一个加载中gif图形HTML
const loading_gif_html = () => { const loading_gif_html = () => {
const gif_base64 = `data:image/svg+xml,%3Csvg t='1656084442571' class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' p-id='3924' width='14' height='14'%3E%3Cpath d='M512.032002 237.105181a29.310168 29.310168 0 0 1-29.310168-29.246172V29.310168a29.310168 29.310168 0 0 1 58.620336 0v178.548841A29.310168 29.310168 0 0 1 512.032002 237.105181zM512.032002 1024a29.310168 29.310168 0 0 1-29.310168-29.310168v-178.484845a29.310168 29.310168 0 1 1 58.620336 0v178.548841A29.310168 29.310168 0 0 1 512.032002 1024z m482.657834-482.657834h-178.484845a29.310168 29.310168 0 1 1 0-58.620336h178.548841a29.310168 29.310168 0 1 1 0 58.620336z m-786.830823 0H29.310172a29.310168 29.310168 0 0 1 0-58.620336h178.548841a29.310168 29.310168 0 0 1 0 58.620336z m519.263546-215.090557a29.182176 29.182176 0 0 1-20.734704-49.980876l126.264108-126.264108a29.310168 29.310168 0 1 1 41.405412 41.405412l-126.264108 126.264108a29.182176 29.182176 0 0 1-20.670708 8.575464zM170.741333 882.568839a29.182176 29.182176 0 0 1-20.734704-49.980876l126.264108-126.264108a29.246172 29.246172 0 1 1 41.405412 41.405412L191.412041 874.057371a29.182176 29.182176 0 0 1-20.670708 8.575464z m682.581338 0a29.182176 29.182176 0 0 1-20.670708-8.575464l-126.264108-126.264108a29.310168 29.310168 0 1 1 41.405412-41.405412l126.264108 126.264108a29.310168 29.310168 0 0 1-20.734704 49.91688zM297.005441 326.251609a29.182176 29.182176 0 0 1-20.670708-8.575464L150.006629 191.412037a29.310168 29.310168 0 1 1 41.405412-41.405412l126.264108 126.264108a29.310168 29.310168 0 0 1-20.734704 49.91688z' p-id='3925'%3E%3C/path%3E%3C/svg%3E` const gif_base64 = `data:image/svg+xml,%3Csvg t='1656084442571' class='icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' p-id='3924' width='14' height='14'%3E%3Cpath d='M512.032002 237.105181a29.310168 29.310168 0 0 1-29.310168-29.246172V29.310168a29.310168 29.310168 0 0 1 58.620336 0v178.548841A29.310168 29.310168 0 0 1 512.032002 237.105181zM512.032002 1024a29.310168 29.310168 0 0 1-29.310168-29.310168v-178.484845a29.310168 29.310168 0 1 1 58.620336 0v178.548841A29.310168 29.310168 0 0 1 512.032002 1024z m482.657834-482.657834h-178.484845a29.310168 29.310168 0 1 1 0-58.620336h178.548841a29.310168 29.310168 0 1 1 0 58.620336z m-786.830823 0H29.310172a29.310168 29.310168 0 0 1 0-58.620336h178.548841a29.310168 29.310168 0 0 1 0 58.620336z m519.263546-215.090557a29.182176 29.182176 0 0 1-20.734704-49.980876l126.264108-126.264108a29.310168 29.310168 0 1 1 41.405412 41.405412l-126.264108 126.264108a29.182176 29.182176 0 0 1-20.670708 8.575464zM170.741333 882.568839a29.182176 29.182176 0 0 1-20.734704-49.980876l126.264108-126.264108a29.246172 29.246172 0 1 1 41.405412 41.405412L191.412041 874.057371a29.182176 29.182176 0 0 1-20.670708 8.575464z m682.581338 0a29.182176 29.182176 0 0 1-20.670708-8.575464l-126.264108-126.264108a29.310168 29.310168 0 1 1 41.405412-41.405412l126.264108 126.264108a29.310168 29.310168 0 0 1-20.734704 49.91688zM297.005441 326.251609a29.182176 29.182176 0 0 1-20.670708-8.575464L150.006629 191.412037a29.310168 29.310168 0 1 1 41.405412-41.405412l126.264108 126.264108a29.310168 29.310168 0 0 1-20.734704 49.91688z' p-id='3925'%3E%3C/path%3E%3C/svg%3E`
return `<img src="${gif_base64}" alt="lgif" style="width:14px;height:14px;" />`; return `<img src="${gif_base64}" alt="lgif" style="width:14px;height:14px;" />`;
} }
// 抢啤酒
let beer = buyBeer();
let popup_node = null;
// 当窗口关闭时关闭所有还存在的通知
let notifies = {count: 0};
window.addEventListener(
'beforeunload',
() => {
if (notifies.count !== 0) {
for (let i = 0; i < notifies.count; i++) {
(notifies[i] !== null) && (notifies[i].close())
}
}
}
);
// 引入rfc方法
let addRFC = window['addRFC'];
// 记录当前窗口唯一id // 记录当前窗口唯一id
const isWindowActive = getWindowActiveState(); const isWindowActive = getWindowActiveState();
@ -316,7 +291,7 @@ export default function userscript(glob: Global): void {
tip: '海外落地后每30秒通知警告', tip: '海外落地后每30秒通知警告',
}); });
// 落地转跳 // 落地转跳
setting_list.push({domType: 'button', domId: '', domText: '落地转跳', clickFunc: landedRedirect}); setting_list.push({ domType: 'button', domId: '', domText: '落地转跳', clickFunc: landedRedirect });
// 公司 // 公司
setting_list.push({ setting_list.push({
@ -383,7 +358,7 @@ export default function userscript(glob: Global): void {
domId: '', domId: '',
domText: '啤酒提醒状态', domText: '啤酒提醒状态',
clickFunc: function () { clickFunc: function () {
WHNotify(`啤酒提醒${beer.status()}`); WHNotify(`啤酒提醒${ beer.status() }`);
} }
}); });
// 啤酒提醒时间 // 啤酒提醒时间
@ -394,7 +369,7 @@ export default function userscript(glob: Global): void {
// tip: '通知提前时间', // tip: '通知提前时间',
clickFunc: function () { clickFunc: function () {
popup_node.close(); popup_node.close();
let popup = popupMsg(`<label>提前提醒时间(秒)<input type="number" value="${getWhSettingObj()['_15AlarmTime']}" /></label><p>区间为 1 ~ 60默认 50</p>`, '啤酒提醒时间设定'); let popup = popupMsg(`<label>提前提醒时间(秒)<input type="number" value="${ getWhSettingObj()['_15AlarmTime'] }" /></label><p>区间为 1 ~ 60默认 50</p>`, '啤酒提醒时间设定');
let confirm = document.createElement('button'); let confirm = document.createElement('button');
confirm.innerHTML = '确定'; confirm.innerHTML = '确定';
confirm.style.float = 'right'; confirm.style.float = 'right';
@ -541,7 +516,7 @@ export default function userscript(glob: Global): void {
setting_list.push({ setting_list.push({
domType: 'checkbox', domType: 'checkbox',
domId: 'wh-dev-mode', domId: 'wh-dev-mode',
domText: ` 开发者模式${isDev() ? ' <button id="wh-devInfo">详情</button>' : ''}`, domText: ` 开发者模式${ isDev() ? ' <button id="wh-devInfo">详情</button>' : '' }`,
dictName: 'isDev', dictName: 'isDev',
isHide: true, isHide: true,
}); });
@ -563,39 +538,39 @@ export default function userscript(glob: Global): void {
menu_list.push({ menu_list.push({
domType: 'plain', domType: 'plain',
domId: 'wh-trans-welcome', domId: 'wh-trans-welcome',
domHTML: `<span>欢迎 <a href="/profiles.php?XID=${player_info.userID}" target="_blank">${player_info.playername}</a>[${player_info.userID}] 大佬</span>`, domHTML: `<span>欢迎 <a href="/profiles.php?XID=${ player_info.userID }" target="_blank">${ player_info.playername }</a>[${ player_info.userID }] 大佬</span>`,
}); });
} }
// 节日 // 节日
let fest_date_html = '<button>节日</button>: '; let fest_date_html = '<button>节日</button>: ';
{ {
const fest_date_dict = { const fest_date_dict = {
'0105': {name: '周末自驾游', eff: '获得双倍的赛车点数与赛车技能等级增益'}, '0105': { name: '周末自驾游', eff: '获得双倍的赛车点数与赛车技能等级增益' },
'0114': {name: '情人节', eff: '使用爱情果汁(Love Juice)后获得降低攻击与复活的能量消耗的增益'}, '0114': { name: '情人节', eff: '使用爱情果汁(Love Juice)后获得降低攻击与复活的能量消耗的增益' },
'0204': {name: '员工激励日', eff: '获得三倍的工作点数与火车增益'}, '0204': { name: '员工激励日', eff: '获得三倍的工作点数与火车增益' },
'0217': {name: '圣帕特里克日', eff: '获得双倍的酒类效果增益,城市中可以捡到绿色世涛(Green Stout)'}, '0217': { name: '圣帕特里克日', eff: '获得双倍的酒类效果增益,城市中可以捡到绿色世涛(Green Stout)' },
'0320': {name: '420日', eff: '获得三倍的大麻(Cannabis)效果增益'}, '0320': { name: '420日', eff: '获得三倍的大麻(Cannabis)效果增益' },
'0418': {name: '博物馆日', eff: '获得10%提高的博物馆PT兑换增益'}, '0418': { name: '博物馆日', eff: '获得10%提高的博物馆PT兑换增益' },
'0514': {name: '世界献血日', eff: '获得减半的抽血CD和扣血增益'}, '0514': { name: '世界献血日', eff: '获得减半的抽血CD和扣血增益' },
'0611': {name: '世界人口日', eff: '获得双倍的通过攻击获取的经验的增益'}, '0611': { name: '世界人口日', eff: '获得双倍的通过攻击获取的经验的增益' },
'0629': {name: '世界老虎日', eff: '获得5倍的狩猎技能增益'}, '0629': { name: '世界老虎日', eff: '获得5倍的狩猎技能增益' },
'0705': {name: '国际啤酒节', eff: '获得5倍的啤酒物品效果增益'}, '0705': { name: '国际啤酒节', eff: '获得5倍的啤酒物品效果增益' },
'0827': {name: '旅游节', eff: '获得双倍的起飞后物品携带容量增益'}, '0827': { name: '旅游节', eff: '获得双倍的起飞后物品携带容量增益' },
'0915': {name: '饮料节', eff: '获得双倍的能量饮料效果增益'}, '0915': { name: '饮料节', eff: '获得双倍的能量饮料效果增益' },
'1014': {name: '世界糖尿病日', eff: '获得三倍的糖类效果增益'}, '1014': { name: '世界糖尿病日', eff: '获得三倍的糖类效果增益' },
'1015': {name: '周年庆', eff: '左上角的TORN图标可以食用'}, '1015': { name: '周年庆', eff: '左上角的TORN图标可以食用' },
'1025': {name: '黑色星期五', eff: '某些商家将提供1元购活动'}, '1025': { name: '黑色星期五', eff: '某些商家将提供1元购活动' },
'1114': {name: '住院日', eff: '获得降低75%的住院时间增益'}, '1114': { name: '住院日', eff: '获得降低75%的住院时间增益' },
}; };
menu_list.fest_date_dict = fest_date_dict; menu_list.fest_date_dict = fest_date_dict;
menu_list.fest_date_list = Object.keys(fest_date_dict); menu_list.fest_date_list = Object.keys(fest_date_dict);
const formatMMDD = (m, d) => { const formatMMDD = (m, d) => {
const MM = m < 10 ? `0${m}` : m.toString(); const MM = m < 10 ? `0${ m }` : m.toString();
const DD = d < 10 ? `0${d}` : d.toString(); const DD = d < 10 ? `0${ d }` : d.toString();
return MM + DD; return MM + DD;
} }
const fest_date_key = formatMMDD(date.getUTCMonth(), date.getUTCDate()); const fest_date_key = formatMMDD(date.getUTCMonth(), date.getUTCDate());
if (fest_date_dict[fest_date_key]) fest_date_html += `今天 - ${fest_date_dict[fest_date_key]['name']}(<button title="${fest_date_dict[fest_date_key]['eff']}">效果</button>)`; if (fest_date_dict[fest_date_key]) fest_date_html += `今天 - ${ fest_date_dict[fest_date_key]['name'] }(<button title="${ fest_date_dict[fest_date_key]['eff'] }">效果</button>)`;
else { else {
// 月日列表 // 月日列表
let fest_date_list = Object.keys(fest_date_dict); let fest_date_list = Object.keys(fest_date_dict);
@ -611,7 +586,7 @@ export default function userscript(glob: Global): void {
fest_date_list[next_fest_date_index !== fest_date_list.length ? next_fest_date_index : 0].slice(2) / 1, fest_date_list[next_fest_date_index !== fest_date_list.length ? next_fest_date_index : 0].slice(2) / 1,
8 8
) - date) / 86400000 | 0; ) - date) / 86400000 | 0;
fest_date_html += `${days_left}天后 - ${next_fest_date.name}(<button title="${next_fest_date.eff}">效果</button>)`; fest_date_html += `${ days_left }天后 - ${ next_fest_date.name }(<button title="${ next_fest_date.eff }">效果</button>)`;
} }
} }
menu_list.push({ menu_list.push({
@ -692,8 +667,8 @@ export default function userscript(glob: Global): void {
}); });
eventObj.html = '<button>活动</button>: '; eventObj.html = '<button>活动</button>: ';
eventObj.onEv eventObj.onEv
? eventObj.html += `${eventObj.current.name}(<button title="${eventObj.current.eff}">详情</button>) - 剩余${eventObj.daysLeft}` ? eventObj.html += `${ eventObj.current.name }(<button title="${ eventObj.current.eff }">详情</button>) - 剩余${ eventObj.daysLeft }`
: eventObj.html += `${eventObj.daysLeft}天后 - ${eventObj.next.name}(<button title="${eventObj.next.eff}">详情</button>)`; : eventObj.html += `${ eventObj.daysLeft }天后 - ${ eventObj.next.name }(<button title="${ eventObj.next.eff }">详情</button>)`;
menu_list.push({ menu_list.push({
domType: 'plain', domType: 'plain',
domId: 'wh-trans-event-cont', domId: 'wh-trans-event-cont',
@ -780,7 +755,7 @@ info{display:block;}
`; `;
const [dest_node, type_node] = node.querySelectorAll('select'); const [dest_node, type_node] = node.querySelectorAll('select');
node.querySelector('button').addEventListener('click', () => { node.querySelector('button').addEventListener('click', () => {
sessionStorage['wh-quick-fly'] = `${dest_node.selectedIndex} ${type_node.selectedIndex} ${new Date().getTime()}`; sessionStorage['wh-quick-fly'] = `${ dest_node.selectedIndex } ${ type_node.selectedIndex } ${ new Date().getTime() }`;
if (!href.contains('travelagency.php')) { if (!href.contains('travelagency.php')) {
WHNotify('正在转跳'); WHNotify('正在转跳');
location.href = 'https://www.torn.com/travelagency.php'; location.href = 'https://www.torn.com/travelagency.php';
@ -816,13 +791,13 @@ info{display:block;}
['~9时54分', '~6时56分', '~4时58分', '~2时58分',], ['~9时54分', '~6时56分', '~4时58分', '~2时58分',],
]; ];
const showTime = function () { const showTime = function () {
time_predict.innerHTML = `往返时间:${predict[dest_node.selectedIndex][type_node.selectedIndex]}`; time_predict.innerHTML = `往返时间:${ predict[dest_node.selectedIndex][type_node.selectedIndex] }`;
} }
dest_node.addEventListener('change', showTime); dest_node.addEventListener('change', showTime);
type_node.addEventListener('change', showTime); type_node.addEventListener('change', showTime);
document.body.append(node); document.body.append(node);
showTime(); showTime();
yaoCD.innerHTML = `药CD剩余${getYaoCD()}`; yaoCD.innerHTML = `药CD剩余${ getYaoCD() }`;
}, },
}); });
// NPC LOOT // NPC LOOT
@ -840,7 +815,7 @@ info{display:block;}
<li><a href="https://www.torn.com/loader.php?sid=attack&user2ID=20" target="_blank">Fernando()</a></li> <li><a href="https://www.torn.com/loader.php?sid=attack&user2ID=20" target="_blank">Fernando()</a></li>
<li><a href="https://www.torn.com/loader.php?sid=attack&user2ID=21" target="_blank">Tiny()</a></li> <li><a href="https://www.torn.com/loader.php?sid=attack&user2ID=21" target="_blank">Tiny()</a></li>
</ul> </ul>
<div><img alt="stock.png" src="https://jjins.github.io/t2i/loot.png?${performance.now()}" style="max-width:100%;display:block;margin:0 auto;" /></div>`; <div><img alt="stock.png" src="https://jjins.github.io/t2i/loot.png?${ performance.now() }" style="max-width:100%;display:block;margin:0 auto;" /></div>`;
popupMsg(insert, 'NPC LOOT'); popupMsg(insert, 'NPC LOOT');
}, },
tip: '显示5个可击杀NPC的开打时间', tip: '显示5个可击杀NPC的开打时间',
@ -866,8 +841,8 @@ info{display:block;}
<input type="radio" name="wh-nnb-check-select" value="bw" checked/><b> PDA ()</b> <input type="radio" name="wh-nnb-check-select" value="bw" checked/><b> PDA ()</b>
<p>APIKeyPDA提供</p> <p>APIKeyPDA提供</p>
<p>使APIKey<br/> <p>使APIKey<br/>
<input readonly value="${localStorage.getItem('APIKey') || '不可用'}">()<br/> <input readonly value="${ localStorage.getItem('APIKey') || '不可用' }">()<br/>
<input readonly value="${isPDA ? PDA_APIKey : '不可用'}">(PDA)</p> <input readonly value="${ isPDA ? PDA_APIKey : '不可用' }">(PDA)</p>
</label> </label>
<label> <label>
<input type="radio" name="wh-nnb-check-select" value="ori"/><b> </b> <input type="radio" name="wh-nnb-check-select" value="ori"/><b> </b>
@ -885,11 +860,11 @@ info{display:block;}
// API 计算 // API 计算
if (select.checked) { if (select.checked) {
const api_key = isPDA ? PDA_APIKey : window.localStorage.getItem('APIKey'); const api_key = isPDA ? PDA_APIKey : window.localStorage.getItem('APIKey');
fetch(`https://api.torn.com/user/?selections=bars,perks&key=${api_key}`) fetch(`https://api.torn.com/user/?selections=bars,perks&key=${ api_key }`)
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
if (data['error']) { if (data['error']) {
node.innerHTML = `出错了 ${Obj2Str(data['error'])}`; node.innerHTML = `出错了 ${ Obj2Str(data['error']) }`;
ev.target.style.display = null; ev.target.style.display = null;
return; return;
} }
@ -901,7 +876,7 @@ info{display:block;}
s.includes('maximum nerve') && (perks += /[0-9]./.exec(s)[0] | 0) s.includes('maximum nerve') && (perks += /[0-9]./.exec(s)[0] | 0)
}) })
}); });
node.innerHTML = `NNB: ${nb - perks}`; node.innerHTML = `NNB: ${ nb - perks }`;
ev.target.style.display = null; ev.target.style.display = null;
}); });
} }
@ -914,7 +889,7 @@ info{display:block;}
const str = elem.innerText.toLowerCase(); const str = elem.innerText.toLowerCase();
str.includes('maximum nerve') && (perks += /[0-9]./.exec(str)[0] | 0) str.includes('maximum nerve') && (perks += /[0-9]./.exec(str)[0] | 0)
}); });
node.innerHTML = `NNB: ${nb - perks}`; node.innerHTML = `NNB: ${ nb - perks }`;
ev.target.style.display = null; ev.target.style.display = null;
return; return;
} }
@ -1016,7 +991,7 @@ background-size: 100% auto !important;
}); });
let insert = '<p>'; let insert = '<p>';
quick_link_dict.forEach(el => { quick_link_dict.forEach(el => {
insert += `<a href="${el.url}"${el.new_tab ? ' target="_blank"' : ''}><span class="wh-link-collection-img" style="background: url(${el.img})"></span><span>${el.name}</span></a>`; insert += `<a href="${ el.url }"${ el.new_tab ? ' target="_blank"' : '' }><span class="wh-link-collection-img" style="background: url(${ el.img })"></span><span>${ el.name }</span></a>`;
}); });
insert += '</p>' insert += '</p>'
let popup = popupMsg(insert, '常用链接'); let popup = popupMsg(insert, '常用链接');
@ -1053,11 +1028,11 @@ background-size: 100% auto !important;
</style> </style>
<p>-1</p> <p>-1</p>
<p>APIKeyAPIKey为<br/> <p>APIKeyAPIKey为<br/>
<input readonly value="${localStorage.getItem('APIKey') || '不可用'}">()<br/> <input readonly value="${ localStorage.getItem('APIKey') || '不可用' }">()<br/>
<input readonly value="${isPDA ? PDA_APIKey : '不可用'}">(PDA) <input readonly value="${ isPDA ? PDA_APIKey : '不可用' }">(PDA)
</p> </p>
<p><b>PT</b><label> $ <input type="number" value="${watcher_conf['pt'] || -1}" /></label></p> <p><b>PT</b><label> $ <input type="number" value="${ watcher_conf['pt'] || -1 }" /></label></p>
<p><b>XAN</b><label> $ <input type="number" value="${watcher_conf['xan'] || -1}" /></label></p> <p><b>XAN</b><label> $ <input type="number" value="${ watcher_conf['xan'] || -1 }" /></label></p>
<p><button></button></p> <p><button></button></p>
`; `;
const popup = popupMsg(html, '价格监视设置'); const popup = popupMsg(html, '价格监视设置');
@ -1078,7 +1053,7 @@ background-size: 100% auto !important;
clickFunc: function () { clickFunc: function () {
// 弹出小窗口 // 弹出小窗口
const ifHTML = `<iframe src="/crimes.php?step=main" style="width:100%;max-width: 450px;margin: 0 auto;display: none;height: 340px;"></iframe>`; const ifHTML = `<iframe src="/crimes.php?step=main" style="width:100%;max-width: 450px;margin: 0 auto;display: none;height: 340px;"></iframe>`;
const popup_insert = `<p>加载中请稍后${loading_gif_html()}</p><div id="wh-quick-crime-if-container"></div>`; const popup_insert = `<p>加载中请稍后${ loading_gif_html() }</p><div id="wh-quick-crime-if-container"></div>`;
const $popup = popupMsg(popup_insert, '小窗快速犯罪'); const $popup = popupMsg(popup_insert, '小窗快速犯罪');
// 运行状态node // 运行状态node
let loading_node = $popup.querySelector('p:first-of-type'); let loading_node = $popup.querySelector('p:first-of-type');
@ -1141,9 +1116,9 @@ background-size: 100% auto !important;
new MutationObserver((m, o) => { new MutationObserver((m, o) => {
o.disconnect(); o.disconnect();
if (!elem.querySelector('.wh-translate')) elem.prepend(mobile_prepend_node); if (!elem.querySelector('.wh-translate')) elem.prepend(mobile_prepend_node);
o.observe(elem, {childList: true, subtree: true}); o.observe(elem, { childList: true, subtree: true });
}) })
.observe(elem, {childList: true, subtree: true}); .observe(elem, { childList: true, subtree: true });
}); });
// 隐藏返回顶部按钮 // 隐藏返回顶部按钮
elementReady('#go-to-top-btn button', ifDocu).then(e => e.style.display = 'none'); elementReady('#go-to-top-btn button', ifDocu).then(e => e.style.display = 'none');
@ -1185,7 +1160,7 @@ background-size: 100% auto !important;
clickFunc: function (e) { clickFunc: function (e) {
e.target.blur(); e.target.blur();
const insert = `<p>即将打开危险功能,使用这些功能可能会造成账号封禁。请自行考虑是否使用。</p> const insert = `<p>即将打开危险功能,使用这些功能可能会造成账号封禁。请自行考虑是否使用。</p>
<p><label><input type="checkbox" ${getWhSettingObj()['dangerZone'] ? 'checked ' : ' '}/> </label></p> <p><label><input type="checkbox" ${ getWhSettingObj()['dangerZone'] ? 'checked ' : ' ' }/> </label></p>
<div><button disabled></button></div>`; <div><button disabled></button></div>`;
const popup = popupMsg(insert, '⚠️警告'); const popup = popupMsg(insert, '⚠️警告');
const warning_check = popup.querySelector('input'); const warning_check = popup.querySelector('input');
@ -1216,8 +1191,35 @@ background-size: 100% auto !important;
}); });
// 更新历史 // 更新历史
menu_list.push({ menu_list.push({
domType: 'button', domId: '', domText: '🐞 更新历史', clickFunc: () => { domType: 'button', domId: '', domText: '🐞 更新历史', clickFunc: async () => {
popupMsg('更新历史现已迁移:<br/><a target="_blank" href="https://gitlab.com/JJins/wuhu-torn-helper/-/blob/dev/CHANGELOG.md">https://gitlab.com/JJins/wuhu-torn-helper/-/blob/dev/CHANGELOG.md</a>', '更新历史'); let popup = popupMsg(
'更新历史:<br/><a target="_blank" href="https://gitlab.com/JJins/wuhu-torn-helper/-/blob/dev/CHANGELOG.md">https://gitlab.com/JJins/wuhu-torn-helper/-/blob/dev/CHANGELOG.md</a><br/>',
'更新历史'
);
popup.classList.add('wh-changelog');
let progressBar = document.createElement('div');
progressBar.style.height = '2px';
progressBar.style.width = '1%';
progressBar.style.backgroundColor = 'red';
let progressText = document.createElement('p');
progressText.innerText = '加载更新文件……';
progressText.style.textAlign = 'center';
let style = document.createElement('style');
style.innerHTML = `.wh-changelog h2,.wh-changelog h3,.wh-changelog h4 {margin:8px 0;}.wh-changelog li{list-style: inside;}`;
popup.append(progressBar, progressText, style);
let update = await COFetch('https://gitlab.com/JJins/wuhu-torn-helper/-/raw/dev/CHANGELOG.md?' + Date.now());
progressBar.style.width = '60%';
progressText.innerText = '解析中……';
let md = mdParse(update);
popup.append(md);
progressBar.style.width = '100%';
progressText.innerText = '加载完成';
setTimeout(() => {
progressBar.remove();
progressText.remove()
}, 3000);
}, },
}); });
// 助手设置 // 助手设置
@ -1240,16 +1242,16 @@ background-size: 100% auto !important;
} }
const insert = `<table id="wh-dev-info-tb"> const insert = `<table id="wh-dev-info-tb">
<tr><td>URL</td><td>${window.location.href}</td></tr> <tr><td>URL</td><td>${ window.location.href }</td></tr>
<tr><td></td><td>${window.innerWidth}x${window.innerHeight}</td></tr> <tr><td></td><td>${ window.innerWidth }x${ window.innerHeight }</td></tr>
<tr><td></td><td>${getDeviceType().toUpperCase()}</td></tr> <tr><td></td><td>${ getDeviceType().toUpperCase() }</td></tr>
<tr><td></td><td>${{'gm': '油猴', 'raw': '直接运行', 'pda': 'TornPDA'}[getScriptEngine()]}</td></tr> <tr><td></td><td>${ { 'gm': '油猴', 'raw': '直接运行', 'pda': 'TornPDA' }[getScriptEngine()] }</td></tr>
<tr><td></td><td>${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}</td></tr> <tr><td></td><td>${ date.getFullYear() }/${ date.getMonth() + 1 }/${ date.getDate() } ${ date.getHours() }:${ date.getMinutes() }:${ date.getSeconds() }</td></tr>
<tr><td></td><td>${version}</td></tr> <tr><td></td><td>${ version }</td></tr>
<tr><td></td><td>${os}</td></tr> <tr><td></td><td>${ os }</td></tr>
<tr><td>UA</td><td>${window.navigator.userAgent}</td></tr> <tr><td>UA</td><td>${ window.navigator.userAgent }</td></tr>
<tr><td>ID</td><td>${player_info.userID}</td></tr> <tr><td>ID</td><td>${ player_info.userID }</td></tr>
<tr><td></td><td>${player_info.playername}</td></tr> <tr><td></td><td>${ player_info.playername }</td></tr>
</table> </table>
<style> <style>
#wh-dev-info-tb td{ #wh-dev-info-tb td{
@ -1268,8 +1270,9 @@ color:black;
domType: 'button', domType: 'button',
domId: '', domId: '',
domText: '📐️ 测试', domText: '📐️ 测试',
clickFunc: function () { clickFunc: async function () {
WHNotify('芜湖助手', {sysNotify: true, timeout: 15}) let res = await COFetch('https://gitlab.com/JJins/wuhu-torn-helper/-/raw/dev/CHANGELOG.md')
log(mdParse(res))
}, },
}); });
// endregion // endregion
@ -3260,7 +3263,8 @@ margin: 0 0 3px;
addActionBtn('公司存钱', companyDepositAnywhere, $zhongNode); addActionBtn('公司存钱', companyDepositAnywhere, $zhongNode);
} }
if (getPlayerInfo()['userID'] === 2687093) { if (getPlayerInfo()['userID'] === 2687093 && getDeviceType() === Device.PC) {
await getSidebarData();
let item = document.getElementById('nav-items'); let item = document.getElementById('nav-items');
if (item) { if (item) {
let copy = item.cloneNode(true); let copy = item.cloneNode(true);
@ -4200,22 +4204,7 @@ margin: 0 0 3px;
} }
} }
/**
* style
* @param {CSSRule.cssText|String} css CSS规则
*/
function addStyle(css) {
let wh_gStyle = document.querySelector('style#wh-trans-gStyle');
if (wh_gStyle) {
wh_gStyle.innerHTML += css;
} else {
wh_gStyle = document.createElement("style");
wh_gStyle.id = 'wh-trans-gStyle';
wh_gStyle.innerHTML = css;
document.head.append(wh_gStyle);
}
log('CSS规则已添加', wh_gStyle);
}
/* /*
@ -4396,18 +4385,7 @@ margin: 0 0 3px;
}); });
} }
// 得到一个两数之间的随机整数
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min; //不含最大值,含最小值
}
// 用户脚本平台类型
function getScriptEngine() {
return UWCopy ? UserScriptEngine.GM : PDA_APIKey.slice(-1) !== '#'
? UserScriptEngine.PDA : UserScriptEngine.RAW;
}
// 用户设备类型 对应PC MOBILE TABLET // 用户设备类型 对应PC MOBILE TABLET
function getDeviceType() { function getDeviceType() {
@ -4415,209 +4393,12 @@ margin: 0 0 3px;
? Device.PC : window.innerWidth <= 600 ? Device.MOBILE : Device.TABLET; ? Device.PC : window.innerWidth <= 600 ? Device.MOBILE : Device.TABLET;
} }
// 跨域get请求 返回text
function COFetch(url, method = 'get', body = null) {
const engine = getScriptEngine();
switch (engine) {
case UserScriptEngine.RAW: {
return new Promise((_, reject) => {
console.error(`[wh] 跨域请求错误:${UserScriptEngine.RAW}环境下无法进行跨域请求`);
reject(`错误:${UserScriptEngine.RAW}环境下无法进行跨域请求`);
});
}
case UserScriptEngine.PDA: {
const {PDA_httpGet, PDA_httpPost} = window;
return method === 'get' ?
// get
new Promise((resolve, reject) => {
if (typeof PDA_httpGet !== 'function') {
console.error('[wh] 跨域请求错误PDA版本不支持');
reject('错误PDA版本不支持');
}
PDA_httpGet(url)
.catch(e => {
console.error('[wh] 网络错误', e);
reject(`[wh] 网络错误 ${e}`);
})
.then(res => resolve(res.responseText));
}) :
// post
new Promise((resolve, reject) => {
if (typeof PDA_httpPost !== 'function') {
console.error('[wh] 跨域请求错误PDA版本不支持');
reject('错误PDA版本不支持');
}
PDA_httpPost(url, {'content-type': 'application/json'}, body)
.catch(e => {
console.error('[wh] 网络错误', e);
reject(`[wh] 网络错误 ${e}`);
})
.then(res => resolve(res.responseText));
});
}
case UserScriptEngine.GM: {
return new Promise((resolve, reject) => {
if (typeof GM_xmlhttpRequest !== 'function') {
console.error('[wh] 跨域请求错误用户脚本扩展API错误');
reject('错误用户脚本扩展API错误');
}
GM_xmlhttpRequest({
method: method,
url: url,
data: method === 'get' ? null : body,
headers: method === 'get' ? null : {'content-type': 'application/json'},
onload: res => resolve(res.response),
onerror: res => reject(`连接错误 ${JSON.stringify(res)}`),
ontimeout: res => reject(`连接超时 ${JSON.stringify(res)}`),
});
});
}
}
}
// 简单 object 转字符串 // 简单 object 转字符串
function Obj2Str(obj) { function Obj2Str(obj) {
return JSON.stringify(obj); return JSON.stringify(obj);
} }
// console.log改写
function log(...o) {
if (isDev()) console.log('[WH]', ...o)
}
/**
*
* @param {string} msg -
* @param {Object} [options] -
* @param {number} [options.timeout] -
* @param {function} [options.callback] -
* @param {boolean} [options.sysNotify] -
* @param {string} [options.sysNotifyTag] -
* @param {function} [options.sysNotifyClick] -
* @return {HTMLElement}
*/
function WHNotify(msg, options = {}) {
let {
timeout = 3,
callback = doNothing,
sysNotify = false,
sysNotifyTag = '芜湖助手',
sysNotifyClick = () => window.focus()
} = options;
if (!isWindowActive() || isIframe) return null;
const date = new Date();
// 通知的唯一id
const uid = `${date.getHours()}${date.getSeconds()}${date.getMilliseconds()}${getRandomInt(1000, 9999)}`;
// 通知容器id
const node_id = 'wh-notify';
// 通知的容器
let notify_contain = document.querySelector(`#${node_id}`);
// 添加通知到容器
const add_notify = () => {
// 每条通知
const new_node = document.createElement('div');
new_node.id = `wh-notify-${uid}`;
new_node.classList.add('wh-notify-item');
new_node.innerHTML = `<div class="wh-notify-bar"></div>
<div class="wh-notify-cont">
<div class="wh-notify-close"></div>
<div class="wh-notify-msg"><p>${msg}</p></div>
</div>`;
notify_contain.append(new_node);
notify_contain.msgInnerText = new_node.querySelector('.wh-notify-msg').innerText;
// 进度条node
const progressBar = new_node.querySelector('.wh-notify-bar');
// 是否hover
let mouse_enter = false;
new_node.addEventListener('mouseenter', () => mouse_enter = true, true);
new_node.addEventListener('mouseleave', () => mouse_enter = false);
// 通知进度条
let progressCount = 101;
// 删除通知
new_node.close = () => {
clearInterval(intervalID);
new_node.remove();
callback();
};
// 计时器
let intervalID = window.setInterval(() => {
if (mouse_enter) {
progressCount = 101;
progressBar.style.width = '100%';
return;
}
progressCount--;
progressBar.style.width = `${progressCount}%`;
if (progressCount === 0) new_node.remove();
}, timeout * 1000 / 100);
new_node.querySelector('.wh-notify-close').addEventListener('click', new_node.close);
return new_node;
};
// 不存在容器 创建
if (!notify_contain) {
notify_contain = document.createElement('div');
notify_contain.id = node_id;
addStyle(`
#${node_id} {
display: inline-block;
position: fixed;
top: 0;
left: calc(50% - 180px);
width: 360px;
z-index: 9999990;
color:#333;
}
#${node_id} a{
color:red;
text-decoration:none;
}
#${node_id} .wh-notify-item {
/*height: 50px;*/
background: rgb(239 249 255 / 90%);
border-radius: 2px;
margin: 0.5em 0 0 0;
box-shadow: 0 0 5px 0px #959595;
}
#${node_id} .wh-notify-item:hover {
background: rgb(239 249 255 / 98%);
}
#${node_id} .wh-notify-item .wh-notify-bar {
height:2px;
background:#2196f3;
}
#${node_id} .wh-notify-item .wh-notify-close {
float:right;
padding:0;
width:16px;height:16px;
background:url('data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%201024%201024%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M923%20571H130.7c-27.6%200-50-22.4-50-50s22.4-50%2050-50H923c27.6%200%2050%2022.4%2050%2050s-22.4%2050-50%2050z%22%20fill%3D%22%232196f3%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E') no-repeat center;
background-size:100%;
margin: 6px 6px 0 0;
cursor: pointer;
}
#${node_id} .wh-notify-item .wh-notify-msg {
padding:12px;
}
`);
document.body.append(notify_contain);
}
const notify_obj = add_notify();
// 浏览器通知
if (window.Notification && Notification.permission === 'granted' && sysNotify) {
const date_local_string = `[${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}]\r`;
notify_obj.sys_notify = new Notification('芜湖助手', {
body: date_local_string + notify_contain.msgInnerText,
requireInteraction: true,
renotify: true,
tag: sysNotifyTag + getRandomInt(0, 99),
});
notify_obj.sys_notify.onclick = sysNotifyClick;
notify_obj.sys_notify.onshow = () => setTimeout(() => notify_obj.sys_notify.close(), timeout * 1000);
notify_obj.sys_notify.id = notifies.count++;
notifies[notify_obj.sys_notify.id] = notify_obj.sys_notify;
notify_obj.sys_notify.addEventListener('close', () => notifies[notify_obj.sys_notify.id] = null);
}
return notify_obj;
}
// gs loader // gs loader
function loadGS(use) { function loadGS(use) {
@ -4745,19 +4526,6 @@ z-index:100001;
}); });
} }
// 格式化金额数字
function toThousands(num) {
num = (num || 0).toString();
let result = '';
while (num.length > 3) {
result = ',' + num.slice(-3) + result;
num = num.slice(0, num.length - 3);
}
if (num) {
result = num + result;
}
return result;
}
// 插件的配置 getter // 插件的配置 getter
function getWhSettingObj() { function getWhSettingObj() {
@ -4776,75 +4544,7 @@ z-index:100001;
// 价格监视handle
function priceWatcherHandle() {
setInterval(() => {
const price_conf = getWhSettingObj()['priceWatcher'];
const apikey = isPDA ? PDA_APIKey : localStorage.getItem('APIKey');
if (!apikey) {
log('无法获取APIKey')
return;
}
if (price_conf['pt'] !== -1) priceWatcherPt(apikey, price_conf['pt']).then();
if (price_conf['xan'] !== -1) priceWatcherXan(apikey, price_conf['xan']).then();
}, 10000)
return {status: true};
}
// pt价格监视
async function priceWatcherPt(apikey, lower_price) {
if (!priceWatcher['watch-pt-lower-id']) priceWatcher['watch-pt-lower-id'] = [];
const res = await fetch('https://api.torn.com/market/?selections=pointsmarket&key=' + apikey);
const obj = await res.json();
if (obj['pointsmarket']) {
// 过滤低于价格的物品出售id
const lower_arr = [];
let low = Infinity;
Object.keys(obj['pointsmarket']).forEach(key => {
if (obj['pointsmarket'][key]['cost'] <= lower_price) {
lower_arr.push(key);
if (obj['pointsmarket'][key]['cost'] < low) low = obj['pointsmarket'][key]['cost'];
}
});
if (lower_arr.length === 0) return;
// 将id与之前存在的比较不相同时发送通知
if (JSON.stringify(priceWatcher['watch-pt-lower-id']) !== JSON.stringify(lower_arr)) {
priceWatcher['watch-pt-lower-id'] = lower_arr;
WHNotify(`PT新低价$${toThousands(low)}( < $${toThousands(lower_price)}) - <a href="/pmarket.php" target="_blank">点击转跳</a>`, {
timeout: 6,
sysNotify: true,
sysNotifyClick: () => window.open('https://www.torn.com/pmarket.php'),
});
}
} else {
// 查询出错了
log('pt查询出错了')
}
}
// xan价格监视
async function priceWatcherXan(apikey, lower_price) {
// 初始化记录上一个条目的id避免重复发送通知
if (!priceWatcher['watch-xan-lower-id']) priceWatcher['watch-xan-lower-id'] = '';
const res = await fetch('https://api.torn.com/market/206?selections=bazaar&key=' + apikey);
const obj = await res.json();
if (obj['bazaar']) {
const lowest_item = obj['bazaar'][0]
if (lowest_item['cost'] <= lower_price) {
if (priceWatcher['watch-xan-lower-id'] !== lowest_item['ID']) {
priceWatcher['watch-xan-lower-id'] = lowest_item['ID'];
WHNotify(`XAN新低价$${toThousands(lowest_item['cost'])}( < $${toThousands(lower_price)}) - <a href="/imarket.php#/p=shop&step=shop&type=&searchname=Xanax" target="_blank">点击转跳</a>`, {
timeout: 6,
sysNotify: true,
sysNotifyClick: () => window.open('https://www.torn.com/imarket.php#/p=shop&step=shop&type=&searchname=Xanax')
});
}
}
} else {
// 查询出错了
log('xan查询出错了')
}
}
// 空函数 // 空函数
function doNothing() { function doNothing() {
@ -7342,13 +7042,7 @@ z-index:100001;
document.head.appendChild(node); document.head.appendChild(node);
} }
// 玩家状态
function getUserState() {
let obj = {};
let hdd = sessionStorage['headerData'];
if (hdd) obj = JSON.parse(hdd)['user']['state'];
return obj;
}
// 一键起飞 // 一键起飞
function doQuickFly() { function doQuickFly() {
@ -7675,9 +7369,45 @@ z-index:100001;
} }
} }
// 边栏信息 /**
function getSidebarData() { *
return JSON.parse(document.querySelector('#sidebar_data').innerHTML) * @deprecated
* @returns {any}
*/
async function getSidebarData() {
let ret = {};
let sidebar_id = null;
let sessionKeys = Object.keys(sessionStorage);
if (sessionKeys.length < 2) {
// dom获取
let sidebar_menu_list = document.querySelectorAll('#sidebar a span[class*="linkName___"]');
log.info({ sidebar_menu_list })
if (sidebar_menu_list.length === 0) {
// TODO 当前根据侧边栏等待 sessionData
await elementReady('#sidebar a span[class*="linkName___"]');
sidebar_menu_list = document.querySelectorAll('#sidebar a span[class*="linkName___"]');
}
sidebar_menu_list.forEach(node => ret[node.innerHTML.trim().toLowerCase().replaceAll(' ', '_')] = true);
} else {
// session storage获取
for (let key of sessionKeys) {
if (key.startsWith('sidebarData')) {
sidebar_id = JSON.parse(sessionStorage.getItem(key));
break;
}
}
if (sidebar_id !== null) {
for (let area of Object.keys(sidebar_id['areas'])) {
ret[area] = true;
}
}
}
log.info({ ret, sidebar_id, sessionKeys })
if (Object.keys(ret).length === 0) {
log.error('无法获取数据,建议刷新重试');
}
return ret;
} }
/** /**
@ -7746,7 +7476,70 @@ z-index:100001;
return fetch(url, req_params); return fetch(url, req_params);
} }
/**
* Markdown
* @param {String} from
* @param {Number} max_line 500
* @returns {HTMLDivElement}
*/
function mdParse(from, max_line) {
max_line = max_line || 500;
const base = document.createElement('div');
let lines = from.split('\n');
if (lines.length > max_line) {
lines = lines.slice(0, max_line);
lines.push("...");
}
let prev = '';
let child_cont;
lines.forEach(line => {
if (line.trim() === '') return;
let node;
let spl = line.split(' ');
let md_flag = spl[0];
switch (md_flag) {
// 标题
case '#':
case '##':
case '###':
if (prev === 'li') {
child_cont = null;
}
prev = 'h' + (md_flag.length + 1);
node = document.createElement(prev);
node.innerText = line.slice(md_flag.length + 1);
base.append(node);
return;
// 列表
case '-':
if (prev !== 'li') {
child_cont = document.createElement('ul');
if (!base.contains(child_cont)) base.append(child_cont);
}
prev = 'li';
node = document.createElement(prev);
node.innerText = line.slice(2);
child_cont.append(node);
return;
}
prev = 'p';
node = document.createElement(prev);
node.innerText = line.trim();
base.append(node);
})
return base;
}
/**
*
* @param {Number} ms
* @returns {Promise<unknown>}
*/
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
$zhongNode.initTimer.innerHTML = `助手加载时间 ${Date.now() - start_timestamp}ms`; $zhongNode.initTimer.innerHTML = `助手加载时间 ${Date.now() - start_timestamp}ms`;
} }
// export userscript;

View File

@ -12,8 +12,11 @@ async function main() {
} }
} }
// 防止脚本重复运行 // 防止脚本重复运行
if (window.hasOwnProperty('WHTRANS')) return; if (window.hasOwnProperty('WHTRANS')) {
else window.WHTRANS = true; return;
} else {
window.WHTRANS = true;
}
const version = '$$WUHU_DEV_VERSION$$'; const version = '$$WUHU_DEV_VERSION$$';
const isIframe = self !== top; const isIframe = self !== top;
const $ = window['jQuery']; const $ = window['jQuery'];