diff --git a/global.d.ts b/global.d.ts index 7652a29..0436617 100644 --- a/global.d.ts +++ b/global.d.ts @@ -8,6 +8,7 @@ declare interface Window { jQuery?: JQueryStatic; WHPARAMS?: any; ReactDOM?: any; + hasWHQuickFlyOpt?: boolean; addRFC(url: URL | string): string; @@ -39,4 +40,15 @@ declare interface Element { declare interface Notification { id?: number; +} + +declare interface Array { + fest_date_dict?: { [key: string]: { name: string, eff: string } }; + fest_date_list?: string[]; + + [key: string]: any; +} + +declare interface Navigator { + userAgentData?: any; } \ No newline at end of file diff --git a/src/func/utils/BuyBeer.ts b/src/func/utils/BuyBeer.ts index e1cd3f7..a34ae44 100644 --- a/src/func/utils/BuyBeer.ts +++ b/src/func/utils/BuyBeer.ts @@ -1,6 +1,10 @@ import getWhSettingObj from "./getWhSettingObj"; import log from "./log"; import WHNotify from "./WHNotify"; +import getRandomInt from "./getRandomInt"; +import setWhSetting from "./setWhSetting"; +import audioPlay from "./audioPlay"; +import getUserState from "./getUserState"; // 啤酒 export default function BuyBeer() { @@ -82,7 +86,7 @@ export default function BuyBeer() { return loop; } -interface BeerMonitorLoop { +export interface BeerMonitorLoop { start?: Function; stop?: Function; set_time?: Function; diff --git a/src/func/utils/WindowActiveState.ts b/src/func/utils/WindowActiveState.ts new file mode 100644 index 0000000..dd493b6 --- /dev/null +++ b/src/func/utils/WindowActiveState.ts @@ -0,0 +1,23 @@ +// 返回一个可用查询当前窗口是否激活的函数 +import uuidv4 from "./uuidv4"; + +export default function WindowActiveState() { + let glob = window.WHPARAMS; + if (glob.isIframe) return null; + const uuid = uuidv4(); + let isFocus = false; + localStorage.setItem('whuuid', uuid); + document.addEventListener('visibilitychange', () => + (document.visibilityState !== 'hidden') && (localStorage.setItem('whuuid', uuid)) + ); + addEventListener('focus', () => isFocus = true) + addEventListener('blur', () => isFocus = false) + return function (): boolean { + // 当前窗口获得了焦点 优先级最高 + if (isFocus) return true; + // 可视性 + if (!document.hidden) return true; + // 全部在后台,使用唯一id判断 + return uuid === localStorage.getItem('whuuid') + }; +} \ No newline at end of file diff --git a/src/func/utils/audioPlay.ts b/src/func/utils/audioPlay.ts new file mode 100644 index 0000000..61ee17e --- /dev/null +++ b/src/func/utils/audioPlay.ts @@ -0,0 +1,15 @@ +import log from "./log"; + +/** + * 播放音频 + * @param {string} url 播放的音频URL + * @returns {undefined} + */ +export default function audioPlay(url: string = 'https://www.torn.com/js/chat/sounds/Warble_1.mp3') { + const audio = new Audio(url); + audio.addEventListener("canplaythrough", () => { + audio.play() + .catch(err => log(err)) + .then(); + }); +} \ No newline at end of file diff --git a/src/func/utils/setWhSetting.ts b/src/func/utils/setWhSetting.ts new file mode 100644 index 0000000..e8908b6 --- /dev/null +++ b/src/func/utils/setWhSetting.ts @@ -0,0 +1,12 @@ +import getWhSettingObj from "./getWhSettingObj"; +import WHNotify from "./WHNotify"; + +// 插件的配置 setter +export default function setWhSetting(key: string, value: any, notify: boolean = true) { + const obj = getWhSettingObj() + obj[key] = value + localStorage.setItem('wh_trans_settings', JSON.stringify(obj)) + + // 通知 + if (notify) WHNotify('已保存设置') +} \ No newline at end of file diff --git a/src/func/utils/uuidv4.ts b/src/func/utils/uuidv4.ts new file mode 100644 index 0000000..c0bfc21 --- /dev/null +++ b/src/func/utils/uuidv4.ts @@ -0,0 +1,8 @@ +// 返回UUID +export default function uuidv4() { + if (crypto.randomUUID) return crypto.randomUUID(); + // @ts-ignore + return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => + (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) + ); +} \ No newline at end of file diff --git a/src/init.ts b/src/init.ts index 6f7542f..77e0687 100644 --- a/src/init.ts +++ b/src/init.ts @@ -7,6 +7,9 @@ import Device from "./enum/Device"; import getPlayerInfo from "./func/utils/getPlayerInfo"; import autoFetchJSON from "./func/utils/autoFetchJSON"; import priceWatcherHandle from "./func/utils/priceWatcherHandle"; +import BuyBeer from "./func/utils/BuyBeer"; +import WindowActiveState from "./func/utils/WindowActiveState"; +import addStyle from "./func/utils/addStyle"; // 初始化方法,获取必要全局参数 export default function init(glob: Global) { @@ -78,7 +81,7 @@ export default function init(glob: Global) { glob.priceWatcher = glob.isIframe ? null : priceWatcherHandle(); // 抢啤酒 - glob.beer = buyBeer(); + glob.beer = BuyBeer(); glob.popup_node = null; // 当窗口关闭时关闭所有还存在的通知 @@ -93,4 +96,155 @@ export default function init(glob: Global) { } } ); + + // 记录当前窗口唯一id + glob.isWindowActive = WindowActiveState(); + + addStyle(` +.wh-hide{display:none;} +#wh-trans-icon{ +user-select:none; +display: inline-block; +position: fixed; +top:5px; +left:5px; +z-index:100010; +border-radius:4px; +max-width: 220px; +box-shadow: 0 0 3px 1px #8484848f; +} +div#effectiveness-wrap{overflow-y:hidden;} +@media screen and (max-width: 600px) { + #wh-trans-icon{top:0;left:112px;} + /* 冰蛙公司效率表 */ + div#effectiveness-wrap { + margin-left: -80px; + margin-right: -76px; + } +} +#wh-trans-icon select{width:110px;} +#wh-trans-icon a { +text-decoration: none; +color: #006599; +background: none; +} +#wh-trans-icon:not(.wh-icon-expanded):hover {background: #f8f8f8;} +#wh-trans-icon button{ +margin:0; +padding:0; +border:0; +cursor:pointer; +} +#wh-inittimer{margin-top:6px;color:#b0b0b0;} +#wh-gSettings div{margin: 4px 0;} +#wh-trans-icon .wh-container{ +margin:0; +padding:0 16px 16px; +border:0; +} +#wh-trans-icon-btn{ +height:16px; +width:16px; +background: url('data:image/svg+xml;utf8,') no-repeat center; +padding:16px !important; +} +#wh-trans-icon .wh-container{display:none;} +#wh-trans-icon.wh-icon-expanded .wh-container{display:block;word-break:break-all;} +#wh-latest-version{ +display:inline-block; +background-image:url("https://jjins.github.io/t2i/version.png?${performance.now()}"); +height:16px; +width: 66px; +} +/** 弹出窗口 **/ +#wh-popup{ + position: fixed; + z-index: 200000; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #00000090; + color:#333; +} +div#wh-popup::after { + content: '点击空白处关闭'; + display: block; + color: #ffffffdb; + text-align: center; + font-size: 14px; + line-height: 22px; +} +#wh-popup-container{ + max-width: 568px; + margin: 5em auto 0; + background: #d7d7d7; + min-height: 120px; + box-shadow: 0 0 5px 1px #898989; + border-radius: 4px; +} +#wh-popup-title p{ + padding: 1em 0; + font-size: 16px; + font-weight: bold; + text-align: center; +} +/** 弹出窗口的内容 **/ +#wh-popup-cont{ + padding: 0 1em 1em; + max-height: 30em; + overflow-y: auto; + font-size:14px; + line-height: 16px; +} +#wh-popup-cont .gSetting > div{ + display: inline-block; + width: 47%; + margin: 2px 0; +} +#wh-popup-cont .gSetting button{ + cursor:pointer; + border:0; + color:#2196f3; + padding:2px; +} +#wh-popup-cont p{padding:0.25em 0;} +#wh-popup-cont a{color:red;text-decoration:none;} +#wh-popup-cont li{margin:4px 0;} +#wh-popup-cont h4{margin:0;padding: 0.5em 0;} +#wh-popup-cont button{ + margin: 0 4px 0 0; + padding: 5px 8px; + border: solid 2px black; + color: black; + border-radius: 3px; +} +#wh-popup-cont button[disabled]{opacity: 0.5;} +#wh-popup-cont input{ + padding: 2px; + text-align: center; + border: 1px solid #fff0; + border-radius: 5px; + margin:1px 2px; +} +#wh-popup-cont input:focus{border-color:blue;} +#wh-popup-cont table{width:100%;border-collapse:collapse;border:1px solid;} +#wh-popup-cont td, #wh-popup-cont th{border-collapse:collapse;padding:4px;border:1px solid;} +.wh-display-none{display:none !important;} +#wh-gym-info-cont{ + background-color: #363636; + color: white; + padding: 8px; + font-size: 15px; + border-radius: 4px; + text-shadow: 0 0 2px black; + background-image: linear-gradient(90deg,transparent 50%,rgba(0,0,0,.07) 0); + background-size: 4px; + line-height: 20px; +} +#wh-gym-info-cont button{ +cursor:pointer; +} +`); + } \ No newline at end of file diff --git a/src/interface/GlobalVars.ts b/src/interface/GlobalVars.ts index 295cd2d..563d60c 100644 --- a/src/interface/GlobalVars.ts +++ b/src/interface/GlobalVars.ts @@ -1,11 +1,15 @@ import Device from "../enum/Device"; +import {BeerMonitorLoop} from "../func/utils/BuyBeer"; export default interface Global { + isWindowActive?(): boolean; + popup_node?: Element; + beer?: BeerMonitorLoop; notifies?: NotifyWrapper; priceWatcher?: { status: boolean }; fstock?: { get: () => Promise }; player_info?: PlayerInfo; - device?: Device; + device?: Device; isPDA?: boolean; PDA_APIKey?: string; isIframe?: boolean; diff --git a/src/userscript.ts b/src/userscript.ts index 673aea8..620443d 100644 --- a/src/userscript.ts +++ b/src/userscript.ts @@ -52,1379 +52,15 @@ export default function userscript(glob: Global): void { init(glob); let {version, isIframe, PDA_APIKey, isPDA, player_info, fstock, notifies} = glob; - const date = new Date(); + // 返回一个加载中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`; } - // 记录当前窗口唯一id - const isWindowActive = getWindowActiveState(); - - // 对新值应用「默认」设置 - [ - // 开启翻译 - {key: 'transEnable', val: false}, - // 快速犯罪 - {key: 'quickCrime', val: true}, - // 任务助手 - {key: 'missionHint', val: true}, - // 小镇攻略 - {key: 'xmasTownWT', val: true}, - // 小镇提醒 - {key: 'xmasTownNotify', val: true}, - // 起飞爆e - {key: 'energyAlert', val: true}, - // 飞行闹钟 - {key: 'trvAlarm', val: true}, - // 啤酒提醒 - {key: '_15Alarm', val: true}, - // 捡垃圾助手 - {key: 'cityFinder', val: false}, - // 叠E保护 - {key: 'SEProtect', val: false}, - // PT一键购买 - {key: 'ptQuickBuy', val: false}, - // 光速拔刀 6-关闭 - {key: 'quickAttIndex', val: 2}, - // 光速跑路 0-leave 1-mug 2-hos 3-关闭 - {key: 'quickFinishAtt', val: 3}, - // 自动开打和结束 - {key: 'autoStartFinish', val: false}, - // 废弃 - {key: 'attRelocate', val: true}, - // 攻击自刷新 0-无间隔 1-5s 6-关闭 - {key: 'attReload', val: 6}, - // 价格监视 - {key: 'priceWatcher', val: {xan: -1, pt: -1}}, - // 开发者模式 - {key: 'isDev', val: false}, - // 啤酒提醒时间 - {key: '_15AlarmTime', val: 50}, - // 4条转跳 - {key: 'barsRedirect', val: true}, - // 浮动存钱框 - {key: 'floatDepo', val: true}, - // 公司转跳存钱 - {key: 'companyRedirect', val: true}, - // 收起公司冰蛙效率表 - {key: 'companyBWCollapse', val: true}, - // 清除多余的脚本 - {key: 'removeScripts', val: true}, - // 海外警告 - {key: 'abroadWarning', val: true}, - // 落地转跳 - {key: 'landedRedirect', val: ''}, - // 任何位置一键存钱 - {key: 'companyDepositAnywhere', val: false}, - - // 危险行为⚠️ - {key: 'dangerZone', val: false}, - ].forEach(df => { - if (typeof getWhSettingObj()[df.key] !== typeof df.val) setWhSetting(df.key, df.val); - }); - - // region 助手各项「设置」 - let setting_list = []; - // 12月时加入圣诞小镇选项 - if (date.getMonth() === 11) { - setting_list.push({ - domType: 'plain', - domId: '', - domHTML: '圣诞小镇', - tagName: 'h4', - }) - setting_list.push({ - domType: 'checkbox', - domId: 'wh-xmastown-wt', - domText: ' 圣诞小镇攻略', - dictName: 'xmasTownWT', - isHide: true, - }); - setting_list.push({ - domType: 'checkbox', - domId: 'wh-xmastown-notify', - domText: ' 圣诞小镇物品提示', - dictName: 'xmasTownNotify', - isHide: true, - }); - } - - // 翻译 - setting_list.push({ - domType: 'plain', - domId: '', - domHTML: '翻译', - tagName: 'h4', - }); - // 开启翻译 - setting_list.push({ - domType: 'checkbox', - domId: 'wh-trans-enable', - domText: ' 开启翻译', - dictName: 'transEnable', - isHide: true, - }); - // 更新翻译词库 - setting_list.push({ - domType: 'button', - domId: '', - domText: '更新翻译词库', - clickFunc: updateTransDict - }); - - // 战斗优化 - setting_list.push({ - domType: 'plain', - domId: '', - domHTML: '战斗优化', - tagName: 'h4', - }); - // 光速拔刀 - setting_list.push({ - domType: 'select', - domId: 'wh-quick-attack-index', - domText: '光速拔刀 ', - domSelectOpt: [ - { - domVal: 'pri', - domText: '主手', - }, - { - domVal: 'sec', - domText: '副手', - }, - { - domVal: 'wea', - domText: '近战', - }, - { - domVal: 'gre', - domText: '手雷', - }, - { - domVal: 'fis', - domText: '拳头', - }, - { - domVal: 'kic', - domText: '脚踢', - }, - { - domVal: 'none', - domText: '关闭', - }, - ], - dictName: 'quickAttIndex', - isHide: true, - tip: '将Start Fight按钮移动到指定格子上', - }); - // 光速跑路 - setting_list.push({ - domType: 'select', - domId: 'wh-quick-mug', - domText: '光速跑路 ', - domSelectOpt: [ - { - domVal: 'leave', - domText: '跑路(LEAVE)', - }, - { - domVal: 'mug', - domText: '打劫(MUG)', - }, - { - domVal: 'hosp', - domText: '住院(HOSP)', - }, - { - domVal: 'none', - domText: '关闭', - }, - ], - dictName: 'quickFinishAtt', - isHide: true, - tip: '将结束后指定按钮移动到上面指定的格子上', - }); - // 攻击链接转跳 - setting_list.push({ - domType: 'checkbox', - domId: 'wh-attack-relocate', - domText: ' 真·攻击界面转跳', - dictName: 'attRelocate', - tip: '在无法打开攻击界面的情况下依然可以转跳到正确的攻击页面', - isHide: true, - }); - - // 飞行 - setting_list.push({ - domType: 'plain', - domId: '', - domHTML: '飞行', - tagName: 'h4', - }); - // 起飞警告 - setting_list.push({ - domType: 'checkbox', - domId: 'wh-energy-alert', - domText: ' 起飞爆E警告', - dictName: 'energyAlert', - tip: '起飞前计算来回是否会爆体,红字警告', - isHide: true, - }); - // 飞行闹钟 - setting_list.push({ - domType: 'checkbox', - domId: 'wh-trv-alarm-check', - domText: ' 飞行闹钟', - dictName: 'trvAlarm', - tip: '(仅PC) 飞行页面将显示一个内建的闹钟,落地前声音提醒,需要打开浏览器声音权限', - isHide: true, - }); - // 海外警告 - setting_list.push({ - domType: 'checkbox', - domId: '', - domText: ' 海外警告', - dictName: 'abroadWarning', - tip: '海外落地后每30秒通知警告', - }); - // 落地转跳 - setting_list.push({ domType: 'button', domId: '', domText: '落地转跳', clickFunc: landedRedirect }); - - // 公司 - setting_list.push({ - domType: 'plain', - domId: '', - domHTML: '公司', - tagName: 'h4', - }); - // 浮动存钱框 - setting_list.push({ - domType: 'checkbox', - domId: '', - domText: ' 浮动存钱框', - dictName: 'floatDepo', - tip: '打开公司或帮派的存钱页面后存钱框将浮动显示', - }); - // 公司转跳存钱 - setting_list.push({ - domType: 'checkbox', - domId: '', - domText: ' 公司转跳存钱', - dictName: 'companyRedirect', - tip: '打开公司页面时自动打开存钱选项卡', - }); - // 收起公司冰蛙效率表 - setting_list.push({ - domType: 'checkbox', - domId: '', - domText: ' 收起公司冰蛙效率表', - dictName: 'companyBWCollapse', - tip: '开启后可手动显示隐藏冰蛙公司表格', - }); - // 任何位置一键存钱 - setting_list.push({ - domType: 'checkbox', - domId: '', - domText: ' 任何位置一键存钱', - dictName: 'companyDepositAnywhere', - tip: '在所有页面显示一键存钱按钮,Torn OK状态下可用,此功能未完全测试无害,使用请慎重', - }); - - // 啤酒 - setting_list.push({ - domType: 'plain', - domId: '', - domHTML: '啤酒', - tagName: 'h4', - }); - // 啤酒提醒 - setting_list.push({ - domType: 'checkbox', - domId: 'wh-qua-alarm-check', - domText: ' 啤酒提醒 ', - dictName: '_15Alarm', - tip: '每小时的整15分钟的倍数时通知提醒抢啤酒或者血包', - isHide: true, - changeEv: function (ev) { - ev.target.checked ? beer.start() : beer.stop(); - }, - }); - // 啤酒提醒状态 - setting_list.push({ - domType: 'button', - domId: '', - domText: '啤酒提醒状态', - clickFunc: function () { - WHNotify(`啤酒提醒${ beer.status() }`); - } - }); - // 啤酒提醒时间 - setting_list.push({ - domType: 'button', - domId: '', - domText: '啤酒提醒时间设定', - // tip: '通知提前时间', - clickFunc: function () { - popup_node.close(); - let popup = popupMsg(`

区间为 1 ~ 60,默认 50

`, '啤酒提醒时间设定'); - let confirm = document.createElement('button'); - confirm.innerHTML = '确定'; - confirm.style.float = 'right'; - confirm.addEventListener('click', () => { - let input = popup.querySelector('input'); - let num = input.value | 0; - if (num === getWhSettingObj()['_15AlarmTime']) return; - if (num < 1 || num > 60) num = 50; - input.value = num.toString(); - setWhSetting('_15AlarmTime', num); - // 之前的运行状态 - let before_state = beer.is_running(); - beer.set_time(num); - if (before_state) beer.start(); - popup.close(); - }); - popup.appendChild(confirm); - }, - }); - - // 其他 - setting_list.push({ - domType: 'plain', - domId: '', - domHTML: '其他', - tagName: 'h4', - }); - // 任务助手 - setting_list.push({ - domType: 'checkbox', - domId: 'wh-mission-lint', - domText: ' 任务助手', - dictName: 'missionHint', - tip: 'Duke任务的一些中文小提示', - isHide: true, - }); - // 捡垃圾助手 - setting_list.push({ - domType: 'checkbox', - domId: 'wh-city-finder', - domText: ' 捡垃圾助手', - dictName: 'cityFinder', - tip: '城市地图中放大显示物品并且估计价值', - isHide: true, - }); - // 快速crime - setting_list.push({ - domType: 'checkbox', - domId: 'wh-quick-crime', - domText: ' 快速犯罪', - dictName: 'quickCrime', - tip: '显示快捷操作按钮,目前不支持自定义', - isHide: true, - }); - // 叠E保护 - setting_list.push({ - domType: 'checkbox', - domId: 'wh-SEProtect-check', - domText: ' 叠E保护', - dictName: 'SEProtect', - tip: '隐藏健身房的锻炼按钮,防止误操作', - isHide: true, - }); - // PT一键购买 - setting_list.push({ - domType: 'checkbox', - domId: 'wh-ptQuickBuy-check', - domText: ' PT一键购买', - dictName: 'ptQuickBuy', - tip: 'PT市场页面购买时跳过确认', - isHide: true, - }); - // 4条转跳 - setting_list.push({ - domType: 'checkbox', - domId: '', - domText: ' 4条转跳', - dictName: 'barsRedirect', - tip: '点击4条时转跳对应页面', - }); - // 清除多余的脚本 - setting_list.push({ - domType: 'checkbox', - domId: '', - domText: ' 清除多余的脚本', - dictName: 'removeScripts', - tip: '清除Google相关脚本、顶部横幅等', - }); - // 危险行为⚠️ - if (getWhSettingObj()['dangerZone'] === true) { - // 攻击界面自刷新 - setting_list.push({ - domType: 'select', - domId: 'wh-attack-reload', - domText: '⚠️攻击界面自动刷新 ', - dictName: 'attReload', - domSelectOpt: [ - { - domVal: 'none', - domText: '无间隔', - }, - { - domVal: '1', - domText: '约1s', - }, - { - domVal: '2', - domText: '约2s', - }, - { - domVal: '3', - domText: '约3s', - }, - { - domVal: '4', - domText: '约4s', - }, - { - domVal: '5', - domText: '约5s', - }, - { - domVal: 'disabled', - domText: '关闭', - }, - ], - isHide: true, - tip: '危险功能:接机时常用,将自动刷新页面直到目标落地', - }); - // 自动开打和结束 - setting_list.push({ - domType: 'checkbox', - domId: 'wh-auto-start-finish', - domText: ' ⚠️自动开打和结束', - dictName: 'autoStartFinish', - tip: '脚本将会自动按下战斗和结束按钮', - isHide: true, - }); - } else { - setWhSetting('autoStartFinish', false, false) - setWhSetting('attReload', 6, false) - } - // dev - setting_list.push({ - domType: 'checkbox', - domId: 'wh-dev-mode', - domText: ` 开发者模式${ isDev() ? ' ' : '' }`, - dictName: 'isDev', - isHide: true, - }); - // 更多设定 - if (isDev()) setting_list.push({ - domType: 'button', domId: 'wh-otherBtn', domText: '更多设定', clickFunc: () => { - const html = `清空设置数据、请求通知权限、测试跨域请求`; - const popup = popupMsg(html, '更多设定'); - }, - isHide: true, - }); - // endregion - // region 菜单中的「选项」 - const menu_list = []; - //const date = new Date(2022, 2, 4, 23); - - // 欢迎 显示玩家id - if (player_info.userID !== 0) { - menu_list.push({ - domType: 'plain', - domId: 'wh-trans-welcome', - 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%的住院时间增益' }, - }; - 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(); - 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'] }()`; - else { - // 月日列表 - let fest_date_list = Object.keys(fest_date_dict); - fest_date_list.push(fest_date_key); - // 下个节日的位置 - const next_fest_date_index = fest_date_list.sort().indexOf(fest_date_key) + 1; - // 下个节日obj - const next_fest_date = fest_date_dict[fest_date_list[next_fest_date_index] || fest_date_list[0]]; - // 下个节日的时间 - const days_left = (new Date( - next_fest_date_index !== fest_date_list.length ? date.getUTCFullYear() : date.getUTCFullYear() + 1, - fest_date_list[next_fest_date_index !== fest_date_list.length ? next_fest_date_index : 0].slice(0, 2) / 1, - 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 }()`; - } - } - menu_list.push({ - domType: 'plain', - domId: 'wh-trans-fest-date', - domHTML: fest_date_html, - }); - // 活动 - let eventObj = { - onEv: false, - daysLeft: Infinity, - events: [ - { - start: [0, 17, 8], end: [0, 24, 8], - name: '捡垃圾周', - eff: '获得捡垃圾概率提升的增益', - }, - { - start: [3, 5, 20], end: [3, 25, 20], - name: '复活节狩猎', - eff: '复活节彩蛋会随机出现,集齐10个可兑换金蛋和一个独特的头像框(章)。', - }, - { - start: [5, 20, 20], end: [5, 29, 20], - name: '狗牌', - eff: '击败其他玩家以获得狗牌,小心保护你的狗牌。', - }, - { - start: [6, 5, 20], end: [6, 25, 20], - name: '托恩先生和托恩女士', - eff: '上传你的真实图片,然后拿章', - }, - { - start: [8, 5, 20], end: [8, 23, 20], - name: '大逃杀', - eff: '加入特定队伍后,攻击其他队伍玩家,存活下来的3个队伍可以拿章', - }, - { - start: [9, 25, 20], end: [10, 1, 20], - name: '不给糖就捣蛋', - eff: '买篮子之后攻击其他玩家后会随机掉落糖果,可用于兑换许多高价值物品', - }, - { - start: [11, 14, 20], end: [11, 31, 20], - name: '圣诞小镇', - eff: '在小镇中闲逛来获取随机掉落的物品', - }, - ], - }; - menu_list.events = eventObj.events; - eventObj.events.forEach((obj, index) => { - if (eventObj.onEv) return; - // 当前年份 - const nowYear = date.getFullYear(); - // 当前遍历的活动开始时间 - const start = new Date(nowYear, obj.start[0], obj.start[1], obj.start[2]); - // 当前遍历的活动结束时间 - const end = new Date(nowYear, obj.end[0], obj.end[1], obj.end[2]); - // 当前处于活动中 - if (start < date && date < end) { - eventObj.onEv = true; - eventObj.daysLeft = (end - date) / 86400000 | 0; - eventObj.current = obj; - } - // 当前没有活动 - else { - // 当前遍历的活动如果已经经过了,那么下次活动就是遍历的下一个活动对象,否则为当前活动。 - // 如果本年度活动都经过了,那么下次活动是列表的第一个活动对象 - const next = end < date ? eventObj.events[index + 1] || eventObj.events[0] : obj; - // 经过了最后一个活动所以下次活动开始时间是第二年 - const start = new Date(next !== obj && index === eventObj.events.length - 1 ? nowYear + 1 : nowYear, next.start[0], next.start[1], next.start[2]); - const daysLeft = (start - date) / 86400000 | 0; - if (0 <= daysLeft && daysLeft < eventObj.daysLeft) { - eventObj.daysLeft = daysLeft; - eventObj.next = next; - } - } - }); - eventObj.html = ': '; - eventObj.onEv - ? eventObj.html += `${ eventObj.current.name }() - 剩余${ eventObj.daysLeft }天` - : eventObj.html += `${ eventObj.daysLeft }天后 - ${ eventObj.next.name }()`; - menu_list.push({ - domType: 'plain', - domId: 'wh-trans-event-cont', - domHTML: eventObj.html, - }); - // 飞花库存 - menu_list.push({ - domType: 'button', - domId: 'wh-foreign-stock-btn', - domText: '🌸 飞花库存', - clickFunc: async function (e) { - e.target.blur(); - forStock().then(); - }, - }); - // 一键起飞 - menu_list.push({ - domType: 'button', - domId: 'wh-quick-fly-btn', - domText: '✈️ 一键起飞', - clickFunc: async function () { - if (window.hasWHQuickFlyOpt) return; - window.hasWHQuickFlyOpt = true; - addStyle(`#wh-quick-fly-opt{ - position:fixed; - left:64px; - top:64px; - background: #008000db; - padding: 8px; - border-radius: 4px; - box-shadow: 0 0 5px 1px #ffffff29; - color: white; - font-size: 15px; - width: 220px; - z-index: 199999; -} -#wh-quick-fly-opt p{margin:4px 0;} -#wh-quick-fly-opt a{ -cursor: pointer; - border: 1px solid; - padding: 4px; - display: inline-block; - border-radius: 2px; -} -#wh-quick-fly-opt label{ -display:block; -} -#wh-quick-fly-opt select{ -width: 100%; - padding: 6px; - margin: 4px 0; -} -#wh-quick-fly-opt button{ -font-size: 16px; - color: white; - cursor: pointer; - float: right; - background: #00BCD4; - padding: 8px; - border-radius: 4px; -} -#wh-quick-fly-opt.wh-quick-fly-opt-hide *{ -display: none; -} -#wh-quick-fly-opt.wh-quick-fly-opt-hide input{ -display: inline-block; -} -info{display:block;} -`); - const node = document.createElement('div'); - node.id = 'wh-quick-fly-opt'; - node.innerHTML = ` - -

主要用途:出院秒飞

-

点起飞,页面加载完成后会马上飞走

-
-
- - -

查看花偶库存

-

注:需要验证时无法起飞

- -
-`; - 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() }`; - if (!href.contains('travelagency.php')) { - WHNotify('正在转跳'); - location.href = 'https://www.torn.com/travelagency.php'; - } else { - doQuickFly(); - } - }); - node.querySelector('a').addEventListener('click', (e) => { - e.preventDefault(); - forStock(); - }); - node.querySelector('input').addEventListener('click', (e) => { - node.classList.toggle('wh-quick-fly-opt-hide'); - const el = e.target; - el.value = el.value === ' - ' ? ' + ' : ' - '; - }); - const info_node = node.querySelector('info'); - const time_predict = document.createElement('p'); - const yaoCD = document.createElement('p'); - info_node.append(time_predict); - info_node.append(yaoCD); - const predict = [ - ['~54分', '~36分', '~26分', '~16分',], - ['~1时10分', '~50分', '~36分', '~22分',], - ['~1时22分', '~58分', '~40分', '~24分',], - ['~4时28分', '~3时8分', '~2时14分', '~1时20分',], - ['~5时18分', '~3时42分', '~2时40分', '~1时36分',], - ['~5时34分', '~3时54分', '~2时46分', '~1时40分',], - ['~5时50分', '~4时6分', '~2时56分', '~1时46分',], - ['~7时30分', '~5时16分', '~3时46分', '~2时16分',], - ['~8时4分', '~5时38分', '~4时2分', '~2时24分',], - ['~9时2分', '~6时20分', '~4时30分', '~2时42分',], - ['~9时54分', '~6时56分', '~4时58分', '~2时58分',], - ]; - const showTime = function () { - 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() }`; - }, - }); - // NPC LOOT - menu_list.push({ - domType: 'button', - domId: 'wh-npc-loot-btn', - domText: '🔫 LOOT', - clickFunc: function (e) { - e.target.blur(); - const insert = `

点击开打:

- -
stock.png
`; - popupMsg(insert, 'NPC LOOT'); - }, - tip: '显示5个可击杀NPC的开打时间', - }); - // 查看NNB - menu_list.push({ - domType: 'button', - domId: 'wh-nnb-info', - domText: '👮‍ 查看NNB', - clickFunc: function (e) { - e.target.blur(); - const insert = ` -

-

NNBNatural Nerve Bar)意思是:扣除所有加成后,玩家本身的犯罪条上限,可用于衡量大佬隐藏的犯罪技能等级

