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 = `
+
+
+

${msg}

+
`; + 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 `lgif`; } - // 抢啤酒 - 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(大锤)
  • -
    stock.png
    `; +
    stock.png
    `; popupMsg(insert, 'NPC LOOT'); }, tip: '显示5个可击杀NPC的开打时间', @@ -866,8 +841,8 @@ info{display:block;} 冰蛙或PDA (推荐)

    由于需要用到APIKey,因此需要冰蛙或PDA提供

    当前可以使用的APIKey:
    -(来自冰蛙)
    -(来自PDA)

    +(来自冰蛙)
    +(来自PDA)