// ==UserScript== // @name Neopets TVW Auto Volunteer // @namespace GreaseMonkey // @author @willnjohnson // @license MIT // @version 1.0.0 // @description Adds an Auto-Join Shifts button that enrolls all available pets, latest Acts first. // @match *://*.neopets.com/hospital/volunteer* // @run-at document-end // @grant none // @downloadURL https://update.greasyfork.icu/scripts/576649/Neopets%20TVW%20Auto%20Volunteer.user.js // @updateURL https://update.greasyfork.icu/scripts/576649/Neopets%20TVW%20Auto%20Volunteer.meta.js // ==/UserScript== (function () { 'use strict'; const wait = (ms) => new Promise(r => setTimeout(r, ms)); const rand = (min, max) => wait(Math.floor(Math.random() * (max - min + 1)) + min); const run = async (btn) => { btn.disabled = true; btn.textContent = 'Joining…'; // Close any popup that might already be open document.querySelectorAll('.togglePopup__2020.movePopup__2020').forEach(p => { if (p.style.display !== 'none') p.querySelector('.popup-exit')?.click(); }); const shade = document.getElementById('navpopupshade__2020'); if (shade) shade.style.display = 'none'; await rand(400, 600); // Collect rewards for any completed shifts for (const rb of [...document.querySelectorAll('button[onclick="completeShift(this)"]')]) { rb.click(); await rand(1000, 1500); document.querySelector('#VolunteerFinishPopup .popup-exit')?.click(); await rand(500, 800); } const used = new Set(); const processed = new Set(); while (true) { // Re-query each loop pass so we see DOM updates after each enrollment const nextBtn = [...document.querySelectorAll('button[onclick^="selectFight"]')] .reverse() .find(b => { const m = b.getAttribute('onclick').match(/\((\d+)\)/); return m && !processed.has(m[1]); }); if (!nextBtn) break; const fightId = nextBtn.getAttribute('onclick').match(/\((\d+)\)/)[1]; processed.add(fightId); // 1. Click "Join Shift" → triggers selectFight(N), shows "Ready for Volunteering?" popup nextBtn.click(); await rand(700, 1000); // Wait for popup to appear let n = 0; const joinPopup = document.getElementById('VolunteerJoinPopup'); while (n++ < 15 && (!joinPopup || joinPopup.style.display === 'none')) await wait(200); if (!joinPopup || joinPopup.style.display === 'none') continue; // 2. Click "I'm Ready" → calls showPets(), fetches and shows pet list joinPopup.querySelector('button[onclick="showPets()"]')?.click(); await rand(1200, 1800); // Wait for pet list to load (VolunteerPetLoading gets hide class when done) n = 0; while (n++ < 30) { const loading = document.getElementById('VolunteerPetLoading'); if ((!loading || loading.classList.contains('hide')) && document.querySelectorAll('#VolunteerPetList .vc-pet').length > 0) break; await wait(200); } // 3. Find first available pet (not ineligible, not already used this run) const petEl = [...document.querySelectorAll('#VolunteerPetList .vc-pet')] .find(p => !p.classList.contains('vc-ineligible') && !used.has((p.dataset.petname || '').toLowerCase())); if (!petEl) { // No free pet — go back to fight list and skip document.querySelector('#VolunteerSelectPet button[onclick="showFights()"]')?.click(); await rand(600, 1000); continue; } // 4. Click the pet to select it petEl.click(); await rand(500, 800); // Wait for "Join Volunteer Team" button to become enabled const joinBtn = document.getElementById('VolunteerJoinButton'); n = 0; while (n++ < 10 && joinBtn?.disabled) await wait(200); if (!joinBtn || joinBtn.disabled) { document.querySelector('#VolunteerSelectPet button[onclick="showFights()"]')?.click(); await rand(600, 1000); continue; } // 5. Click "Join Volunteer Team" → calls startShift(this) const petName = (joinBtn.dataset.pet || petEl.dataset.petname || '').toLowerCase(); joinBtn.click(); await rand(1500, 2500); if (petName) used.add(petName); // Close "Thank you for Volunteering!" popup if it appears const donePopup = document.getElementById('VolunteerJoinedPopup'); if (donePopup && donePopup.style.display !== 'none') { donePopup.querySelector('.popup-exit')?.click(); await rand(500, 800); } // Wait for fight list to reappear before processing the next fight n = 0; while (n++ < 25 && document.getElementById('VolunteerFightInfo')?.classList.contains('hide')) { await wait(300); } } btn.textContent = 'Done! Reloading…'; await wait(1000); window.location.reload(); }; const inject = () => { if (document.getElementById('auto-join-btn')) return true; const anchor = document.querySelector('.volunteer-centre'); if (!anchor) return false; const btn = document.createElement('button'); btn.id = 'auto-join-btn'; btn.tabIndex = 0; btn.className = 'button-default__2020 button-yellow__2020 btn-single__2020 plot-button vc-button'; btn.style.cssText = 'display:block; margin:10px auto;'; btn.textContent = 'Auto-Join Shifts'; btn.onclick = () => run(btn); anchor.insertAdjacentElement('beforebegin', btn); return true; }; const init = () => { if (inject()) return; let n = 0; const t = setInterval(() => { if (inject() || ++n >= 50) clearInterval(t); }, 200); }; if (document.readyState === 'complete' || document.readyState === 'interactive') init(); document.addEventListener('DOMContentLoaded', init); window.addEventListener('load', init); })();