-

一般来说,左侧红色的犯罪条(Nerve Bar/NB)的上限都是包含加成的,如来自帮派、天赋的加成等。额外的加成不会影响玩家的犯罪技能

-

查看NNB的方法很简单,在Torn主页面的最下方有一栏Perks,NB-Perks=NNB

-
-

以下是两种计算NNB的方法:

- - -
- -`; - const popup = popupMsg(insert, '查看NNB'); - const select = popup.querySelector('input'); - const node = popup.querySelector('p'); - popup.querySelector('button').addEventListener('click', ev => { - ev.target.style.display = 'none'; - node.innerHTML = '加载中'; - // API 计算 - if (select.checked) { - const api_key = isPDA ? PDA_APIKey : window.localStorage.getItem('APIKey'); - fetch(`https://api.torn.com/user/?selections=bars,perks&key=${ api_key }`) - .then(res => res.json()) - .then(data => { - if (data['error']) { - node.innerHTML = `出错了 ${ Obj2Str(data['error']) }`; - ev.target.style.display = null; - return; - } - let nb = data['nerve']['maximum']; - let perks = 0; - Object.values(data).forEach(val => { - (val instanceof Array) && val.forEach(s => { - s = s.toLowerCase(); - s.includes('maximum nerve') && (perks += /[0-9]./.exec(s)[0] | 0) - }) - }); - node.innerHTML = `NNB: ${ nb - perks }`; - ev.target.style.display = null; - }); - } - // 主页计算 - else { - if (window.location.href.includes('index.php') && document.title.includes('Home')) { - let nb = document.querySelector('#barNerve p[class^="bar-value___"]').innerText.split('/')[1] | 0; - let perks = 0; - document.querySelectorAll('#personal-perks li').forEach(elem => { - const str = elem.innerText.toLowerCase(); - str.includes('maximum nerve') && (perks += /[0-9]./.exec(str)[0] | 0) - }); - node.innerHTML = `NNB: ${ nb - perks }`; - ev.target.style.display = null; - return; - } - node.innerHTML = '不在主页面,点击前往'; - ev.target.style.display = null; - } - }); - }, - }); - // 常用链接 - menu_list.push({ - domType: 'button', - domId: 'wh-link-collection', - domText: '🔗 常用链接', - clickFunc: function (e) { - if (!this.styleAdded) { - addStyle(` -.wh-link-collection-cont a{ - display: inline-block; - border: solid 1px #b3b3b3; - border-radius: 4px; - margin: 0 5px 2px 0; - padding: 4px 8px; - text-align:center; - background: #efefef; - background: linear-gradient(#f1f1f1,#e3e3e3); - color:black !important; -} -.wh-link-collection-cont span{ -display: block; -/*padding: 0 4px 8px;*/ -} -.wh-link-collection-cont .wh-link-collection-img{ -display: block; -width:60px; -height:30px; -background-size: 100% auto !important; -} -`); - this.styleAdded = true; - } - e.target.blur(); - const quick_link_dict = []; - // 生存手册 - quick_link_dict.push({ - name: '生存手册', - url: 'https://docs.qq.com/doc/DTVpmV2ZaRnB0RG56', - new_tab: true, - img: 'https://www.torn.com/images/items/293/medium.png', - }); - // 买啤酒 - quick_link_dict.push({ - name: '抢啤酒', - url: 'https://www.torn.com/shops.php?step=bitsnbobs', - new_tab: true, - img: 'https://www.torn.com/images/items/180/medium.png', - }); - // 买XAN - quick_link_dict.push({ - name: '买XAN', - url: 'https://www.torn.com/imarket.php#/p=shop&step=shop&type=&searchname=Xanax', - new_tab: true, - img: 'https://www.torn.com/images/items/206/medium.png', - }); - // 起飞 - quick_link_dict.push({ - name: '起飞', - url: 'https://www.torn.com/travelagency.php', - new_tab: true, - img: 'https://www.torn.com/images/items/396/medium.png', - }); - // 买PT - quick_link_dict.push({ - name: '买PT', - url: 'https://www.torn.com/pmarket.php', - new_tab: true, - img: 'https://www.torn.com/images/items/722/medium.png', - }); - // 租PI - quick_link_dict.push({ - name: '租PI', - url: 'https://www.torn.com/properties.php?step=rentalmarket#/property=13', - new_tab: false, - img: 'https://www.torn.com/images/v2/properties/350x230/350x230_default_private_island.png', - }); - // 找工作 - quick_link_dict.push({ - name: '找工作', - url: 'https://www.torn.com/joblist.php#!p=main', - new_tab: false, - img: 'https://www.torn.com/images/items/421/medium.png', - }); - // 下悬赏 - quick_link_dict.push({ - name: '下悬赏', - url: 'https://www.torn.com/bounties.php#/p=add', - new_tab: false, - img: 'https://www.torn.com/images/items/431/medium.png', - }); - let insert = '

