您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
1.自动激活最先结束地城的英雄;2.自动加速地城;3.每日访问一次仓库存放战利品
当前为
// ==UserScript== // @name WOD AFK Helper // @version 1.0.4 // @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* // @match *://*.world-of-dungeons.org/wod/spiel/hero/items.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 StateManager { static STORAGE_KEY = 'WOD_HELPER_STATE'; static DEFAULT_STATE = { _version: '1.0.4', lastStoredDate: 0, currentHeroIndex: 0, reportCheckTimeout: 0, carryingMaxLootHeroes: [] }; static get state() { const storedData = GM_getValue(this.STORAGE_KEY, {}); const mergedState = { ...this.DEFAULT_STATE, ...storedData }; if (mergedState._version !== this.DEFAULT_STATE._version) { this.delete(); return this.DEFAULT_STATE; } return mergedState; } static update(value) { const newState = { ...this.state, ...value }; GM_setValue(this.STORAGE_KEY, newState); } static delete() { GM_deleteValue(this.STORAGE_KEY); } } class HeroesPageManager { constructor() { this.handstuffedText = ''; this.heroRows = null; this.nextDungeonDisabledHeroRows = null; this.nextDungeonAvailableHeroRows = null; this.nextDungeonAvailableHeroDetails = null; this.firstCompletedDungeonTime = null; this.firstCompletedheroDetails = null; this.nextDungeonReducibleHeroRows = null; this.submitBtn = document.querySelector('input[type="submit"][name="ok"]'); this.reduceBtn = document.querySelector('input[name="reduce_dungeon_time"]'); this.storingText = '入库中'; this.emptyHandsText = '入库完成'; this.carryingMaxLootText = '手里拿满了'; this.unselectedText = '未选择地城'; this.init(); } init() { if (!this.inHeroListPageContent()) return; this.processHeroList(); if (this.storeLoot()) return; if (this.handleReduceBtn()) return; this.startDungeonsCountDown(); } inHeroListPageContent() { if (document.querySelector('input[name=uv_start]')) { return true; } return false; } handleReduceBtn() { if (this.reduceBtn?.style.display == '') { this.reduceBtn.addEventListener('click', () => { // TODO: 监控AJAX,成功请求后刷新页面 setTimeout(() => { window.location.reload() }, 1000 * 3); }); this.reduceBtn.click(); return true; } return false; } 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, }); } calculateTimeRemaining() { return getOffsetCountdown(this.firstCompletedDungeonTime); } storeLoot() { const didntStoredToday = () => { return new Date().getDate() === StateManager.state.lastStoredDate }; const isEnoughTime = () => { return 1000 * 60 * this.heroRows.length > this.calculateTimeRemaining(); }; if (!didntStoredToday() && isEnoughTime()) { this.storeLootProcess(); return true; } return false; } storeLootProcess() { let currentIndex = StateManager.state.currentHeroIndex; console.log({ currentIndex: currentIndex }); if (currentIndex >= this.heroRows.length) { console.log('所有英雄处理完毕'); StateManager.update({ lastStoredDate: new Date().getDate(), currentHeroIndex: 0, }); window.location.reload(); return; } if (this.reduceBtn?.style.display == '') { this.reduceBtn.click(); } 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, index) => { const newTd = document.createElement('td'); console.log({ carryingMaxLootHeroes: StateManager.state.carryingMaxLootHeroes, index: index, }); if (StateManager.state.carryingMaxLootHeroes.some(obj => obj.heroTableIndex === index)) { newTd.textContent = this.carryingMaxLootText; } else { newTd.textContent = this.emptyHandsText; } tr.appendChild(newTd); }); } const newTd = document.createElement('td'); newTd.textContent = this.storingText; this.heroRows[currentIndex].appendChild(newTd); GM_xmlhttpRequest({ method: 'GET', url: '/wod/spiel/hero/items.php', onload: (response) => { if (response.status >= 200 && response.status < 300) { if (response.responseText.includes(WOD.carryingMaxLootText)) { const aTag = this.heroRows[currentIndex].querySelector('td:first-child a'); const sessionHeroId = new URL(aTag?.href, window.location.href) .searchParams .get('session_hero_id'); let carryingMaxLootHeroes = StateManager.state.carryingMaxLootHeroes; carryingMaxLootHeroes.push({ sessionHeroId: sessionHeroId, heroTableIndex: currentIndex, }); StateManager.update({ carryingMaxLootHeroes: carryingMaxLootHeroes }); newTd.textContent = this.carryingMaxLootText; } else { newTd.textContent = this.emptyHandsText; } StateManager.update({ currentHeroIndex: ++currentIndex }); this.storeLootProcess(); } else { console.error(`请求失败,状态码: ${response.status}`); } }, onerror: (error) => { console.error('请求发生错误:', error); } }); } startDungeonsCountDown() { if (!this.activeHeroes()) return; this.sendReminder(); this.firstCompletedheroDetails = this.firstCompletedheroDetails.map(row => ({ ...row, newTd: document.createElement('td') })) this.firstCompletedheroDetails.forEach(row => { row.dom.appendChild(row.newTd) }); let timeoutId = null; const checkTimeout = () => { const countdown = this.calculateTimeRemaining(); if (countdown > 0) { this.firstCompletedheroDetails.forEach(row => { row.newTd.textContent = '⏱️' + formatTime(countdown); }); timeoutId = setTimeout(checkTimeout, 1000); } else { clearTimeout(timeoutId); const newTimeout = StateManager.state.reportCheckTimeout + 5000; StateManager.update({ reportCheckTimeout: newTimeout }); console.log({ newTimeout: newTimeout }); this.firstCompletedheroDetails.forEach(row => { row.newTd.textContent = `⏱️等待生成战报,${Math.floor(newTimeout/1000)}秒后重试`; }); setTimeout(() => { window.location.reload() }, newTimeout); } }; checkTimeout(); } 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; StateManager.update({ reportCheckTimeout: 0 }); this.submitBtn.click(); return false; } } } else if (lastUvHeroIndex == index && !radio.checked) { radio.checked = true; StateManager.update({ reportCheckTimeout: 0 }); this.submitBtn.click(); return false; } }); return true; } sendReminder() { this.nextDungeonDisabledHeroRows.forEach(row => { const newTd = document.createElement('td'); newTd.className = 'warning'; newTd.textContent = this.unselectedText; row.appendChild(newTd); }); StateManager.state.carryingMaxLootHeroes.forEach(item => { const row = this.heroRows[item.heroTableIndex]; const newTd = document.createElement('td'); newTd.className = 'warning'; newTd.textContent = this.carryingMaxLootText; row.appendChild(newTd); }); } } 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 ItemsPageManager { constructor() { this.init(); } init() { if (!this.hasText(WOD.carryingMaxLootText)) return; console.log('carryingMaxLoot'); const submitBtn = document.querySelectorAll('input[type=submit][name=ok]'); const markAsEmptyHands = () => { const input = document.querySelector('input[type=hidden][name=session_hero_id]'); const seesionHeroId = input.value; const carryingMaxLootHeroes = StateManager.state.carryingMaxLootHeroes; const newCarryingMaxLootHeroes = carryingMaxLootHeroes.filter(item => item.sessionHeroId != seesionHeroId); StateManager.update({ carryingMaxLootHeroes: newCarryingMaxLootHeroes, }); }; submitBtn.forEach(btn => { btn.addEventListener('click', markAsEmptyHands); }); } hasText(text) { return document.body.textContent.includes(text); } } class WOD { static carryingMaxLootText = '满了'; // TODO: 替换正确文本 static get pageName() { 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'); return pageName; } static get classMap() { return { HeroesPageManager, VotePageManager, ItemsPageManager, }; } static AFK() { const className = this.pageName[0].toUpperCase() + this.pageName.slice(1) + 'PageManager'; const DynamicClass = this.classMap[className]; console.log('Route: ', { currentPageName: this.pageName, className: className, DynamicClass: DynamicClass }); new DynamicClass(); } } WOD.AFK(); })();