您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
1.自动激活最先结束地城的英雄;2.自动加速地城;3.每日访问一次仓库存放战利品
当前为
// ==UserScript== // @name WOD AFK Helper // @version 1.0.3 // @description 1.自动激活最先结束地城的英雄;2.自动加速地城;3.每日访问一次仓库存放战利品 // @author purupurupururu // @namespace https://github.com/purupurupururu // @match *://*.world-of-dungeons.org/wod/spiel/settings/heroes.php* // @match *://*.world-of-dungeons.org/wod/spiel/rewards/vote.php* // @icon http://info.world-of-dungeons.org/wod/css/WOD.gif // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_xmlhttpRequest // ==/UserScript== (function() { 'use strict'; // 解析字符串里的时间 function parseTime(text) { if ((/每日|立刻/).test(text)) return 0; const match = text.match(/(今天|明天)?\s(\d{2}):(\d{2})/); if (!match) throw new Error(`not support string:${text}`); const [_, dayPart, hours, minutes] = match; const date = new Date(); if (dayPart === '明天') { date.setDate(date.getDate() + 1); } date.setHours(hours, minutes); return date.getTime(); } function getOffsetCountdown(baseTime, offsetSeconds = 60) { return Math.floor((baseTime - Date.now()) / 1000) + offsetSeconds; } function formatTime(seconds) { const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = seconds % 60; return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`; } ///////////////////////////////////////////////////////////////////////////////// class State{ static STORAGE_KEY = 'WOD_HELPER_STATE'; static getState() { return GM_getValue(this.STORAGE_KEY, { lastStoredDate: 0, currentHeroIndex: 0, checkTime: true, }); } static updateState(updater) { const newState = {...this.getState(), ...updater}; GM_setValue(this.STORAGE_KEY, newState); return newState; } static resetState(){ GM_deleteValue(this.STORAGE_KEY); } } class HeroesPageManager { constructor(){ this.heroRows = null; this.nextDungeonDisabledHeroRows = null; this.nextDungeonAvailableHeroRows = null; this.nextDungeonAvailableHeroDetails = null; this.firstCompletedDungeonTime = null; this.firstCompletedheroDetails = null; this.submitBtn = document.querySelector('input[type="submit"][name="ok"]'); this.reduceBtn = document.querySelector('input[name="reduce_dungeon_time"]'); this.init(); } init() { if (!this.inHeroListPageContent()) return; if (this.handleReduceBtn()) return; this.processHeroList(); this.monitor(); } inHeroListPageContent(){ if(document.querySelector('input[name=uv_start]')){ return true; } return false; } handleReduceBtn() { if(this.reduceBtn?.style.display == '') { this.reduceBtn.addEventListener('click', () => { setTimeout(() => {window.location.reload()}, 1000*3); // TODO: 监控AJAX请求,成功请求后刷新页面 }); this.reduceBtn.click(); return true; } return false; } calculateTimeRemaining(){ return getOffsetCountdown(this.firstCompletedDungeonTime); } processHeroList() { this.heroRows = Array.from( document.querySelectorAll('table.content_table > tbody > tr:not(.header)') ); this.nextDungeonDisabledHeroRows = Array.from( document.querySelectorAll('table.content_table > tbody > tr:not(.header):not(:has(td:nth-child(5) img))') ); this.nextDungeonAvailableHeroRows = Array.from( document.querySelectorAll('table.content_table > tbody > tr:not(.header):has(td:nth-child(5) img)') ); this.nextDungeonAvailableHeroDetails = this.nextDungeonAvailableHeroRows.map(row => ({ dom: row, time: parseTime(row.lastElementChild.textContent), owned: row.querySelector('input[type="submit"]') ? false : true })); this.firstCompletedDungeonTime = Math.min( ...this.nextDungeonAvailableHeroDetails.map(h => h.time) ); this.firstCompletedheroDetails = this.nextDungeonAvailableHeroDetails.filter( h => h.time == this.firstCompletedDungeonTime ); console.log('processHeroList: ',{ heroRows: this.heroRows, nextDungeonDisabledHeroRows: this.nextDungeonDisabledHeroRows, nextDungeonAvailableHeroRows: this.nextDungeonAvailableHeroRows, nextDungeonAvailableHeroDetails: this.nextDungeonAvailableHeroDetails, firstCompletedDungeonTime: this.firstCompletedDungeonTime, firstCompletedheroDetails: this.firstCompletedheroDetails, }); } sendReminder(){ this.nextDungeonDisabledHeroRows.forEach(row => { const newTd = document.createElement('td'); newTd.className = 'warning'; newTd.textContent = '未选择地城'; row.appendChild(newTd); }); } activeHeroes(){ // deselect all checkbox this.heroRows.forEach(tr => { const checkbox = tr.querySelector('input[type="checkbox"]'); if(checkbox && checkbox.checked){ checkbox.checked = false; console.log('deselect: ', checkbox); } }); let lastOwnedHero = null; let lastOwnedHeroIndex = -1; for (let i = this.firstCompletedheroDetails.length - 1; i >= 0; i--) { if (this.firstCompletedheroDetails[i].owned) { lastOwnedHero = this.firstCompletedheroDetails[i]; lastOwnedHeroIndex = i; break; } } console.log({ lastOwnedHero: lastOwnedHero, lastOwnedHeroIndex: lastOwnedHeroIndex, }); let lastUvHero = null; let lastUvHeroIndex = -1; for (let i = this.firstCompletedheroDetails.length - 1; i >= 0; i--) { if (!this.firstCompletedheroDetails[i].owned) { lastUvHero = this.firstCompletedheroDetails[i]; lastUvHeroIndex = i; break; } } console.log({ lastUvHero: lastUvHero, lastUvHeroIndex: lastUvHeroIndex, }); this.firstCompletedheroDetails.forEach((row, index) => { const checkbox = row.dom.querySelector('input[type="checkbox"]'); const radio = row.dom.querySelector('input[type="radio"][name="FIGUR"]'); if(lastOwnedHero){ if(row.owned && checkbox) { checkbox.checked = true; console.log('seleted: ', checkbox); let checkboxNotActivated = false; if(row.dom.querySelector('.hero_inactive')){ checkboxNotActivated = true; } if(lastOwnedHeroIndex == index && !radio.checked || checkboxNotActivated){ radio.checked = true; this.submitBtn.click(); } } }else{ if(lastUvHeroIndex == index && !radio.checked){ radio.checked = true; this.submitBtn.click(); } } }); } storeLoot(){ let currentIndex = State.getState().currentHeroIndex; if (currentIndex >= this.heroRows.length) { console.log('所有英雄处理完毕'); State.updateState({ lastStoredDate: new Date().getDate(), currentHeroIndex: 0, }); window.location.reload(); console.log(State.getState()); return; } const radio = this.heroRows[currentIndex].querySelector('input[type=radio]'); if(radio && !radio.checked){ radio.checked = true; this.submitBtn.click(); return; } if(currentIndex > 0){ this.heroRows.slice(0, currentIndex).forEach(tr => { let newTd = document.createElement('td'); newTd.textContent = '入库完成'; tr.appendChild(newTd); }); } let newTd = document.createElement('td'); newTd.textContent = '入库中'; this.heroRows[currentIndex].appendChild(newTd); GM_xmlhttpRequest({ method: 'GET', url: '/wod/spiel/hero/items.php', onload: (response) => { if (response.status >= 200 && response.status < 300) { const buildTextContent = (res) => { // TODO: 正则获取关键字判断手上的战利品是不是满了 }; State.updateState({currentHeroIndex: ++currentIndex}); newTd.textContent = '入库完成'; this.storeLoot(); } else { console.error(`请求失败,状态码: ${response.status}`); } }, onerror: (error) => { console.error('请求发生错误:', error); } }); } monitor() { // 战利品入库 const didntStoredToday = () => (new Date().getDate() === State.getState().lastStoredDate); const isEnoughTime = () => (1000*60*this.heroRows.length > this.calculateTimeRemaining()); if(!didntStoredToday() && isEnoughTime()){ this.storeLoot(); return; } // 地城倒计时 this.sendReminder(); this.activeHeroes(); this.startCountdonw(); } startCountdonw(){ this.firstCompletedheroDetails = this.firstCompletedheroDetails.map(row => ({ ...row, display: document.createElement('td') })) this.firstCompletedheroDetails.forEach(row => { row.dom.appendChild(row.display) }); let timeoutId = null; const checkTimeout = () => { const countdown = this.calculateTimeRemaining(); if(countdown > 0){ this.firstCompletedheroDetails.forEach(row => { row.display.innerHTML = '⏱️'+ formatTime(countdown); }); timeoutId = setTimeout(checkTimeout, 1000); }else{ clearTimeout(timeoutId); window.location.reload(); } }; checkTimeout(); } } class VotePageManager{ constructor(){ this.currentVote = null; this.init(); } init(){ this.processVoteList(); this.refreshAtMidnight(); this.monitor(); } extractJsUrls(a){ const onclickAttr = a?.getAttribute('onclick'); if(!onclickAttr) return null; const match = onclickAttr.match(/js_goto_url\('([^']+)'/); return match ? match[1] : null; } processVoteList(){ const imgList = Array.from(document.querySelectorAll('div.vote.reward img[title=荣誉]')) .map(row =>({ dom: row, url: this.extractJsUrls(row.closest('div.vote.reward').previousElementSibling.querySelector('a')), time: parseTime(row.parentElement.textContent) })); const minItem = imgList.reduce((min, current) => { if (!min || current.time < min.time) return current; return min; }, null); this.currentVote = minItem; } refreshAtMidnight(){ const midnight = new Date(); midnight.setHours(24, 0, 0, 0); const checkTimeout = () => { const remainingtime = getOffsetCountdown(midnight.getTime(), 0); remainingtime > 0 ? setTimeout(checkTimeout, 1000) : window.location.reload(); } checkTimeout(); } monitor(){ const newSpan = document.createElement('span'); this.currentVote.dom.parentElement.appendChild(newSpan); const checkTimeout = () => { const remainingtime = getOffsetCountdown(this.currentVote.time); if (remainingtime > 0) { setTimeout(checkTimeout, 1000); newSpan.innerHTML = ' ⏱️' + formatTime(remainingtime); }else{ window.location = this.currentVote.url; } } checkTimeout(); } } class WOD{ static AFK(){ const path = window.location.pathname; const match = path.match(/\/([^\/]+?)\.php$/); const pageName = match ? match[1] : ''; if (typeof pageName !== 'string' || pageName.length === 0) throw new Error('not support current page name'); const classMap = { HeroesPageManager, VotePageManager }; const className = pageName[0].toUpperCase() + pageName.slice(1) + 'PageManager'; const DynamicClass = classMap[className]; console.log('Route: ',{ currentPathName: path, currentPageName: pageName, className: className, DynamicClass: DynamicClass }); new DynamicClass(); } } WOD.AFK(); })();