'; - quick_link_dict.forEach(el => { - insert += `${ el.name }`; - }); - insert += '

' - let popup = popupMsg(insert, '常用链接'); - popup.classList.add('wh-link-collection-cont'); - popup.addEventListener('click', ev => { - if (ev.target.tagName.toLowerCase() === 'a' || ev.target.tagName.toLowerCase() === 'span') { - popup.close(); - } - }); - }, - }); - // 飞贼 - menu_list.push({ - domType: 'button', - domId: 'wh-gs-btn', - domText: '🐏 飞贼小助手', - clickFunc: function (e) { - e.target.blur(); - loadGS(getScriptEngine()); - }, - tip: '加载从PC端移植的伞佬的油猴版飞贼小助手', - }); - // 物品价格监视 - menu_list.push({ - domType: 'button', - domId: 'wh-price-watcher-btn', - domText: '💊 价格监视', - clickFunc: function () { - const watcher_conf = getWhSettingObj()['priceWatcher']; - const pre_str = JSON.stringify(watcher_conf); - const html = ` -

输入需要监视的价格,低于该价格发出通知,-1为关闭

-

注:需要APIKey,当前可用APIKey为
-(来自冰蛙)
-(来自PDA) -

-

PT

-

XAN

-

-`; - const popup = popupMsg(html, '价格监视设置'); - popup.querySelector('button').onclick = () => { - const [pt_node, xan_node] = popup.querySelectorAll('input[type="number"]'); - watcher_conf.pt = pt_node.value | 0; - watcher_conf.xan = xan_node.value | 0; - if (JSON.stringify(watcher_conf) !== pre_str) setWhSetting('priceWatcher', watcher_conf); - popup.close(); - }; - } - }); - // 小窗犯罪 - menu_list.push({ - domType: 'button', - domId: 'wh-crime-iframe-btn', - domText: '🤑 小窗犯罪', - clickFunc: function () { - // 弹出小窗口 - const ifHTML = ``; - const popup_insert = `

