// ==UserScript== // @name Move Training Pets To Top // @namespace https://hollymcfarland.com // @version 1.0 // @description At the three battledome training areas in Neopia, move pets currently in a course or waiting for input to the top of the list // @author monorail // @match https://www.neopets.com/pirates/academy.phtml?type=status // @match https://www.neopets.com/island/training.phtml?type=status // @match https://www.neopets.com/island/fight_training.phtml?type=status // @icon https://www.google.com/s2/favicons?sz=64&domain=neopets.com // @grant none // ==/UserScript== /* Note that this has not been tested in the Secret Ninja Training School, simply because I don't have any pets even close to level 250. But the DOM looks the same at the Swashbuckling Academy and the Mystery Island Training School so, I assume it'll work there too? If someone let me know I'd appreciate it haha */ (function() { 'use strict'; function getPairs(iter) { /* Given an iterable (something that can be converted to an array, anyway), return an array of two-length arrays containing each element once, in the same order e.g. getPairs([1, 2, 3, 4, 5, 6]) => [[1, 2], [3, 4], [5, 6]] */ // The function getting mapped here behaves differently depending on the parity // of the index. // Odd index => Empty array // Even index => Array of array(!!) of the element at that index and the one after it // Then because it's getting _flat_mapped, the whole thing is flattened by one level. // Empty arrays disappear and the arrays of arrays become just two-length arrays // https://stackoverflow.com/a/57851980/2114129 return [...iter].flatMap((_, i, a) => i % 2 ? [] : [a.slice(i, i + 2)]); } // Table that shows each pet const statusTable = document.querySelector(".content > p:nth-child(6) > table:nth-child(1) > tbody:nth-child(1)"); for (let [header, pet] of getPairs(statusTable.children)) { // Each "pet" here refers to a element representing that pet's status. // Every pet has at exactly two elements as children. The first displays // their image and stats and the second shows any applicable status (i.e. the // pet is waiting for payment, currently training, or waiting for "course // complete" confirmation). This second element is empty for pets with no status // at all. // Any pet that has any kind of status at all should be moved to the top, and // ideally we'd also like to otherwise preserve the order. To accomplish this, // what we're actually going to do is take every pet that _doesn't_ have a // status (i.e. the second child is empty) and move it to the bottom. We // do this on a copy of the list of children so we don't hit any pet more than // once or skip any, and it's all done in the correct order. // To move an element to the bottom, we just have to append it as a child to // the element it's already a child of. Because Neopets uses tables for layout // in current year, the header above each pet is actually a completely separate // element, so those are handled at the same time. if (pet.lastChild.childElementCount === 0) { statusTable.appendChild(header); statusTable.appendChild(pet); } } })();