From a5f0a8221109cf8d0e1bb42a25748a1a51d5dc42 Mon Sep 17 00:00:00 2001
From: Liwanyi
Date: Fri, 9 Sep 2022 18:03:47 +0800
Subject: [PATCH] =?UTF-8?q?TS=E9=87=8D=E6=9E=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
global.d.ts | 42 ++
src/GlobalVars.ts | 3 -
src/dictionary/translation.ts | 38 --
src/func/{ => translate}/eventsTrans.ts | 4 +-
src/func/utils/BuyBeer.ts | 92 ++++
src/func/utils/COFetch.ts | 14 +-
src/func/utils/WHNotify.ts | 148 ++++++
src/func/utils/addStyle.ts | 18 +
src/func/utils/autoFetchJSON.ts | 2 +
src/func/utils/getPlayerInfo.ts | 13 +-
src/func/utils/getRandomInt.ts | 7 +
src/func/utils/getScriptEngine.ts | 9 +
src/func/utils/getUserState.ts | 7 +
src/func/utils/priceWatcherHandle.ts | 77 +++
src/func/utils/toThousands.ts | 13 +
src/init.ts | 95 ++--
src/interface/GlobalVars.ts | 16 +
src/interface/MyHTMLElement.ts | 5 +
src/interface/NotifyWrapper.ts | 5 +
src/interface/PlayerInfo.ts | 4 +
src/main.d.ts | 5 -
src/main.ts | 3 +-
src/userscript.ts | 607 ++++++++----------------
wuhu-torn-helper.js | 7 +-
24 files changed, 727 insertions(+), 507 deletions(-)
create mode 100644 global.d.ts
delete mode 100644 src/GlobalVars.ts
rename src/func/{ => translate}/eventsTrans.ts (99%)
create mode 100644 src/func/utils/BuyBeer.ts
create mode 100644 src/func/utils/WHNotify.ts
create mode 100644 src/func/utils/addStyle.ts
create mode 100644 src/func/utils/getRandomInt.ts
create mode 100644 src/func/utils/getScriptEngine.ts
create mode 100644 src/func/utils/getUserState.ts
create mode 100644 src/func/utils/priceWatcherHandle.ts
create mode 100644 src/func/utils/toThousands.ts
create mode 100644 src/interface/GlobalVars.ts
create mode 100644 src/interface/MyHTMLElement.ts
create mode 100644 src/interface/NotifyWrapper.ts
create mode 100644 src/interface/PlayerInfo.ts
delete mode 100644 src/main.d.ts
diff --git a/global.d.ts b/global.d.ts
new file mode 100644
index 0000000..7652a29
--- /dev/null
+++ b/global.d.ts
@@ -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_httpPost(url: URL | string, init: any, body: any): Promise;
+
+ 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;
+}
\ No newline at end of file
diff --git a/src/GlobalVars.ts b/src/GlobalVars.ts
deleted file mode 100644
index d3e9370..0000000
--- a/src/GlobalVars.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export default interface Global {
- startTimestamp: number;
-}
\ No newline at end of file
diff --git a/src/dictionary/translation.ts b/src/dictionary/translation.ts
index a0594cb..b85fc14 100644
--- a/src/dictionary/translation.ts
+++ b/src/dictionary/translation.ts
@@ -2696,44 +2696,6 @@ export const calDict = {
}
// 中文字符集正则
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))
export * from './translation'
\ No newline at end of file
diff --git a/src/func/eventsTrans.ts b/src/func/translate/eventsTrans.ts
similarity index 99%
rename from src/func/eventsTrans.ts
rename to src/func/translate/eventsTrans.ts
index 35a3492..916e5b3 100644
--- a/src/func/eventsTrans.ts
+++ b/src/func/translate/eventsTrans.ts
@@ -1,5 +1,5 @@
-import {eventsDict, ocList, gymList} from "../dictionary/translation";
-import log from "./utils/log";
+import {eventsDict, ocList, gymList} from "../../dictionary/translation";
+import log from "../utils/log";
export default function eventsTrans(events: JQuery = $('span.mail-link')) {
const index = window.location.href.indexOf('events.php#/step=received') >= 0 ? 1 : 0;
diff --git a/src/func/utils/BuyBeer.ts b/src/func/utils/BuyBeer.ts
new file mode 100644
index 0000000..e1cd3f7
--- /dev/null
+++ b/src/func/utils/BuyBeer.ts
@@ -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 = `啤酒小助手
提醒您:还有不到 50 秒 NPC 的商品就要刷新了,啤酒血包要抢的可以准备咯。
【啤酒店】 【血包店】`
+ loop.skip_today = () => {
+ const date = new Date();
+ setWhSetting('_15_alarm_ignore', [date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()], false);
+ // 通知
+ const notify = WHNotify(`明早8点前将不再提醒 `);
+ // 通知中的取消按钮
+ 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;
+}
\ No newline at end of file
diff --git a/src/func/utils/COFetch.ts b/src/func/utils/COFetch.ts
index acb465f..b8f2e6d 100644
--- a/src/func/utils/COFetch.ts
+++ b/src/func/utils/COFetch.ts
@@ -1,7 +1,8 @@
-// 跨域get请求 返回text
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 {
const engine = getScriptEngine();
switch (engine) {
case UserScriptEngine.RAW: {
@@ -20,11 +21,11 @@ function COFetch(url, method = 'get', body = null) {
reject('错误:PDA版本不支持');
}
PDA_httpGet(url)
+ .then(res => resolve(res.responseText))
.catch(e => {
console.error('[wh] 网络错误', e);
reject(`[wh] 网络错误 ${e}`);
- })
- .then(res => resolve(res.responseText));
+ });
}) :
// post
new Promise((resolve, reject) => {
@@ -33,14 +34,15 @@ function COFetch(url, method = 'get', body = null) {
reject('错误:PDA版本不支持');
}
PDA_httpPost(url, {'content-type': 'application/json'}, body)
+ .then(res => resolve(res.responseText))
.catch(e => {
console.error('[wh] 网络错误', e);
reject(`[wh] 网络错误 ${e}`);
- })
- .then(res => resolve(res.responseText));
+ });
});
}
case UserScriptEngine.GM: {
+ let {GM_xmlhttpRequest} = window;
return new Promise((resolve, reject) => {
if (typeof GM_xmlhttpRequest !== 'function') {
console.error('[wh] 跨域请求错误:用户脚本扩展API错误');
diff --git a/src/func/utils/WHNotify.ts b/src/func/utils/WHNotify.ts
new file mode 100644
index 0000000..e8c6401
--- /dev/null
+++ b/src/func/utils/WHNotify.ts
@@ -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 = `
+`;
+ 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;
+}
\ No newline at end of file
diff --git a/src/func/utils/addStyle.ts b/src/func/utils/addStyle.ts
new file mode 100644
index 0000000..e5aaac6
--- /dev/null
+++ b/src/func/utils/addStyle.ts
@@ -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);
+}
diff --git a/src/func/utils/autoFetchJSON.ts b/src/func/utils/autoFetchJSON.ts
index aecfb05..30afc15 100644
--- a/src/func/utils/autoFetchJSON.ts
+++ b/src/func/utils/autoFetchJSON.ts
@@ -1,3 +1,5 @@
+import COFetch from "./COFetch";
+
/**
* 循环获取json对象
* @param dest
diff --git a/src/func/utils/getPlayerInfo.ts b/src/func/utils/getPlayerInfo.ts
index a612861..acf8306 100644
--- a/src/func/utils/getPlayerInfo.ts
+++ b/src/func/utils/getPlayerInfo.ts
@@ -3,18 +3,11 @@
* @return {PlayerInfo} rs
*/
function getPlayerInfo(): PlayerInfo {
- let rs = new PlayerInfo();
const node = document.querySelector('script[uid]');
- if (node) {
- rs.playername = node.getAttribute('name');
- rs.userID = node.getAttribute('uid') as unknown as number;
+ if (node) return {
+ playername: node.getAttribute('name'),
+ userID: node.getAttribute('uid') as unknown as number,
}
- return rs;
-}
-
-class PlayerInfo {
- playername: string
- userID: number
}
export default getPlayerInfo
\ No newline at end of file
diff --git a/src/func/utils/getRandomInt.ts b/src/func/utils/getRandomInt.ts
new file mode 100644
index 0000000..cfee587
--- /dev/null
+++ b/src/func/utils/getRandomInt.ts
@@ -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;
+}
diff --git a/src/func/utils/getScriptEngine.ts b/src/func/utils/getScriptEngine.ts
new file mode 100644
index 0000000..6125ba3
--- /dev/null
+++ b/src/func/utils/getScriptEngine.ts
@@ -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;
+}
\ No newline at end of file
diff --git a/src/func/utils/getUserState.ts b/src/func/utils/getUserState.ts
new file mode 100644
index 0000000..568e492
--- /dev/null
+++ b/src/func/utils/getUserState.ts
@@ -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;
+}
\ No newline at end of file
diff --git a/src/func/utils/priceWatcherHandle.ts b/src/func/utils/priceWatcherHandle.ts
new file mode 100644
index 0000000..7b4e85c
--- /dev/null
+++ b/src/func/utils/priceWatcherHandle.ts
@@ -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)}) - 点击转跳`, {
+ 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)}) - 点击转跳`, {
+ timeout: 6,
+ sysNotify: true,
+ sysNotifyClick: () => window.open('https://www.torn.com/imarket.php#/p=shop&step=shop&type=&searchname=Xanax')
+ });
+ }
+ }
+ } else {
+ // 查询出错了
+ log('xan查询出错了')
+ }
+}
\ No newline at end of file
diff --git a/src/func/utils/toThousands.ts b/src/func/utils/toThousands.ts
new file mode 100644
index 0000000..7458d6d
--- /dev/null
+++ b/src/func/utils/toThousands.ts
@@ -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;
+}
\ No newline at end of file
diff --git a/src/init.ts b/src/init.ts
index dfdf215..6f7542f 100644
--- a/src/init.ts
+++ b/src/init.ts
@@ -1,53 +1,55 @@
-import './global'
+import '../global'
import log from "./func/utils/log";
import getWhSettingObj from "./func/utils/getWhSettingObj";
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"];
- try {
- window = UWCopy || window;
- } catch {
+// 初始化方法,获取必要全局参数
+export default function init(glob: Global) {
+ glob.window = window;
+ window.WHPARAMS = glob;
+ let UWCopy = null;
+ if (window.hasOwnProperty('unsafeWindow')) {
+ UWCopy = window.unsafeWindow;
+ try {
+ window = UWCopy;
+ } catch {
+ }
}
- // 防止脚本重复运行
- if (window["WHTRANS"]) throw new DOMException('已在运行');
- window["WHTRANS"] = true;
+ glob.UWCopy = UWCopy;
// 脚本版本
- const version = '$$WUHU_DEV_VERSION$$';
+ glob.version = '$$WUHU_DEV_VERSION$$';
// iframe运行
- const isIframe = self !== top;
- const $ = window['jQuery'];
- // PDA
- const PDA_APIKey = '###PDA-APIKEY###';
- const isPDA = PDA_APIKey.slice(-1) !== '#';
+ glob.isIframe = self !== top;
- // 通知权限
+ // PDA
+ glob.PDA_APIKey = '###PDA-APIKEY###';
+ glob.isPDA = glob.PDA_APIKey.slice(-1) !== '#';
+
+ // 请求通知权限
if (window.Notification) {
- Notification.requestPermission().then(status => {
- // 这将使我们能在 Chrome/Safari 中使用 Notification.permission
- if (Notification.permission !== status) {
- // @ts-ignore
- Notification['permission'] = status;
- }
- })
+ Notification.requestPermission().then();
}
- // regexp test
- String.prototype.contains = function (keywords: RegExp) {
- let that: String = this;
+ // 扩展String正则方法
+ String.prototype.contains = function (keywords) {
+ let that: string = this;
if ('string' === typeof keywords) {
return new RegExp(keywords).test(that);
- }
- if (keywords.test) {
+ } else {
return keywords.test(that);
}
};
- // region 监听fetch
+ // 监听fetch
const ori_fetch = window.fetch;
- window.fetch = async (url, init) => {
+ window.fetch = async (url: string, init: RequestInit) => {
if (url.contains('newsTickers')) {
// 阻止获取新闻横幅
return new Response('{}');
@@ -59,9 +61,36 @@ export default function init() {
}
let clone = res.clone();
let text = await res.text();
- log({url, init, text});
+ log.info({url, init, text});
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())
+ }
+ }
+ }
+ );
}
\ No newline at end of file
diff --git a/src/interface/GlobalVars.ts b/src/interface/GlobalVars.ts
new file mode 100644
index 0000000..295cd2d
--- /dev/null
+++ b/src/interface/GlobalVars.ts
@@ -0,0 +1,16 @@
+import Device from "../enum/Device";
+
+export default interface Global {
+ notifies?: NotifyWrapper;
+ priceWatcher?: { status: boolean };
+ fstock?: { get: () => Promise };
+ player_info?: PlayerInfo;
+ device?: Device;
+ isPDA?: boolean;
+ PDA_APIKey?: string;
+ isIframe?: boolean;
+ version?: string;
+ window?: Window;
+ UWCopy?: Window & typeof globalThis;
+ startTimestamp: number;
+}
\ No newline at end of file
diff --git a/src/interface/MyHTMLElement.ts b/src/interface/MyHTMLElement.ts
new file mode 100644
index 0000000..98bf284
--- /dev/null
+++ b/src/interface/MyHTMLElement.ts
@@ -0,0 +1,5 @@
+interface MyHTMLElement extends HTMLElement {
+ sys_notify?: Notification;
+ msgInnerText?: string;
+ close?: () => void;
+}
\ No newline at end of file
diff --git a/src/interface/NotifyWrapper.ts b/src/interface/NotifyWrapper.ts
new file mode 100644
index 0000000..f3b6a01
--- /dev/null
+++ b/src/interface/NotifyWrapper.ts
@@ -0,0 +1,5 @@
+interface NotifyWrapper {
+ count: number;
+
+ [notifyId: number]: Notification;
+}
\ No newline at end of file
diff --git a/src/interface/PlayerInfo.ts b/src/interface/PlayerInfo.ts
new file mode 100644
index 0000000..b363c10
--- /dev/null
+++ b/src/interface/PlayerInfo.ts
@@ -0,0 +1,4 @@
+interface PlayerInfo {
+ playername: string
+ userID: number
+}
\ No newline at end of file
diff --git a/src/main.d.ts b/src/main.d.ts
deleted file mode 100644
index 13f7325..0000000
--- a/src/main.d.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-declare interface String {
-
- contains(keywords: RegExp|String): boolean
-
-}
diff --git a/src/main.ts b/src/main.ts
index 8f85ec3..2f1eea5 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,7 +1,8 @@
import userscript from "./userscript";
-import Global from "./global";
+import Global from "./interface/GlobalVars";
const glob: Global = {
startTimestamp: -1,
};
+
userscript(glob);
\ No newline at end of file
diff --git a/src/userscript.ts b/src/userscript.ts
index d2a5244..673aea8 100644
--- a/src/userscript.ts
+++ b/src/userscript.ts
@@ -43,46 +43,21 @@ import * as DICTION from './dictionary/translation'
import Device from "./enum/Device";
import UserScriptEngine from "./enum/UserScriptEngine";
import getPlayerInfo from "./func/utils/getPlayerInfo";
-import autoFetchJSON from "./func/utils/autoFetchJSON";
-import Global from "./global";
+import Global from "./interface/GlobalVars";
export default function userscript(glob: Global): void {
glob.startTimestamp = Date.now();
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 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
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`
return `
`;
}
- // 抢啤酒
- 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
const isWindowActive = getWindowActiveState();
@@ -316,7 +291,7 @@ export default function userscript(glob: Global): void {
tip: '海外落地后每30秒通知警告',
});
// 落地转跳
- setting_list.push({domType: 'button', domId: '', domText: '落地转跳', clickFunc: landedRedirect});
+ setting_list.push({ domType: 'button', domId: '', domText: '落地转跳', clickFunc: landedRedirect });
// 公司
setting_list.push({
@@ -383,7 +358,7 @@ export default function userscript(glob: Global): void {
domId: '',
domText: '啤酒提醒状态',
clickFunc: function () {
- WHNotify(`啤酒提醒${beer.status()}`);
+ WHNotify(`啤酒提醒${ beer.status() }`);
}
});
// 啤酒提醒时间
@@ -394,7 +369,7 @@ export default function userscript(glob: Global): void {
// tip: '通知提前时间',
clickFunc: function () {
popup_node.close();
- let popup = popupMsg(`区间为 1 ~ 60,默认 50
`, '啤酒提醒时间设定');
+ let popup = popupMsg(`区间为 1 ~ 60,默认 50
`, '啤酒提醒时间设定');
let confirm = document.createElement('button');
confirm.innerHTML = '确定';
confirm.style.float = 'right';
@@ -541,7 +516,7 @@ export default function userscript(glob: Global): void {
setting_list.push({
domType: 'checkbox',
domId: 'wh-dev-mode',
- domText: ` 开发者模式${isDev() ? ' ' : ''}`,
+ domText: ` 开发者模式${ isDev() ? ' ' : '' }`,
dictName: 'isDev',
isHide: true,
});
@@ -563,39 +538,39 @@ export default function userscript(glob: Global): void {
menu_list.push({
domType: 'plain',
domId: 'wh-trans-welcome',
- domHTML: `欢迎 ${player_info.playername}[${player_info.userID}] 大佬`,
+ domHTML: `欢迎 ${ player_info.playername }[${ player_info.userID }] 大佬`,
});
}
// 节日
let fest_date_html = ': ';
{
const fest_date_dict = {
- '0105': {name: '周末自驾游', eff: '获得双倍的赛车点数与赛车技能等级增益'},
- '0114': {name: '情人节', eff: '使用爱情果汁(Love Juice)后获得降低攻击与复活的能量消耗的增益'},
- '0204': {name: '员工激励日', eff: '获得三倍的工作点数与火车增益'},
- '0217': {name: '圣帕特里克日', eff: '获得双倍的酒类效果增益,城市中可以捡到绿色世涛(Green Stout)'},
- '0320': {name: '420日', eff: '获得三倍的大麻(Cannabis)效果增益'},
- '0418': {name: '博物馆日', eff: '获得10%提高的博物馆PT兑换增益'},
- '0514': {name: '世界献血日', eff: '获得减半的抽血CD和扣血增益'},
- '0611': {name: '世界人口日', eff: '获得双倍的通过攻击获取的经验的增益'},
- '0629': {name: '世界老虎日', eff: '获得5倍的狩猎技能增益'},
- '0705': {name: '国际啤酒节', eff: '获得5倍的啤酒物品效果增益'},
- '0827': {name: '旅游节', eff: '获得双倍的起飞后物品携带容量增益'},
- '0915': {name: '饮料节', eff: '获得双倍的能量饮料效果增益'},
- '1014': {name: '世界糖尿病日', eff: '获得三倍的糖类效果增益'},
- '1015': {name: '周年庆', eff: '左上角的TORN图标可以食用'},
- '1025': {name: '黑色星期五', eff: '某些商家将提供1元购活动'},
- '1114': {name: '住院日', eff: '获得降低75%的住院时间增益'},
+ '0105': { name: '周末自驾游', eff: '获得双倍的赛车点数与赛车技能等级增益' },
+ '0114': { name: '情人节', eff: '使用爱情果汁(Love Juice)后获得降低攻击与复活的能量消耗的增益' },
+ '0204': { name: '员工激励日', eff: '获得三倍的工作点数与火车增益' },
+ '0217': { name: '圣帕特里克日', eff: '获得双倍的酒类效果增益,城市中可以捡到绿色世涛(Green Stout)' },
+ '0320': { name: '420日', eff: '获得三倍的大麻(Cannabis)效果增益' },
+ '0418': { name: '博物馆日', eff: '获得10%提高的博物馆PT兑换增益' },
+ '0514': { name: '世界献血日', eff: '获得减半的抽血CD和扣血增益' },
+ '0611': { name: '世界人口日', eff: '获得双倍的通过攻击获取的经验的增益' },
+ '0629': { name: '世界老虎日', eff: '获得5倍的狩猎技能增益' },
+ '0705': { name: '国际啤酒节', eff: '获得5倍的啤酒物品效果增益' },
+ '0827': { name: '旅游节', eff: '获得双倍的起飞后物品携带容量增益' },
+ '0915': { name: '饮料节', eff: '获得双倍的能量饮料效果增益' },
+ '1014': { name: '世界糖尿病日', eff: '获得三倍的糖类效果增益' },
+ '1015': { name: '周年庆', eff: '左上角的TORN图标可以食用' },
+ '1025': { name: '黑色星期五', eff: '某些商家将提供1元购活动' },
+ '1114': { name: '住院日', eff: '获得降低75%的住院时间增益' },
};
menu_list.fest_date_dict = fest_date_dict;
menu_list.fest_date_list = Object.keys(fest_date_dict);
const formatMMDD = (m, d) => {
- const MM = m < 10 ? `0${m}` : m.toString();
- const DD = d < 10 ? `0${d}` : d.toString();
+ const MM = m < 10 ? `0${ m }` : m.toString();
+ const DD = d < 10 ? `0${ d }` : d.toString();
return MM + DD;
}
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']}()`;
+ if (fest_date_dict[fest_date_key]) fest_date_html += `今天 - ${ fest_date_dict[fest_date_key]['name'] }()`;
else {
// 月日列表
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,
8
) - date) / 86400000 | 0;
- fest_date_html += `${days_left}天后 - ${next_fest_date.name}()`;
+ fest_date_html += `${ days_left }天后 - ${ next_fest_date.name }()`;
}
}
menu_list.push({
@@ -692,8 +667,8 @@ export default function userscript(glob: Global): void {
});
eventObj.html = ': ';
eventObj.onEv
- ? eventObj.html += `${eventObj.current.name}() - 剩余${eventObj.daysLeft}天`
- : eventObj.html += `${eventObj.daysLeft}天后 - ${eventObj.next.name}()`;
+ ? eventObj.html += `${ eventObj.current.name }() - 剩余${ eventObj.daysLeft }天`
+ : eventObj.html += `${ eventObj.daysLeft }天后 - ${ eventObj.next.name }()`;
menu_list.push({
domType: 'plain',
domId: 'wh-trans-event-cont',
@@ -780,7 +755,7 @@ info{display:block;}
`;
const [dest_node, type_node] = node.querySelectorAll('select');
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')) {
WHNotify('正在转跳');
location.href = 'https://www.torn.com/travelagency.php';
@@ -816,13 +791,13 @@ info{display:block;}
['~9时54分', '~6时56分', '~4时58分', '~2时58分',],
];
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);
type_node.addEventListener('change', showTime);
document.body.append(node);
showTime();
- yaoCD.innerHTML = `药CD剩余:${getYaoCD()}`;
+ yaoCD.innerHTML = `药CD剩余:${ getYaoCD() }`;
},
});
// NPC LOOT
@@ -840,7 +815,7 @@ info{display:block;}
Fernando(毒伞)
Tiny(大锤)
-`;
+`;
popupMsg(insert, 'NPC LOOT');
},
tip: '显示5个可击杀NPC的开打时间',
@@ -866,8 +841,8 @@ info{display:block;}
冰蛙或PDA (推荐)
由于需要用到APIKey,因此需要冰蛙或PDA提供
当前可以使用的APIKey:
-(来自冰蛙)
-(来自PDA)
+(来自冰蛙)
+(来自PDA)