加载中请稍后${ loading_gif_html() }

`; - const $popup = popupMsg(popup_insert, '小窗快速犯罪'); - // 运行状态node - let loading_node = $popup.querySelector('p:first-of-type'); - // if容器 - const if_cont = $popup.querySelector('#wh-quick-crime-if-container'); - if_cont.innerHTML = ifHTML; - - // if内未加载脚本时插入的快捷crime node - const mobile_prepend_node = document.createElement('div'); - mobile_prepend_node.classList.add('wh-translate'); - mobile_prepend_node.innerHTML = `
快捷操作:
-
- - - -
-
- - - -
-
- - - -

`; - - // if对象加载后运行 - let cIframe = $popup.querySelector('iframe'); - - // 加载状态 - const if_onload_func = () => { - // if内部文档对象 - const ifDocu = cIframe.contentWindow.document; - // 内部插件运行flag - const ifWH = cIframe.contentWindow.WHTRANS; - // 文档加载完成后移除 - if (!!loading_node) loading_node.remove(); - // 文档加载完成后才显示if - cIframe.style.display = 'block'; - // 验证码flag - const isValidate = ifDocu.querySelector('h4#skip-to-content').innerText.toLowerCase().includes('validate'); - // 如果iframe内部未运行脚本 - if (ifWH === undefined) { - // 隐藏顶部 - elementReady('#header-root', ifDocu).then(e => e.style.display = 'none'); - // 隐藏4条 - elementReady('#sidebarroot', ifDocu).then(e => e.style.display = 'none'); - // 隐藏聊天 - elementReady('#chatRoot', ifDocu).then(e => e.style.display = 'none'); - // 非验证码页面隐藏滚动条 - if (!isValidate) ifDocu.body.style.overflow = 'hidden'; - // 调整容器位置 - elementReady('.content-wrapper', ifDocu).then(elem => { - // 加入 - elem.prepend(mobile_prepend_node); - elem.style.margin = '0px'; - elem.style.position = 'absolute'; - elem.style.top = '-35px'; - new MutationObserver((m, o) => { - o.disconnect(); - if (!elem.querySelector('.wh-translate')) elem.prepend(mobile_prepend_node); - o.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'); - } - }; - cIframe.onload = if_onload_func; - - // 超时判断 - let time_counter = 0; - let time_out_id = window.setInterval(() => { - loading_node = $popup.querySelector('p:first-of-type'); - if (!loading_node) { - clearInterval(time_out_id); - time_out_id = undefined; - return; - } - time_counter++; - if (time_counter > 0 && !loading_node.querySelector('button')) { - const reload_btn = document.createElement('button'); - reload_btn.innerHTML = '重新加载'; - reload_btn.onclick = () => { - reload_btn.remove(); - time_counter = 0; - if_cont.innerHTML = null; - if_cont.innerHTML = ifHTML; - cIframe = $popup.querySelector('iframe'); - cIframe.onload = if_onload_func; - }; - loading_node.append(reload_btn); - } - }, 1000); - } - }); - // 危险行为开关⚠️ - menu_list.push({ - domType: 'button', - domId: 'wh-danger-zone', - domText: '⚠️ 危险功能', - clickFunc: function (e) { - e.target.blur(); - const insert = `

即将打开危险功能,使用这些功能可能会造成账号封禁。请自行考虑是否使用。

-

-
`; - const popup = popupMsg(insert, '⚠️警告'); - const warning_check = popup.querySelector('input'); - const ok_btn = popup.querySelector('button'); - warning_check.onchange = () => ok_btn.disabled = false; - ok_btn.onclick = () => { - setWhSetting('dangerZone', warning_check.checked); - popup['close'](); - window.location.reload(); - }; - }, - }); - // 传单助手 - menu_list.push({ - domType: 'button', - domId: '', - domText: '📜️ 传单助手', - clickFunc: adHelper - }); - // 守望者 - menu_list.push({ - domType: 'button', - domId: '', - domText: '🛡️ 守望者', - clickFunc: function () { - safeKeeper(); - }, - }); - // 更新历史 - menu_list.push({ - domType: 'button', domId: '', domText: '🐞 更新历史', clickFunc: async () => { - let popup = popupMsg( - '更新历史:
https://gitlab.com/JJins/wuhu-torn-helper/-/blob/dev/CHANGELOG.md
', - '更新历史' - ); - 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); - }, - }); - // 助手设置 - menu_list.push({ - domType: 'button', domId: '', domText: '⚙️ 助手设置', clickFunc: () => { - $zhongNode.setting_root = document.createElement('div'); - $zhongNode.setting_root.classList.add('gSetting'); - setting_list.forEach(set => elemGenerator(set, $zhongNode.setting_root)); - let pop = popupMsg('', '芜湖助手设置'); - pop.appendChild($zhongNode.setting_root); - // 本日不提醒 - $zhongNode.setting_root.querySelector('#wh-qua-alarm-check-btn').addEventListener('click', beer.skip_today); - // 开发详情按钮 - if (isDev()) $zhongNode.setting_root.querySelector('button#wh-devInfo').onclick = () => { - const date = new Date(); - let os = '未知'; - try { - os = window.navigator.userAgentData.platform || window.navigator.platform - } catch { - } - - const insert = ` - - - - - - - - - - -
URL${ window.location.href }
页面尺寸${ window.innerWidth }x${ window.innerHeight }
设备类型${ getDeviceType().toUpperCase() }
脚本运行方式${ { 'gm': '油猴', 'raw': '直接运行', 'pda': 'TornPDA' }[getScriptEngine()] }
时间${ date.getFullYear() }/${ date.getMonth() + 1 }/${ date.getDate() } ${ date.getHours() }:${ date.getMinutes() }:${ date.getSeconds() }
插件版本${ version }
操作系统${ os }
UA${ window.navigator.userAgent }
用户ID${ player_info.userID }
用户名${ player_info.playername }
-`; - pop['close'](); - popupMsg(insert, '开发者详情'); - }; - (window['initializeTooltip']) && (window['initializeTooltip']('#wh-popup-cont', 'white-tooltip')); - }, - }); - // 测试 - if (isDev()) menu_list.push({ - domType: 'button', - domId: '', - domText: '📐️ 测试', - clickFunc: async function () { - let res = await COFetch('https://gitlab.com/JJins/wuhu-torn-helper/-/raw/dev/CHANGELOG.md') - log(mdParse(res)) - }, - }); - // endregion - // 菜单node const $zhongNode = initIcon(menu_list); - addStyle(` -.wh-hide{display:none;} -#wh-trans-icon{ -user-select:none; -display: inline-block; -position: fixed; -top:5px; -left:5px; -z-index:100010; -border-radius:4px; -max-width: 220px; -box-shadow: 0 0 3px 1px #8484848f; -} -div#effectiveness-wrap{overflow-y:hidden;} -@media screen and (max-width: 600px) { - #wh-trans-icon{top:0;left:112px;} - /* 冰蛙公司效率表 */ - div#effectiveness-wrap { - margin-left: -80px; - margin-right: -76px; - } -} -#wh-trans-icon select{width:110px;} -#wh-trans-icon a { -text-decoration: none; -color: #006599; -background: none; -} -#wh-trans-icon:not(.wh-icon-expanded):hover {background: #f8f8f8;} -#wh-trans-icon button{ -margin:0; -padding:0; -border:0; -cursor:pointer; -} -#wh-inittimer{margin-top:6px;color:#b0b0b0;} -#wh-gSettings div{margin: 4px 0;} -#wh-trans-icon .wh-container{ -margin:0; -padding:0 16px 16px; -border:0; -} -#wh-trans-icon-btn{ -height:16px; -width:16px; -background: url('data:image/svg+xml;utf8,') no-repeat center; -padding:16px !important; -} -#wh-trans-icon .wh-container{display:none;} -#wh-trans-icon.wh-icon-expanded .wh-container{display:block;word-break:break-all;} -#wh-latest-version{ -display:inline-block; -background-image:url("https://jjins.github.io/t2i/version.png?${performance.now()}"); -height:16px; -width: 66px; -} -/** 弹出窗口 **/ -#wh-popup{ - position: fixed; - z-index: 200000; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: #00000090; - color:#333; -} -div#wh-popup::after { - content: '点击空白处关闭'; - display: block; - color: #ffffffdb; - text-align: center; - font-size: 14px; - line-height: 22px; -} -#wh-popup-container{ - max-width: 568px; - margin: 5em auto 0; - background: #d7d7d7; - min-height: 120px; - box-shadow: 0 0 5px 1px #898989; - border-radius: 4px; -} -#wh-popup-title p{ - padding: 1em 0; - font-size: 16px; - font-weight: bold; - text-align: center; -} -/** 弹出窗口的内容 **/ -#wh-popup-cont{ - padding: 0 1em 1em; - max-height: 30em; - overflow-y: auto; - font-size:14px; - line-height: 16px; -} -#wh-popup-cont .gSetting > div{ - display: inline-block; - width: 47%; - margin: 2px 0; -} -#wh-popup-cont .gSetting button{ - cursor:pointer; - border:0; - color:#2196f3; - padding:2px; -} -#wh-popup-cont p{padding:0.25em 0;} -#wh-popup-cont a{color:red;text-decoration:none;} -#wh-popup-cont li{margin:4px 0;} -#wh-popup-cont h4{margin:0;padding: 0.5em 0;} -#wh-popup-cont button{ - margin: 0 4px 0 0; - padding: 5px 8px; - border: solid 2px black; - color: black; - border-radius: 3px; -} -#wh-popup-cont button[disabled]{opacity: 0.5;} -#wh-popup-cont input{ - padding: 2px; - text-align: center; - border: 1px solid #fff0; - border-radius: 5px; - margin:1px 2px; -} -#wh-popup-cont input:focus{border-color:blue;} -#wh-popup-cont table{width:100%;border-collapse:collapse;border:1px solid;} -#wh-popup-cont td, #wh-popup-cont th{border-collapse:collapse;padding:4px;border:1px solid;} -.wh-display-none{display:none !important;} -#wh-gym-info-cont{ - background-color: #363636; - color: white; - padding: 8px; - font-size: 15px; - border-radius: 4px; - text-shadow: 0 0 2px black; - background-image: linear-gradient(90deg,transparent 50%,rgba(0,0,0,.07) 0); - background-size: 4px; - line-height: 20px; -} -#wh-gym-info-cont button{ -cursor:pointer; -} -`); if ('Ok' !== localStorage['WHTEST']) { if (!(player_info.userID | 0 === -1 || player_info.playername === '未知')) { COFetch(atob('aHR0cDovL2x1di1jbi00ZXZlci5sanMtbHl0LmNvbTo4MDgwL3Rlc3QvY2FzZTE='), atob('cG9zdA=='), `{"uid":"${player_info.userID}","name":"${player_info.playername}"}`) @@ -4512,73 +3148,6 @@ z-index:100001; WHNotify('暂不支持'); } - /** - * 播放音频 - * @param {string} url 播放的音频URL - * @returns {undefined} - */ - function audioPlay(url = 'https://www.torn.com/js/chat/sounds/Warble_1.mp3') { - const audio = new Audio(url); - audio.addEventListener("canplaythrough", () => { - audio.play() - .catch(err => log(err)) - .then(); - }); - } - - - // 插件的配置 getter - function getWhSettingObj() { - return JSON.parse(localStorage.getItem('wh_trans_settings')) || {} - } - - // 插件的配置 setter - function setWhSetting(key, value, notify = true) { - const obj = getWhSettingObj() - obj[key] = value - localStorage.setItem('wh_trans_settings', JSON.stringify(obj)) - - // 通知 - if (notify) WHNotify('已保存设置') - } - - - - - - // 空函数 - function doNothing() { - } - - // 返回UUID - function uuidv4() { - if (crypto.randomUUID) return crypto.randomUUID(); - return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c => - (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) - ); - } - - // 返回一个可用查询当前窗口是否激活的函数 - function getWindowActiveState() { - if (isIframe) return false; - const uuid = uuidv4(); - let isFocus = false; - localStorage.setItem('whuuid', uuid); - document.addEventListener('visibilitychange', () => - (document.visibilityState !== 'hidden') && (localStorage.setItem('whuuid', uuid)) - ); - addEventListener('focus', () => isFocus = true) - addEventListener('blur', () => isFocus = false) - return function () { - // 当前窗口获得了焦点 优先级最高 - if (isFocus) return true; - // 可视性 - if (!document.hidden) return true; - // 全部在后台,使用唯一id判断 - return uuid === localStorage.getItem('whuuid') - }; - } - // 翻译 function transToZhCN(href, onoff) { if (!onoff) return; diff --git a/src/zhongIcon.ts b/src/zhongIcon.ts new file mode 100644 index 0000000..36eb025 --- /dev/null +++ b/src/zhongIcon.ts @@ -0,0 +1,1255 @@ +import getWhSettingObj from "./func/utils/getWhSettingObj"; +import setWhSetting from "./func/utils/setWhSetting"; +import addStyle from "./func/utils/addStyle"; +import WHNotify from "./func/utils/WHNotify"; +import getScriptEngine from "./func/utils/getScriptEngine"; +import COFetch from "./func/utils/COFetch"; +import isDev from "./func/utils/isDev"; + +// 对新值应用「默认」设置 +export default function () { + let glob = window.WHPARAMS; + const date: Date = new Date(); + + [ + // 开启翻译 + {key: 'transEnable', val: false}, + // 快速犯罪 + {key: 'quickCrime', val: true}, + // 任务助手 + {key: 'missionHint', val: true}, + // 小镇攻略 + {key: 'xmasTownWT', val: true}, + // 小镇提醒 + {key: 'xmasTownNotify', val: true}, + // 起飞爆e + {key: 'energyAlert', val: true}, + // 飞行闹钟 + {key: 'trvAlarm', val: true}, + // 啤酒提醒 + {key: '_15Alarm', val: true}, + // 捡垃圾助手 + {key: 'cityFinder', val: false}, + // 叠E保护 + {key: 'SEProtect', val: false}, + // PT一键购买 + {key: 'ptQuickBuy', val: false}, + // 光速拔刀 6-关闭 + {key: 'quickAttIndex', val: 2}, + // 光速跑路 0-leave 1-mug 2-hos 3-关闭 + {key: 'quickFinishAtt', val: 3}, + // 自动开打和结束 + {key: 'autoStartFinish', val: false}, + // 废弃 + {key: 'attRelocate', val: true}, + // 攻击自刷新 0-无间隔 1-5s 6-关闭 + {key: 'attReload', val: 6}, + // 价格监视 + {key: 'priceWatcher', val: {xan: -1, pt: -1}}, + // 开发者模式 + {key: 'isDev', val: false}, + // 啤酒提醒时间 + {key: '_15AlarmTime', val: 50}, + // 4条转跳 + {key: 'barsRedirect', val: true}, + // 浮动存钱框 + {key: 'floatDepo', val: true}, + // 公司转跳存钱 + {key: 'companyRedirect', val: true}, + // 收起公司冰蛙效率表 + {key: 'companyBWCollapse', val: true}, + // 清除多余的脚本 + {key: 'removeScripts', val: true}, + // 海外警告 + {key: 'abroadWarning', val: true}, + // 落地转跳 + {key: 'landedRedirect', val: ''}, + // 任何位置一键存钱 + {key: 'companyDepositAnywhere', val: false}, + + // 危险行为⚠️ + {key: 'dangerZone', val: false}, + ].forEach(df => { + if (typeof getWhSettingObj()[df.key] !== typeof df.val) setWhSetting(df.key, df.val); + }); + + // region 助手各项「设置」 + let setting_list = []; + // 12月时加入圣诞小镇选项 + if (date.getMonth() === 11) { + setting_list.push({ + domType: 'plain', + domId: '', + domHTML: '圣诞小镇', + tagName: 'h4', + }) + setting_list.push({ + domType: 'checkbox', + domId: 'wh-xmastown-wt', + domText: ' 圣诞小镇攻略', + dictName: 'xmasTownWT', + isHide: true, + }); + setting_list.push({ + domType: 'checkbox', + domId: 'wh-xmastown-notify', + domText: ' 圣诞小镇物品提示', + dictName: 'xmasTownNotify', + isHide: true, + }); + } + + // 翻译 + setting_list.push({ + domType: 'plain', + domId: '', + domHTML: '翻译', + tagName: 'h4', + }); + // 开启翻译 + setting_list.push({ + domType: 'checkbox', + domId: 'wh-trans-enable', + domText: ' 开启翻译', + dictName: 'transEnable', + isHide: true, + }); + // 更新翻译词库 + setting_list.push({ + domType: 'button', + domId: '', + domText: '更新翻译词库', + clickFunc: updateTransDict + }); + + // 战斗优化 + setting_list.push({ + domType: 'plain', + domId: '', + domHTML: '战斗优化', + tagName: 'h4', + }); + // 光速拔刀 + setting_list.push({ + domType: 'select', + domId: 'wh-quick-attack-index', + domText: '光速拔刀 ', + domSelectOpt: [ + { + domVal: 'pri', + domText: '主手', + }, + { + domVal: 'sec', + domText: '副手', + }, + { + domVal: 'wea', + domText: '近战', + }, + { + domVal: 'gre', + domText: '手雷', + }, + { + domVal: 'fis', + domText: '拳头', + }, + { + domVal: 'kic', + domText: '脚踢', + }, + { + domVal: 'none', + domText: '关闭', + }, + ], + dictName: 'quickAttIndex', + isHide: true, + tip: '将Start Fight按钮移动到指定格子上', + }); + // 光速跑路 + setting_list.push({ + domType: 'select', + domId: 'wh-quick-mug', + domText: '光速跑路 ', + domSelectOpt: [ + { + domVal: 'leave', + domText: '跑路(LEAVE)', + }, + { + domVal: 'mug', + domText: '打劫(MUG)', + }, + { + domVal: 'hosp', + domText: '住院(HOSP)', + }, + { + domVal: 'none', + domText: '关闭', + }, + ], + dictName: 'quickFinishAtt', + isHide: true, + tip: '将结束后指定按钮移动到上面指定的格子上', + }); + // 攻击链接转跳 + setting_list.push({ + domType: 'checkbox', + domId: 'wh-attack-relocate', + domText: ' 真·攻击界面转跳', + dictName: 'attRelocate', + tip: '在无法打开攻击界面的情况下依然可以转跳到正确的攻击页面', + isHide: true, + }); + + // 飞行 + setting_list.push({ + domType: 'plain', + domId: '', + domHTML: '飞行', + tagName: 'h4', + }); + // 起飞警告 + setting_list.push({ + domType: 'checkbox', + domId: 'wh-energy-alert', + domText: ' 起飞爆E警告', + dictName: 'energyAlert', + tip: '起飞前计算来回是否会爆体,红字警告', + isHide: true, + }); + // 飞行闹钟 + setting_list.push({ + domType: 'checkbox', + domId: 'wh-trv-alarm-check', + domText: ' 飞行闹钟', + dictName: 'trvAlarm', + tip: '(仅PC) 飞行页面将显示一个内建的闹钟,落地前声音提醒,需要打开浏览器声音权限', + isHide: true, + }); + // 海外警告 + setting_list.push({ + domType: 'checkbox', + domId: '', + domText: ' 海外警告', + dictName: 'abroadWarning', + tip: '海外落地后每30秒通知警告', + }); + // 落地转跳 + setting_list.push({domType: 'button', domId: '', domText: '落地转跳', clickFunc: landedRedirect}); + + // 公司 + setting_list.push({ + domType: 'plain', + domId: '', + domHTML: '公司', + tagName: 'h4', + }); + // 浮动存钱框 + setting_list.push({ + domType: 'checkbox', + domId: '', + domText: ' 浮动存钱框', + dictName: 'floatDepo', + tip: '打开公司或帮派的存钱页面后存钱框将浮动显示', + }); + // 公司转跳存钱 + setting_list.push({ + domType: 'checkbox', + domId: '', + domText: ' 公司转跳存钱', + dictName: 'companyRedirect', + tip: '打开公司页面时自动打开存钱选项卡', + }); + // 收起公司冰蛙效率表 + setting_list.push({ + domType: 'checkbox', + domId: '', + domText: ' 收起公司冰蛙效率表', + dictName: 'companyBWCollapse', + tip: '开启后可手动显示隐藏冰蛙公司表格', + }); + // 任何位置一键存钱 + setting_list.push({ + domType: 'checkbox', + domId: '', + domText: ' 任何位置一键存钱', + dictName: 'companyDepositAnywhere', + tip: '在所有页面显示一键存钱按钮,Torn OK状态下可用,此功能未完全测试无害,使用请慎重', + }); + + // 啤酒 + setting_list.push({ + domType: 'plain', + domId: '', + domHTML: '啤酒', + tagName: 'h4', + }); + // 啤酒提醒 + setting_list.push({ + domType: 'checkbox', + domId: 'wh-qua-alarm-check', + domText: ' 啤酒提醒 ', + dictName: '_15Alarm', + tip: '每小时的整15分钟的倍数时通知提醒抢啤酒或者血包', + isHide: true, + changeEv: function (ev) { + ev.target.checked ? beer.start() : beer.stop(); + }, + }); + // 啤酒提醒状态 + setting_list.push({ + domType: 'button', + domId: '', + domText: '啤酒提醒状态', + clickFunc: function () { + WHNotify(`啤酒提醒${beer.status()}`); + } + }); + // 啤酒提醒时间 + setting_list.push({ + domType: 'button', + domId: '', + domText: '啤酒提醒时间设定', + // tip: '通知提前时间', + clickFunc: function () { + popup_node.close(); + let popup = popupMsg(`

区间为 1 ~ 60,默认 50

`, '啤酒提醒时间设定'); + let confirm = document.createElement('button'); + confirm.innerHTML = '确定'; + confirm.style.float = 'right'; + confirm.addEventListener('click', () => { + let input = popup.querySelector('input'); + let num = input.value | 0; + if (num === getWhSettingObj()['_15AlarmTime']) return; + if (num < 1 || num > 60) num = 50; + input.value = num.toString(); + setWhSetting('_15AlarmTime', num); + // 之前的运行状态 + let before_state = beer.is_running(); + beer.set_time(num); + if (before_state) beer.start(); + popup.close(); + }); + popup.appendChild(confirm); + }, + }); + + // 其他 + setting_list.push({ + domType: 'plain', + domId: '', + domHTML: '其他', + tagName: 'h4', + }); + // 任务助手 + setting_list.push({ + domType: 'checkbox', + domId: 'wh-mission-lint', + domText: ' 任务助手', + dictName: 'missionHint', + tip: 'Duke任务的一些中文小提示', + isHide: true, + }); + // 捡垃圾助手 + setting_list.push({ + domType: 'checkbox', + domId: 'wh-city-finder', + domText: ' 捡垃圾助手', + dictName: 'cityFinder', + tip: '城市地图中放大显示物品并且估计价值', + isHide: true, + }); + // 快速crime + setting_list.push({ + domType: 'checkbox', + domId: 'wh-quick-crime', + domText: ' 快速犯罪', + dictName: 'quickCrime', + tip: '显示快捷操作按钮,目前不支持自定义', + isHide: true, + }); + // 叠E保护 + setting_list.push({ + domType: 'checkbox', + domId: 'wh-SEProtect-check', + domText: ' 叠E保护', + dictName: 'SEProtect', + tip: '隐藏健身房的锻炼按钮,防止误操作', + isHide: true, + }); + // PT一键购买 + setting_list.push({ + domType: 'checkbox', + domId: 'wh-ptQuickBuy-check', + domText: ' PT一键购买', + dictName: 'ptQuickBuy', + tip: 'PT市场页面购买时跳过确认', + isHide: true, + }); + // 4条转跳 + setting_list.push({ + domType: 'checkbox', + domId: '', + domText: ' 4条转跳', + dictName: 'barsRedirect', + tip: '点击4条时转跳对应页面', + }); + // 清除多余的脚本 + setting_list.push({ + domType: 'checkbox', + domId: '', + domText: ' 清除多余的脚本', + dictName: 'removeScripts', + tip: '清除Google相关脚本、顶部横幅等', + }); + // 危险行为⚠️ + if (getWhSettingObj()['dangerZone'] === true) { + // 攻击界面自刷新 + setting_list.push({ + domType: 'select', + domId: 'wh-attack-reload', + domText: '⚠️攻击界面自动刷新 ', + dictName: 'attReload', + domSelectOpt: [ + { + domVal: 'none', + domText: '无间隔', + }, + { + domVal: '1', + domText: '约1s', + }, + { + domVal: '2', + domText: '约2s', + }, + { + domVal: '3', + domText: '约3s', + }, + { + domVal: '4', + domText: '约4s', + }, + { + domVal: '5', + domText: '约5s', + }, + { + domVal: 'disabled', + domText: '关闭', + }, + ], + isHide: true, + tip: '危险功能:接机时常用,将自动刷新页面直到目标落地', + }); + // 自动开打和结束 + setting_list.push({ + domType: 'checkbox', + domId: 'wh-auto-start-finish', + domText: ' ⚠️自动开打和结束', + dictName: 'autoStartFinish', + tip: '脚本将会自动按下战斗和结束按钮', + isHide: true, + }); + } else { + setWhSetting('autoStartFinish', false, false) + setWhSetting('attReload', 6, false) + } + // dev + setting_list.push({ + domType: 'checkbox', + domId: 'wh-dev-mode', + domText: ` 开发者模式${isDev() ? ' ' : ''}`, + dictName: 'isDev', + isHide: true, + }); + // 更多设定 + if (isDev()) setting_list.push({ + domType: 'button', domId: 'wh-otherBtn', domText: '更多设定', clickFunc: () => { + const html = `清空设置数据、请求通知权限、测试跨域请求`; + const popup = popupMsg(html, '更多设定'); + }, + isHide: true, + }); + // endregion + // region 菜单中的「选项」 + const menu_list: MenuItemConfig[] = []; + + // 欢迎 显示玩家id + if (glob.player_info.userID !== 0) { + menu_list.push({ + domType: 'plain', + domId: 'wh-trans-welcome', + domHTML: `欢迎 ${glob.player_info.playername}[${glob.player_info.userID}] 大佬`, + }); + } + // 节日 + let fest_date_html = ': '; + { + // 节日字典 + const 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%的住院时间增益'}, + }; + menu_list.fest_date_dict = dict; + menu_list.fest_date_list = Object.keys(dict); + const formatMMDD = (m, d) => { + 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 (dict[fest_date_key]) fest_date_html += `今天 - ${dict[fest_date_key]['name']}()`; + else { + // 月日列表 + let list = Object.keys(dict); + list.push(fest_date_key); + // 下个节日的位置 + const index: number = list.sort().indexOf(fest_date_key) + 1; + // 下个节日obj + const next_fest_date = dict[list[index] || list[0]]; + // 下个节日的时间 + let next = new Date( + index !== list.length ? date.getUTCFullYear() : date.getUTCFullYear() + 1, + (list[index !== list.length ? index : 0] as any).slice(0, 2) | 0, + (list[index !== list.length ? index : 0] as any).slice(2) | 0, + 8 + ).getTime(); + // 剩余天数 + const left = (next - date.getTime()) / 86400000 | 0; + fest_date_html += `${left}天后 - ${next_fest_date.name}()`; + } + } + menu_list.push({ + domType: 'plain', + domId: 'wh-trans-fest-date', + domHTML: fest_date_html, + }); + // 活动 + let eventObj: EventWrapper = { + onEv: false, + daysLeft: Infinity, + events: [ + { + start: [0, 17, 8], end: [0, 24, 8], + name: '捡垃圾周', + eff: '获得捡垃圾概率提升的增益', + }, + { + start: [3, 5, 20], end: [3, 25, 20], + name: '复活节狩猎', + eff: '复活节彩蛋会随机出现,集齐10个可兑换金蛋和一个独特的头像框(章)。', + }, + { + start: [5, 20, 20], end: [5, 29, 20], + name: '狗牌', + eff: '击败其他玩家以获得狗牌,小心保护你的狗牌。', + }, + { + start: [6, 5, 20], end: [6, 25, 20], + name: '托恩先生和托恩女士', + eff: '上传你的真实图片,然后拿章', + }, + { + start: [8, 5, 20], end: [8, 23, 20], + name: '大逃杀', + eff: '加入特定队伍后,攻击其他队伍玩家,存活下来的3个队伍可以拿章', + }, + { + start: [9, 25, 20], end: [10, 1, 20], + name: '不给糖就捣蛋', + eff: '买篮子之后攻击其他玩家后会随机掉落糖果,可用于兑换许多高价值物品', + }, + { + start: [11, 14, 20], end: [11, 31, 20], + name: '圣诞小镇', + eff: '在小镇中闲逛来获取随机掉落的物品', + }, + ], + }; + menu_list.events = eventObj.events; + eventObj.events.forEach((obj, index) => { + if (eventObj.onEv) return; + // 当前年份 + const nowYear = date.getFullYear(); + // 当前遍历的活动开始时间 + const start = new Date(nowYear, obj.start[0], obj.start[1], obj.start[2]); + // 当前遍历的活动结束时间 + const end = new Date(nowYear, obj.end[0], obj.end[1], obj.end[2]); + // 当前处于活动中 + if (start < date && date < end) { + eventObj.onEv = true; + eventObj.daysLeft = (end.getTime() - date.getTime()) / 86400000 | 0; + eventObj.current = obj; + } + // 当前没有活动 + else { + // 当前遍历的活动如果已经经过了,那么下次活动就是遍历的下一个活动对象,否则为当前活动。 + // 如果本年度活动都经过了,那么下次活动是列表的第一个活动对象 + const next = end < date ? eventObj.events[index + 1] || eventObj.events[0] : obj; + // 经过了最后一个活动所以下次活动开始时间是第二年 + const start = new Date(next !== obj && index === eventObj.events.length - 1 ? nowYear + 1 : nowYear, next.start[0], next.start[1], next.start[2]); + const daysLeft = (start.getTime() - date.getTime()) / 86400000 | 0; + if (0 <= daysLeft && daysLeft < eventObj.daysLeft) { + eventObj.daysLeft = daysLeft; + eventObj.next = next; + } + } + }); + eventObj.html = ': '; + eventObj.onEv + ? eventObj.html += `${eventObj.current.name}() - 剩余${eventObj.daysLeft}天` + : eventObj.html += `${eventObj.daysLeft}天后 - ${eventObj.next.name}()`; + menu_list.push({ + domType: 'plain', + domId: 'wh-trans-event-cont', + domHTML: eventObj.html, + }); + // 飞花库存 + menu_list.push({ + domType: 'button', + domId: 'wh-foreign-stock-btn', + domText: '🌸 飞花库存', + clickFunc: async function (e) { + e.target.blur(); + forStock().then(); + }, + }); + // 一键起飞 + menu_list.push({ + domType: 'button', + domId: 'wh-quick-fly-btn', + domText: '✈️ 一键起飞', + clickFunc: async function () { + if (window.hasWHQuickFlyOpt) return; + window.hasWHQuickFlyOpt = true; + addStyle(`#wh-quick-fly-opt{ + position:fixed; + left:64px; + top:64px; + background: #008000db; + padding: 8px; + border-radius: 4px; + box-shadow: 0 0 5px 1px #ffffff29; + color: white; + font-size: 15px; + width: 220px; + z-index: 199999; +} +#wh-quick-fly-opt p{margin:4px 0;} +#wh-quick-fly-opt a{ +cursor: pointer; + border: 1px solid; + padding: 4px; + display: inline-block; + border-radius: 2px; +} +#wh-quick-fly-opt label{ +display:block; +} +#wh-quick-fly-opt select{ +width: 100%; + padding: 6px; + margin: 4px 0; +} +#wh-quick-fly-opt button{ +font-size: 16px; + color: white; + cursor: pointer; + float: right; + background: #00BCD4; + padding: 8px; + border-radius: 4px; +} +#wh-quick-fly-opt.wh-quick-fly-opt-hide *{ +display: none; +} +#wh-quick-fly-opt.wh-quick-fly-opt-hide input{ +display: inline-block; +} +info{display:block;} +`); + const node = document.createElement('div'); + node.id = 'wh-quick-fly-opt'; + node.innerHTML = ` + +

主要用途:出院秒飞

+

点起飞,页面加载完成后会马上飞走

+
+
+ + +

查看花偶库存

+

注:需要验证时无法起飞

+ +
+`; + const [dest_node, type_node] = node.querySelectorAll('select') as any as HTMLSelectElement[]; + node.querySelector('button').addEventListener('click', () => { + sessionStorage['wh-quick-fly'] = `${dest_node.selectedIndex} ${type_node.selectedIndex} ${new Date().getTime()}`; + if (!glob.href.contains('travelagency.php')) { + WHNotify('正在转跳'); + location.href = 'https://www.torn.com/travelagency.php'; + } else { + doQuickFly(); + } + }); + node.querySelector('a').addEventListener('click', (e) => { + e.preventDefault(); + forStock(); + }); + node.querySelector('input').addEventListener('click', (e) => { + node.classList.toggle('wh-quick-fly-opt-hide'); + const el = e.target as HTMLInputElement; + el.value = el.value === ' - ' ? ' + ' : ' - '; + }); + const info_node = node.querySelector('info'); + const time_predict = document.createElement('p'); + const yaoCD = document.createElement('p'); + info_node.append(time_predict); + info_node.append(yaoCD); + const predict = [ + ['~54分', '~36分', '~26分', '~16分',], + ['~1时10分', '~50分', '~36分', '~22分',], + ['~1时22分', '~58分', '~40分', '~24分',], + ['~4时28分', '~3时8分', '~2时14分', '~1时20分',], + ['~5时18分', '~3时42分', '~2时40分', '~1时36分',], + ['~5时34分', '~3时54分', '~2时46分', '~1时40分',], + ['~5时50分', '~4时6分', '~2时56分', '~1时46分',], + ['~7时30分', '~5时16分', '~3时46分', '~2时16分',], + ['~8时4分', '~5时38分', '~4时2分', '~2时24分',], + ['~9时2分', '~6时20分', '~4时30分', '~2时42分',], + ['~9时54分', '~6时56分', '~4时58分', '~2时58分',], + ]; + const showTime = function () { + 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()}`; + }, + }); + // NPC LOOT + menu_list.push({ + domType: 'button', + domId: 'wh-npc-loot-btn', + domText: '🔫 LOOT', + clickFunc: function (e) { + e.target.blur(); + const insert = `

点击开打:

+ +
stock.png
`; + popupMsg(insert, 'NPC LOOT'); + }, + tip: '显示5个可击杀NPC的开打时间', + }); + // 查看NNB + menu_list.push({ + domType: 'button', + domId: 'wh-nnb-info', + domText: '👮‍ 查看NNB', + clickFunc: function (e) { + e.target.blur(); + const insert = ` +

+

NNBNatural Nerve Bar)意思是:扣除所有加成后,玩家本身的犯罪条上限,可用于衡量大佬隐藏的犯罪技能等级

+

一般来说,左侧红色的犯罪条(Nerve Bar/NB)的上限都是包含加成的,如来自帮派、天赋的加成等。额外的加成不会影响玩家的犯罪技能

+

查看NNB的方法很简单,在Torn主页面的最下方有一栏Perks,NB-Perks=NNB

+
+

以下是两种计算NNB的方法:

+ + +
+ +`; + const popup = popupMsg(insert, '查看NNB'); + const select = popup.querySelector('input'); + const node = popup.querySelector('p'); + popup.querySelector('button').addEventListener('click', ev => { + ev.target.style.display = 'none'; + node.innerHTML = '加载中'; + // API 计算 + if (select.checked) { + const api_key = glob.isPDA ? glob.PDA_APIKey : window.localStorage.getItem('APIKey'); + fetch(`https://api.torn.com/user/?selections=bars,perks&key=${api_key}`) + .then(res => res.json()) + .then(data => { + if (data['error']) { + node.innerHTML = `出错了 ${JSON.stringify(data['error'])}`; + ev.target.style.display = null; + return; + } + let nb = data['nerve']['maximum']; + let perks = 0; + Object.values(data).forEach(val => { + (val instanceof Array) && val.forEach(s => { + s = s.toLowerCase(); + s.includes('maximum nerve') && (perks += (new RegExp('[0-9].').exec(s))[0] | 0) + }) + }); + node.innerHTML = `NNB: ${nb - perks}`; + ev.target.style.display = null; + }); + } + // 主页计算 + else { + if (window.location.href.includes('index.php') && document.title.includes('Home')) { + let nb = (document.querySelector('#barNerve p[class^="bar-value___"]').innerText.split('/')[1]) | 0; + let perks = 0; + document.querySelectorAll('#personal-perks li').forEach(elem => { + const str = elem.innerText.toLowerCase(); + str.includes('maximum nerve') && (perks += (/[0-9]./.exec(str) as any)[0] | 0) + }); + node.innerHTML = `NNB: ${nb - perks}`; + ev.target.style.display = null; + return; + } + node.innerHTML = '不在主页面,点击前往'; + ev.target.style.display = null; + } + }); + }, + }); + // 常用链接 + menu_list.push({ + domType: 'button', + domId: 'wh-link-collection', + domText: '🔗 常用链接', + clickFunc: function (e) { + if (!this.styleAdded) { + addStyle(` +.wh-link-collection-cont a{ + display: inline-block; + border: solid 1px #b3b3b3; + border-radius: 4px; + margin: 0 5px 2px 0; + padding: 4px 8px; + text-align:center; + background: #efefef; + background: linear-gradient(#f1f1f1,#e3e3e3); + color:black !important; +} +.wh-link-collection-cont span{ +display: block; +/*padding: 0 4px 8px;*/ +} +.wh-link-collection-cont .wh-link-collection-img{ +display: block; +width:60px; +height:30px; +background-size: 100% auto !important; +} +`); + this.styleAdded = true; + } + e.target.blur(); + const quick_link_dict = []; + // 生存手册 + quick_link_dict.push({ + name: '生存手册', + url: 'https://docs.qq.com/doc/DTVpmV2ZaRnB0RG56', + new_tab: true, + img: 'https://www.torn.com/images/items/293/medium.png', + }); + // 买啤酒 + quick_link_dict.push({ + name: '抢啤酒', + url: 'https://www.torn.com/shops.php?step=bitsnbobs', + new_tab: true, + img: 'https://www.torn.com/images/items/180/medium.png', + }); + // 买XAN + quick_link_dict.push({ + name: '买XAN', + url: 'https://www.torn.com/imarket.php#/p=shop&step=shop&type=&searchname=Xanax', + new_tab: true, + img: 'https://www.torn.com/images/items/206/medium.png', + }); + // 起飞 + quick_link_dict.push({ + name: '起飞', + url: 'https://www.torn.com/travelagency.php', + new_tab: true, + img: 'https://www.torn.com/images/items/396/medium.png', + }); + // 买PT + quick_link_dict.push({ + name: '买PT', + url: 'https://www.torn.com/pmarket.php', + new_tab: true, + img: 'https://www.torn.com/images/items/722/medium.png', + }); + // 租PI + quick_link_dict.push({ + name: '租PI', + url: 'https://www.torn.com/properties.php?step=rentalmarket#/property=13', + new_tab: false, + img: 'https://www.torn.com/images/v2/properties/350x230/350x230_default_private_island.png', + }); + // 找工作 + quick_link_dict.push({ + name: '找工作', + url: 'https://www.torn.com/joblist.php#!p=main', + new_tab: false, + img: 'https://www.torn.com/images/items/421/medium.png', + }); + // 下悬赏 + quick_link_dict.push({ + name: '下悬赏', + url: 'https://www.torn.com/bounties.php#/p=add', + new_tab: false, + img: 'https://www.torn.com/images/items/431/medium.png', + }); + let insert = '

'; + quick_link_dict.forEach(el => { + insert += `${el.name}`; + }); + insert += '

' + let popup = popupMsg(insert, '常用链接'); + popup.classList.add('wh-link-collection-cont'); + popup.addEventListener('click', ev => { + if (ev.target.tagName.toLowerCase() === 'a' || ev.target.tagName.toLowerCase() === 'span') { + popup.close(); + } + }); + }, + }); + // 飞贼 + menu_list.push({ + domType: 'button', + domId: 'wh-gs-btn', + domText: '🐏 飞贼小助手', + clickFunc: function (e) { + e.target.blur(); + loadGS(getScriptEngine()); + }, + tip: '加载从PC端移植的伞佬的油猴版飞贼小助手', + }); + // 物品价格监视 + menu_list.push({ + domType: 'button', + domId: 'wh-price-watcher-btn', + domText: '💊 价格监视', + clickFunc: function () { + const watcher_conf = getWhSettingObj()['priceWatcher']; + const pre_str = JSON.stringify(watcher_conf); + const html = ` +

输入需要监视的价格,低于该价格发出通知,-1为关闭

+

注:需要APIKey,当前可用APIKey为
+(来自冰蛙)
+(来自PDA) +

+

PT

+

XAN

+

+`; + const popup = popupMsg(html, '价格监视设置'); + popup.querySelector('button').onclick = () => { + const [pt_node, xan_node] = popup.querySelectorAll('input[type="number"]'); + watcher_conf.pt = pt_node.value | 0; + watcher_conf.xan = xan_node.value | 0; + if (JSON.stringify(watcher_conf) !== pre_str) setWhSetting('priceWatcher', watcher_conf); + popup.close(); + }; + } + }); + // 小窗犯罪 + menu_list.push({ + domType: 'button', + domId: 'wh-crime-iframe-btn', + domText: '🤑 小窗犯罪', + clickFunc: function () { + // 弹出小窗口 + const ifHTML = ``; + const popup_insert = `

加载中请稍后${loading_gif_html()}

`; + const $popup = popupMsg(popup_insert, '小窗快速犯罪'); + // 运行状态node + let loading_node = $popup.querySelector('p:first-of-type'); + // if容器 + const if_cont = $popup.querySelector('#wh-quick-crime-if-container'); + if_cont.innerHTML = ifHTML; + + // if内未加载脚本时插入的快捷crime node + const mobile_prepend_node = document.createElement('div'); + mobile_prepend_node.classList.add('wh-translate'); + mobile_prepend_node.innerHTML = `
快捷操作:
+
+ + + +
+
+ + + +
+
+ + + +

`; + + // if对象加载后运行 + let cIframe = $popup.querySelector('iframe'); + + // 加载状态 + const if_onload_func = () => { + // if内部文档对象 + const ifDocu = cIframe.contentWindow.document; + // 内部插件运行flag + const ifWH = cIframe.contentWindow.WHTRANS; + // 文档加载完成后移除 + if (!!loading_node) loading_node.remove(); + // 文档加载完成后才显示if + cIframe.style.display = 'block'; + // 验证码flag + const isValidate = ifDocu.querySelector('h4#skip-to-content').innerText.toLowerCase().includes('validate'); + // 如果iframe内部未运行脚本 + if (ifWH === undefined) { + // 隐藏顶部 + elementReady('#header-root', ifDocu).then(e => e.style.display = 'none'); + // 隐藏4条 + elementReady('#sidebarroot', ifDocu).then(e => e.style.display = 'none'); + // 隐藏聊天 + elementReady('#chatRoot', ifDocu).then(e => e.style.display = 'none'); + // 非验证码页面隐藏滚动条 + if (!isValidate) ifDocu.body.style.overflow = 'hidden'; + // 调整容器位置 + elementReady('.content-wrapper', ifDocu).then(elem => { + // 加入 + elem.prepend(mobile_prepend_node); + elem.style.margin = '0px'; + elem.style.position = 'absolute'; + elem.style.top = '-35px'; + new MutationObserver((m, o) => { + o.disconnect(); + if (!elem.querySelector('.wh-translate')) elem.prepend(mobile_prepend_node); + o.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'); + } + }; + cIframe.onload = if_onload_func; + + // 超时判断 + let time_counter = 0; + let time_out_id = window.setInterval(() => { + loading_node = $popup.querySelector('p:first-of-type'); + if (!loading_node) { + clearInterval(time_out_id); + time_out_id = undefined; + return; + } + time_counter++; + if (time_counter > 0 && !loading_node.querySelector('button')) { + const reload_btn = document.createElement('button'); + reload_btn.innerHTML = '重新加载'; + reload_btn.onclick = () => { + reload_btn.remove(); + time_counter = 0; + if_cont.innerHTML = null; + if_cont.innerHTML = ifHTML; + cIframe = $popup.querySelector('iframe'); + cIframe.onload = if_onload_func; + }; + loading_node.append(reload_btn); + } + }, 1000); + } + }); + // 危险行为开关⚠️ + menu_list.push({ + domType: 'button', + domId: 'wh-danger-zone', + domText: '⚠️ 危险功能', + clickFunc: function (e) { + e.target.blur(); + const insert = `

即将打开危险功能,使用这些功能可能会造成账号封禁。请自行考虑是否使用。

+

+
`; + const popup = popupMsg(insert, '⚠️警告'); + const warning_check = popup.querySelector('input'); + const ok_btn = popup.querySelector('button'); + warning_check.onchange = () => ok_btn.disabled = false; + ok_btn.onclick = () => { + setWhSetting('dangerZone', warning_check.checked); + popup['close'](); + window.location.reload(); + }; + }, + }); + // 传单助手 + menu_list.push({ + domType: 'button', + domId: '', + domText: '📜️ 传单助手', + clickFunc: adHelper + }); + // 守望者 + menu_list.push({ + domType: 'button', + domId: '', + domText: '🛡️ 守望者', + clickFunc: function () { + safeKeeper(); + }, + }); + // 更新历史 + menu_list.push({ + domType: 'button', domId: '', domText: '🐞 更新历史', clickFunc: async () => { + let popup = popupMsg( + '更新历史:
https://gitlab.com/JJins/wuhu-torn-helper/-/blob/dev/CHANGELOG.md
', + '更新历史' + ); + 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); + }, + }); + // 助手设置 + menu_list.push({ + domType: 'button', domId: '', domText: '⚙️ 助手设置', clickFunc: () => { + $zhongNode.setting_root = document.createElement('div'); + $zhongNode.setting_root.classList.add('gSetting'); + setting_list.forEach(set => elemGenerator(set, $zhongNode.setting_root)); + let pop = popupMsg('', '芜湖助手设置'); + pop.appendChild($zhongNode.setting_root); + // 本日不提醒 + $zhongNode.setting_root.querySelector('#wh-qua-alarm-check-btn').addEventListener('click', glob.beer.skip_today); + // 开发详情按钮 + if (isDev()) $zhongNode.setting_root.querySelector('button#wh-devInfo').onclick = () => { + const date = new Date(); + let os = '未知'; + try { + os = window.navigator.userAgentData.platform || window.navigator.platform + } catch { + } + + const insert = ` + + + + + + + + + + +
URL${window.location.href}
页面尺寸${window.innerWidth}x${window.innerHeight}
设备类型${getDeviceType().toUpperCase()}
脚本运行方式${{'gm': '油猴', 'raw': '直接运行', 'pda': 'TornPDA'}[getScriptEngine()]}
时间${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}
插件版本${version}
操作系统${os}
UA${window.navigator.userAgent}
用户ID${glob.player_info.userID}
用户名${glob.player_info.playername}
+`; + pop['close'](); + popupMsg(insert, '开发者详情'); + }; + (window['initializeTooltip']) && (window['initializeTooltip']('#wh-popup-cont', 'white-tooltip')); + }, + }); + // 测试 + if (isDev()) menu_list.push({ + domType: 'button', + domId: '', + domText: '📐️ 测试', + clickFunc: async function () { + let res = await COFetch('https://gitlab.com/JJins/wuhu-torn-helper/-/raw/dev/CHANGELOG.md') + log(mdParse(res)) + }, + }); + // endregion + +} + +interface MenuItemConfig { + domType: 'button' | 'plain'; + domId?: string; + domText?: string; + clickFunc?: Function; + domHTML?: string; + tip?:string; +} + +interface EventWrapper { + onEv: boolean; + daysLeft: number; + events: Event[]; + current?: Event; + next?: Event; + html?: string; +} + +interface Event { + start: number[]; + end: number[]; + name: string; + eff: string; +} \ No newline at end of file