├── Neopets - Active Pet Switch.user.js
├── Neopets - Battledome Set Selector.user.js
├── Neopets - Better Faster Godori.user.js
├── Neopets - Daily Puzzle Solver.user.js
├── Neopets - Direct Link Display.user.js
├── Neopets - Home Page Redirect.user.js
├── Neopets - Inventory+.user.js
├── Neopets - LoD Shame Tracker.user.js
├── Neopets - Mystery Pic Helper.user.js
├── Neopets - Negg Cave Solver.user.js
├── Neopets - NeoCola Enhancements.user.js
├── Neopets - NeoFoodClub+.user.js
├── Neopets - Quick Exploration Links.user.js
├── Neopets - Quick Inventory Deposit.user.js
├── Neopets - SDB Inventarium.user.js
├── Neopets - Trans Affirming Neopets (Compat).user.js
├── Neopets - Trans Affirming Neopets.user.js
├── README.md
└── mettyneo-icon.png
/Neopets - Active Pet Switch.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Neopets - Active Pet Switch & Fishing Vortex Plus
3 | // @version 2.8.1
4 | // @description APS adds a button that lets you easily switch your active pet. In the classic theme, this will appear underneath your pet's image in the sidebar. In beta, it will appear next to the icons that float at the top of every page (Bookmarks, Favorites, Shop Wizard). FVP adds additional info to the fishing vortex.
5 | // @author Metamagic, with modifications by chanakin
6 | // @match *://*.neopets.com/*
7 | // @icon https://i.imgur.com/RnuqLRm.png
8 | // @grant GM_getValue
9 | // @grant GM_setValue
10 | // @grant GM_deleteValue
11 | // @grant GM_addValueChangeListener
12 | // @grant window.focus
13 | // @grant window.close
14 | // @downloadURL https://github.com/Mettymagic/np-userscripts/raw/main/Neopets%20-%20Active%20Pet%20Switch.user.js
15 | // @updateURL https://github.com/Mettymagic/np-userscripts/raw/main/Neopets%20-%20Active%20Pet%20Switch.user.js
16 | // ==/UserScript==
17 |
18 | // You are free to modify this script for personal use but modified scripts must not be shared publicly without permission.
19 | // Feel free to contact me at @mettymagic on discord for any questions or inquiries. ^^
20 |
21 | // Trans rights are human rights ^^
22 | // metty says hi
23 |
24 | //===========
25 | // APS config
26 | //===========
27 | //collected page data times out after this many hours. default: -1 (aka never)
28 | const HOME_DATA_TIMEOUT = -1
29 |
30 | //===========
31 | // FVP config
32 | //===========
33 | //changes the display mode
34 | // -1 = disable display entirely. still tracks enabled info.
35 | // 0 = display table on fishing page w/o fishing info
36 | // 1 = display table on fishing page, plus fishing info (default)
37 | // 2 = display fishing info on all pages' pet table
38 | const FISHING_DISPLAY_MODE = 1
39 | //tracks and displays pet fishing levels
40 | const FISHING_LEVEL_TRACK = true
41 | //tracks time since last fishing reward
42 | const FISHING_TIME_TRACK = true
43 |
44 | //tracks fishing xp gained and displays level up chance based on /u/neo_truths' post
45 | //https://old.reddit.com/r/neopets/comments/xqylkt/ye_olde_fishing_vortex/
46 | const FISHING_XP_TRACK = true
47 |
48 | //for my mom who keeps accidentally clicking it then getting confused
49 | const REMOVE_CAST_BUTTON = true
50 |
51 | //==============
52 | // main function
53 | //==============
54 |
55 | const url = document.location.href
56 | //check for the top left pet icon to know we're in beta
57 | let isBeta = false
58 | if ($("[class^='nav-pet-menu-icon']").length) isBeta = true
59 |
60 | let timeoutId = null
61 |
62 | if (url.includes("neopets.com/home")) {
63 | getPetData(document) //always update the data while we're here
64 | //close if flagged to
65 | let timeout = GM_getValue("stackpath-timeout")
66 | if (timeout) {
67 | GM_deleteValue("stackpath-timeout")
68 | // won't close tab after 15 second timeout
69 | if (new Date().valueOf() - timeout < 1000 * 15) {
70 | window.close()
71 | }
72 | }
73 | }
74 |
75 | if (url.includes("water/fishing.phtml")) {
76 | handleFishingVortex()
77 | }
78 |
79 | createMenuButton() //adds the button to open the menu
80 | listenForPetUpdates() //adds a listener for updates to the script's stored pet list, which then updates the menu
81 |
82 | //=========
83 | // overhead
84 | //=========
85 | function clearPetList() {
86 | GM_setValue("petlist", {})
87 | }
88 |
89 | function updatePetList() {
90 | clearPetList()
91 | $.get("https://www.neopets.com/home/", function (data) {
92 | let doc = new DOMParser().parseFromString(data, "text/html")
93 | //sometimes stackpath blocks the page, in which case open a newtab and close it after
94 | if (doc.title !== "Welcome to Neopets!") {
95 | console.log("[APS] Home page request blocked by stackpath, opening tab manually.")
96 | GM_setValue("stackpath-timeout", new Date().valueOf())
97 | window.open("https://www.neopets.com/home/")
98 | window.focus()
99 | }
100 | getPetData(doc)
101 | console.log("[APS] Data successfully retrieved.")
102 | })
103 | }
104 |
105 | function timeoutForUpdateExceeded() {
106 | return new Date().valueOf() - GM_getValue("lastupdate", 0) > 1000 * 60 * 60 * HOME_DATA_TIMEOUT && HOME_DATA_TIMEOUT > 0
107 | }
108 |
109 | function usernameHasChanged() {
110 | return getUsername() !== GM_getValue("un")
111 | }
112 |
113 | function petListExists() {
114 | return GM_getValue("petlist", false)
115 | }
116 |
117 | function shouldUpdate() {
118 | //revalidates data if it times out or if username changes
119 | if (timeoutForUpdateExceeded()) {
120 | console.log("[APS] Updating data for new day.")
121 | return true
122 | }
123 |
124 | if (usernameHasChanged()) {
125 | console.log("[APS] Updating data for new user.")
126 | return true
127 | }
128 |
129 | if (!petListExists()) {
130 | console.log("[APS] Getting user pet data for first time.")
131 | return true
132 | }
133 |
134 | return false
135 | }
136 |
137 | function getPetData(doc) {
138 | GM_setValue("petlist", getPets(doc))
139 | GM_setValue("lastupdate", new Date().valueOf())
140 | GM_setValue("un", getUsername())
141 |
142 | console.log("[APS] Pet data updated.")
143 | }
144 |
145 | function emptyJson(newValue) {
146 | return JSON.stringify(newValue) === JSON.stringify({});
147 | }
148 |
149 | function setRefreshButtonVisible(visible) {
150 | Array.from($(".activetable > a")).forEach(link => {
151 | if (visible) {
152 | link.style.display = "none"
153 | } else {
154 | link.style.display = "block"
155 | }
156 | })
157 | }
158 |
159 | function listenForPetUpdates() {
160 | GM_addValueChangeListener("petlist", function (key, oldValue, newValue, _) {
161 | //if pet list is now empty, adds loading msg
162 | if (emptyJson(newValue)) {
163 | console.log("[APS] Cleared pet tables.")
164 | addLoadDivs()
165 | setRefreshButtonVisible(false)
166 | return
167 | }
168 |
169 | console.log("[APS] Updated pet tables.")
170 | console.log(newValue)
171 | clearTimeout(timeoutId)
172 | timeoutId = null
173 | populateTables()
174 | setRefreshButtonVisible(true)
175 | })
176 | }
177 |
178 | //=================
179 | // element creation
180 | //=================
181 |
182 | function createMenuButton() {
183 | let parentElement = getButtonLocation()
184 |
185 | if (parentElement) {
186 | let changeActivePetShortcut
187 |
188 | if (isBeta) {
189 | changeActivePetShortcut = createShortcutIcon("#", "https://images.neopets.com/themes/h5/constellations/images/mypets-icon.svg")
190 | } else {
191 | changeActivePetShortcut = document.createElement("a")
192 | changeActivePetShortcut.href = "#"
193 | changeActivePetShortcut.innerHTML = "Change Active Pet"
194 | }
195 |
196 | addCSS()
197 |
198 | changeActivePetShortcut.classList.add("openmenubutton")
199 | changeActivePetShortcut.addEventListener("click", () => {
200 | openMenu()
201 | })
202 |
203 | parentElement.appendChild(changeActivePetShortcut);
204 | createMenu()
205 | }
206 | }
207 |
208 | function createMenu() {
209 | let menu = document.createElement("div")
210 | menu.id = "select-active-pet-menu"
211 | menu.style.display = "none" //starts not visible
212 |
213 | menu.appendChild(getTableHeader(`Select an Active Pet:
(Click anywhere else to exit)`))
214 |
215 | let table = document.createElement("table")
216 | table.classList.add("activetable")
217 | menu.appendChild(table)
218 | document.body.appendChild(menu)
219 |
220 | // revalidates if it needs to
221 | if (shouldUpdate()) {
222 | updatePetList()
223 | return
224 | }
225 |
226 | populateTables()
227 | }
228 |
229 | function populateTables() {
230 | let petList = Object.values(GM_getValue("petlist", {}))
231 | if (petList.length < 1) {
232 | addLoadDivs()
233 | updatePetList()
234 | return
235 | }
236 |
237 | Array.from($(".activetable")).forEach(table => {
238 | table.innerHTML = ""
239 |
240 | let activePet = getActivePet()
241 |
242 | for (let i = 0; i < 4; i++) { //4 rows
243 | let row = table.insertRow()
244 | for (let j = 0; j < 5; j++) { //5 per row
245 | let index = i * 5 + j
246 | if (index < petList.length) {
247 | let img = petList[index].img.withBG //300x300
248 | let name = petList[index].name
249 |
250 | let cell = row.insertCell()
251 | cell.setAttribute("name", name)
252 | //name = "test"
253 | let d1 = document.createElement("div"), d2 = document.createElement("div")
254 | d1.innerHTML = ``
255 | d1.style.width = "150px !important";
256 | d1.style.height = "150px !important";
257 | d2.innerHTML = name
258 | cell.appendChild(d1)
259 | cell.appendChild(d2)
260 |
261 | if (activePet === name) {
262 | cell.setAttribute("active", "")
263 | } else {
264 | cell.addEventListener("click", (event) => {
265 | event.stopPropagation();
266 | changeActivePet(name);
267 | })
268 | cell.style.cursor = "pointer"
269 | }
270 |
271 | if (url.includes("water/fishing.phtml") || FISHING_DISPLAY_MODE === 2) {
272 | addFishingPetDisplay(cell, name)
273 | }
274 | }
275 | }
276 | }
277 |
278 | let refresh = document.createElement("a")
279 | refresh.innerHTML = "(refresh pet list)"
280 | refresh.addEventListener("click", (event) => {
281 | event.stopPropagation()
282 | updatePetList() //updates pet list
283 | })
284 | table.appendChild(refresh)
285 | console.log(`[APS] Table populated with ${petList.length} pets.`)
286 | })
287 | }
288 |
289 | function addLoadDivs() {
290 | let loadMsg = '
(?)hr
519 | ` 520 | tdiv.style.background = `#c4c4c4` 521 | } else { 522 | let t = getTimeSinceFished(data.lasttime) 523 | tdiv.innerHTML = ` 524 | 525 |${t.toFixed(1)}hr
526 | ` 527 | tdiv.style.background = `conic-gradient(${getCircleColor(t / 26.0)} ${t / 26.0 * 360}deg, #c4c4c4 0deg)` 528 | } 529 | cell.appendChild(tdiv) 530 | } 531 | 532 | function addFishingPetDisplay(cell, name) { 533 | let data = GM_getValue("fishinglist", {})[name] || { 534 | lvl: null, 535 | xp: null, 536 | lasttime: null 537 | } 538 | 539 | addLevelDisplay(data, cell); 540 | addTimeDisplay(data, cell); 541 | } 542 | 543 | function getCircleColor(p) { 544 | if (p < 0.2) { 545 | return "#e87e72" 546 | } 547 | 548 | if (p < 0.4) { 549 | return "#e8b972" 550 | } 551 | 552 | if (p < 0.6) { 553 | return "#ebed77" 554 | } 555 | 556 | if (p < 0.8) { 557 | return "#c0ed77" 558 | } 559 | 560 | if (p < 1.0) { 561 | return "#77ed7d" 562 | } 563 | 564 | return "#88f2dd" 565 | } 566 | 567 | function getTimeSinceFished(tfish) { 568 | if (tfish == null) { 569 | return null 570 | } 571 | 572 | let t = new Date().valueOf() - tfish 573 | return Math.min(Math.floor((t / (1000.0 * 60.0 * 60.0)) * 10) / 10.0, 26.0) //# of hrs w/ 1 decimal place, rounded down 574 | } 575 | 576 | function getLevelUpChance(exp, lvl) { 577 | //if we dont have the data for either, our chance is unknown 578 | if (exp == null || lvl == null) { 579 | return null 580 | } 581 | 582 | //if (1-100) <= p, we win. (eg if p = 5, we have a 5% chance) 583 | let p = Math.floor(((exp + 100) - lvl - 20) / 1.8) 584 | return Math.max(p, 0.0).toFixed(1) //can't have negative percentage, rounds to 1 decimal 585 | } 586 | 587 | 588 | //======== 589 | // getters 590 | //======== 591 | function petsSortedAlphabetically(petInfo) { 592 | return Object.keys(petInfo).sort().reduce((obj, key) => { 593 | obj[key] = petInfo[key] 594 | return obj 595 | }, {}); 596 | } 597 | 598 | function getUsername() { 599 | if (isBeta) { 600 | return $("#navprofiledropdown__2020 > div:nth-child(3) a.text-muted")[0].innerHTML 601 | } 602 | 603 | return $("#header > table > tbody > tr:nth-child(1) > td.user.medText a[href]")[0].innerHTML 604 | } 605 | 606 | function getPets(doc) { 607 | let elements = Array.from(doc.querySelectorAll(".hp-carousel-pet")) 608 | let petNames = elements.map(e => e.getAttribute("data-name")) 609 | petNames = petNames.filter((n, index) => petNames.indexOf(n) === index) 610 | 611 | let petInfo = {} 612 | 613 | petNames.forEach(name => { 614 | let e = elements.find(e => e.getAttribute("data-name") === name) 615 | let img = {noBG: e.style.backgroundImage.replace('"', "").slice(5, -2), withBG: e.getAttribute("data-petimage")} 616 | let species = e.getAttribute("data-species") 617 | let color = e.getAttribute("data-color") 618 | let gender = e.getAttribute("data-gender") 619 | let hp = `${e.getAttribute("data-health")}/${e.getAttribute("data-maxhealth")}` 620 | let level = e.getAttribute("data-level") 621 | let hunger = e.getAttribute("data-hunger") 622 | let mood = e.getAttribute("data-mood") 623 | let p2 = getPetpet(e) 624 | petInfo[name] = { 625 | name: name, 626 | species: species, 627 | color: color, 628 | img: img, 629 | gender: gender, 630 | hp: hp, 631 | level: level, 632 | hunger: hunger, 633 | mood: mood, 634 | p2: p2 635 | } 636 | }) 637 | 638 | if (petInfo === {}) return null 639 | return petsSortedAlphabetically(petInfo) 640 | } 641 | 642 | function getPetpet(e) { 643 | let petpet = null 644 | if (e.hasAttribute("data-petpet")) { 645 | let p = e.getAttribute("data-petpet").split(" ") //no, name, the, fir 646 | let name = p.slice(0, p.lastIndexOf("the")).join(" ") 647 | let species = p.slice(p.lastIndexOf("the") + 1, p.length).join(" ") 648 | let img = e.getAttribute("data-petpetimg") 649 | let p3 = null 650 | if (e.hasAttribute("data-p3")) p3 = {name: e.getAttribute("data-p3"), img: e.getAttribute("data-p3img")} 651 | petpet = {name: name, species: species, img: img, p3: p3} 652 | } 653 | return petpet 654 | } 655 | 656 | function getActivePet() { 657 | if (isBeta) { 658 | return $("#navprofiledropdown__2020 > div:nth-child(4) > a")[0].innerHTML 659 | } 660 | 661 | return $("#content > table > tbody > tr > td.sidebar > div:nth-child(1) > table > tbody > tr:nth-child(1) > td > a > b")[0].innerHTML 662 | } 663 | 664 | 665 | function getButtonLocation() { 666 | if (isBeta) var e = $(".navsub-left__2020") 667 | else e = $(".activePet") 668 | if (e.length) return e[0] 669 | else return null 670 | } 671 | 672 | 673 | //======== 674 | // css 675 | //======== 676 | 677 | function addCSS() { 678 | var theme 679 | var button 680 | var text 681 | 682 | if (isBeta) { 683 | theme = $(".nav-profile-dropdown__2020").css("background-color") 684 | button = $(".nav-profile-dropdown-text").css("color") 685 | text = $(".nav-profile-dropdown-text a.text-muted").css("color") 686 | 687 | document.head.appendChild(document.createElement("style")).innerHTML = ` 688 | .openmenubutton { 689 | height: 25px; 690 | width: 30px; 691 | 692 | } 693 | ` 694 | } else { 695 | theme = $("#content > table > tbody > tr > td.sidebar > div:nth-child(1) > table > tbody > tr:nth-child(1) > td").css("background-color") 696 | button = "gray"; 697 | text = "black"; 698 | 699 | document.head.appendChild(document.createElement("style")).innerHTML = ` 700 | .openmenubutton { 701 | margin-top: 3px; 702 | border: 1px solid black; 703 | background-color: #D3D3D3; 704 | cursor: pointer; 705 | font-size: 8pt; 706 | padding: 1px 0px 1px; 707 | width: 148px; 708 | height: 100%; 709 | display: block; 710 | font-weight: normal; 711 | text-align: center; 712 | } 713 | .openmenubutton:hover { 714 | background-color: #C0C0C0; 715 | } 716 | .openmenubutton:active { 717 | background-color: #808080; 718 | } 719 | ` 720 | } 721 | 722 | document.head.appendChild(document.createElement("style")).innerHTML = ` 723 | .shortcut { 724 | margin-left: 8px; 725 | } 726 | .vertical { 727 | display: flex; 728 | flex-direction: column; 729 | margin: auto; 730 | width: fit-content; 731 | } 732 | #select-active-pet-menu { 733 | position: fixed; 734 | z-index: 100000; 735 | background-color: #8a8a8a; 736 | flex-direction: column; 737 | left: 50%; 738 | top: 50%; 739 | transform: translate(-50%, -50%); 740 | } 741 | #select-active-pet-menu::before { 742 | width: 100vw; 743 | height: 100vh; 744 | opacity: 0.7; 745 | background-color: black; 746 | z-index: -1; 747 | content: ""; 748 | position: fixed; 749 | transform: translate(-50%, -50%); 750 | left: 50%; 751 | top: 50%; 752 | } 753 | .activetableheader { 754 | text-align: center; 755 | font-size: 16pt; 756 | cursor: default; 757 | background-color: ${theme}; 758 | color: ${text}; 759 | padding-top: 8px; 760 | padding-bottom: 8px; 761 | -webkit-user-select: none; 762 | -ms-user-select: none; 763 | user-select: none; 764 | position: relative; 765 | display: block; 766 | min-width: 300px; 767 | } 768 | .activetable > a { 769 | cursor: pointer; 770 | display: block; 771 | position: absolute; 772 | left: 50%; 773 | transform: translateX(-50%); 774 | top: 0%; 775 | padding: 4px; 776 | color: blue; 777 | text-decoration: underline; 778 | font-size: 8pt; 779 | } 780 | .activetable { 781 | padding: 20px 20px; 782 | text-align: center; 783 | background-color: #FFFFFF; 784 | border-spacing: 5px; 785 | position: relative; 786 | } 787 | .activetable td { 788 | font-weight: bold; 789 | text-align: center; 790 | background-color: #e6e6e6; 791 | border-radius: 8px; 792 | padding-top: 10px; 793 | padding-bottom: 5px; 794 | position: relative; 795 | } 796 | .activetable td img { 797 | margin: 0; 798 | padding: 0; 799 | width: 150px !important; 800 | pointer-events: none; 801 | -webkit-user-select: none; 802 | -ms-user-select: none; 803 | user-select: none; 804 | } 805 | 806 | .activetable td:hover { 807 | background-color: #c9e3b3; 808 | } 809 | .activetable td:active { 810 | background-color: #a2cf7c; 811 | } 812 | .activetable td:hover img { 813 | opacity: 0.85; 814 | } 815 | 816 | .activetable td[active] { 817 | cursor: not-allowed; 818 | background-color: #8f8f8f; 819 | color: #4a4a4a; 820 | } 821 | .activetable td[active]::after { 822 | opacity: 0.6; 823 | font-size: 16pt; 824 | text-align: center; 825 | width: 100%; 826 | height: 100%; 827 | top: 0; 828 | left: 0; 829 | cursor: not-allowed; 830 | color: black; 831 | content: "(already active)"; 832 | position: absolute; 833 | display: flex; 834 | justify-content: center; 835 | align-items: center; 836 | transform: rotate(25deg) 837 | } 838 | .activetable td[active] img { 839 | opacity: 0.3; 840 | } 841 | 842 | .leveldisplay { 843 | border-radius: 50%; 844 | width: 40px; 845 | height: 40px; 846 | border: 3px solid #c4c4c4; 847 | font-size: 8pt; 848 | position: absolute; 849 | display: flex; 850 | justify-content: center; 851 | align-items: center; 852 | background-color: rgba(255,255,255,0.95); 853 | right: 55px; 854 | top: 118px; 855 | transition: 0.5s; 856 | box-sizing: border-box; 857 | } 858 | .leveldisplay:hover { 859 | background-color: #d9d9d9; 860 | } 861 | .leveldisplay > div { 862 | justify-content: center; 863 | align-items: center; 864 | display: flex; 865 | transition: 0.2s; 866 | visibility: hidden; 867 | position: absolute; 868 | width: 120px; 869 | height: 40px; 870 | opacity: 0; 871 | top: -4px; 872 | left: -4px; 873 | background-color: white; 874 | border-radius: 4px; 875 | z-index: 100000; 876 | } 877 | .leveldisplay:hover > div { 878 | transition-delay:0.5s; 879 | opacity: 1; 880 | visibility: visible; 881 | } 882 | .timedisplay { 883 | position: absolute; 884 | top: 118px; 885 | right: 13px; 886 | display: flex; 887 | align-items: center; 888 | justify-content: center; 889 | width: 32px; 890 | height: 32px; 891 | opacity: 0.95; 892 | } 893 | ` 894 | 895 | //radial progress bar taken from https://dev.to/shubhamtiwari909/circular-progress-bar-css-1bi9 896 | document.head.appendChild(document.createElement("style")).innerHTML = ` 897 | :root { 898 | --progress-bar-width: 40px; 899 | --progress-bar-height: 40px; 900 | } 901 | .circular-progress { 902 | width: var(--progress-bar-width); 903 | height: var(--progress-bar-height); 904 | border-radius: 50%; 905 | display: flex; 906 | justify-content: center; 907 | align-items: center; 908 | } 909 | .inner-circle { 910 | position: absolute; 911 | width: 34px; 912 | height: 34px; 913 | border-radius: 50%; 914 | background-color: rgba(255,255,255,0.95); 915 | } 916 | 917 | .percentage { 918 | position: relative; 919 | font-size: 8pt !important; 920 | color: black; 921 | } 922 | ` 923 | 924 | //dot spin animation taken from https://codepen.io/nzbin/pen/GGrXbp 925 | document.head.appendChild(document.createElement("style")).innerHTML = ` 926 | .dot-spin::before { 927 | position: absolute; 928 | width: 60px; 929 | height: 60px; 930 | left: 50%; 931 | top: 50%; 932 | background-color: white; 933 | opacity: 0.5; 934 | transform: translate(-50%, -50%) translateZ(-1px); 935 | border-radius: 50%; 936 | content: ""; 937 | display: block; 938 | z-index: -1; 939 | 940 | } 941 | .dot-spin { 942 | transform-style: preserve-3d; 943 | margin: auto; 944 | position: relative; 945 | width: 10px !important; 946 | height: 10px !important; 947 | border-radius: 5px; 948 | background-color: transparent; 949 | color: transparent; 950 | box-shadow: 0 -18px 0 0 #9880ff, 12.727926px -12.727926px 0 0 #9880ff, 18px 0 0 0 #9880ff, 12.727926px 12.727926px 0 0 rgba(152, 128, 255, 0), 0 18px 0 0 rgba(152, 128, 255, 0), -12.727926px 12.727926px 0 0 rgba(152, 128, 255, 0), -18px 0 0 0 rgba(152, 128, 255, 0), -12.727926px -12.727926px 0 0 rgba(152, 128, 255, 0); 951 | animation: dot-spin 1.5s infinite linear; 952 | } 953 | 954 | @keyframes dot-spin { 955 | 0%, 100% { 956 | box-shadow: 0 -18px 0 0 #9880ff, 12.727926px -12.727926px 0 0 #9880ff, 18px 0 0 0 #9880ff, 12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0), 0 18px 0 -5px rgba(152, 128, 255, 0), -12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0), -18px 0 0 -5px rgba(152, 128, 255, 0), -12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0); 957 | } 958 | 12.5% { 959 | box-shadow: 0 -18px 0 -5px rgba(152, 128, 255, 0), 12.727926px -12.727926px 0 0 #9880ff, 18px 0 0 0 #9880ff, 12.727926px 12.727926px 0 0 #9880ff, 0 18px 0 -5px rgba(152, 128, 255, 0), -12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0), -18px 0 0 -5px rgba(152, 128, 255, 0), -12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0); 960 | } 961 | 25% { 962 | box-shadow: 0 -18px 0 -5px rgba(152, 128, 255, 0), 12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0), 18px 0 0 0 #9880ff, 12.727926px 12.727926px 0 0 #9880ff, 0 18px 0 0 #9880ff, -12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0), -18px 0 0 -5px rgba(152, 128, 255, 0), -12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0); 963 | } 964 | 37.5% { 965 | box-shadow: 0 -18px 0 -5px rgba(152, 128, 255, 0), 12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0), 18px 0 0 -5px rgba(152, 128, 255, 0), 12.727926px 12.727926px 0 0 #9880ff, 0 18px 0 0 #9880ff, -12.727926px 12.727926px 0 0 #9880ff, -18px 0 0 -5px rgba(152, 128, 255, 0), -12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0); 966 | } 967 | 50% { 968 | box-shadow: 0 -18px 0 -5px rgba(152, 128, 255, 0), 12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0), 18px 0 0 -5px rgba(152, 128, 255, 0), 12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0), 0 18px 0 0 #9880ff, -12.727926px 12.727926px 0 0 #9880ff, -18px 0 0 0 #9880ff, -12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0); 969 | } 970 | 62.5% { 971 | box-shadow: 0 -18px 0 -5px rgba(152, 128, 255, 0), 12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0), 18px 0 0 -5px rgba(152, 128, 255, 0), 12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0), 0 18px 0 -5px rgba(152, 128, 255, 0), -12.727926px 12.727926px 0 0 #9880ff, -18px 0 0 0 #9880ff, -12.727926px -12.727926px 0 0 #9880ff; 972 | } 973 | 75% { 974 | box-shadow: 0 -18px 0 0 #9880ff, 12.727926px -12.727926px 0 -5px rgba(152, 128, 255, 0), 18px 0 0 -5px rgba(152, 128, 255, 0), 12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0), 0 18px 0 -5px rgba(152, 128, 255, 0), -12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0), -18px 0 0 0 #9880ff, -12.727926px -12.727926px 0 0 #9880ff; 975 | } 976 | 87.5% { 977 | box-shadow: 0 -18px 0 0 #9880ff, 12.727926px -12.727926px 0 0 #9880ff, 18px 0 0 -5px rgba(152, 128, 255, 0), 12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0), 0 18px 0 -5px rgba(152, 128, 255, 0), -12.727926px 12.727926px 0 -5px rgba(152, 128, 255, 0), -18px 0 0 -5px rgba(152, 128, 255, 0), -12.727926px -12.727926px 0 0 #9880ff; 978 | } 979 | } 980 | ` 981 | } 982 | 983 | function createShortcutIcon(link, iconUrl, cssClasses) { 984 | var htmlForIcon = "Loading...
"); 162 | 163 | $.get("https://www.neopets.com/inventory.phtml", ()=>{ 164 | console.log("[Inv+] Refreshed inventory contents.") 165 | $("#tabs > div.inv-tabs-container > ul.invTabs > li.inv-tab-label.invTab-selected")[0].click() //refreshes the inventory tab 166 | reload = true 167 | }) 168 | } 169 | 170 | function setStackView(type) { 171 | //if it's stacked and we want unstack 172 | if(type == "unstack" && $("#invStack > div.stack-icon-container.invfilter-active").length > 0) { 173 | document.getElementById("invStack").click() 174 | } 175 | //if it's unstacked and we want stacked 176 | else if(type == "stack" && $("#invStack > div.unstack-icon-container.invfilter-active").length > 0) { 177 | document.getElementById("invStack").click() 178 | } 179 | //simply toggles it 180 | else document.getElementById("invStack").click() 181 | } 182 | })(jQuery) -------------------------------------------------------------------------------- /Neopets - LoD Shame Tracker.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name Neopets - Lever of Doom Shame Tracker 3 | // @version 1.2 4 | // @description Tracks the amount of money you have lost to the Lever of Doom. 5 | // @author Metamagic 6 | // @match *neofood.club/* 7 | // @match *://www.neopets.com/space/strangelever.phtml* 8 | // @match *://neopets.com/space/strangelever.phtml* 9 | // @match *://www.neopets.com/space/leverofdoom.phtml* 10 | // @match *://neopets.com/space/leverofdoom.phtml* 11 | // @grant GM_getValue 12 | // @grant GM_setValue 13 | // @icon https://i.imgur.com/RnuqLRm.png 14 | // @downloadURL https://github.com/Mettymagic/np-userscripts/raw/main/Neopets%20-%20LoD%20Shame%20Tracker.user.js 15 | // @updateURL https://github.com/Mettymagic/np-userscripts/raw/main/Neopets%20-%20LoD%20Shame%20Tracker.user.js 16 | // ==/UserScript== 17 | 18 | // You are free to modify this script for personal use but modified scripts must not be shared publicly without permission. 19 | // Feel free to contact me at @mettymagic on discord for any questions or inquiries. ^^ 20 | 21 | // Trans rights are human rights ^^ 22 | // metty says hi 23 | 24 | if(window.location.href.includes("leverofdoom.phtml")) { 25 | //counts first pull 26 | if(GM_getValue("firstpull", false)) { 27 | let count = GM_getValue("pullcount", 0) //first load = count of 1 28 | count++ 29 | GM_setValue("pullcount", count) 30 | GM_setValue("firstpull", false) 31 | } 32 | 33 | //displays pull count 34 | $("#container__2020 > p")[0].innerHTML += "Lever Pulls: "+GM_getValue("pullcount", 1)+" | "+"Neopoints Lost: "+(GM_getValue("pullcount", 1)*100).toLocaleString("en-US")+"<\p>"
35 | let np = $("#npanchor")[0].innerHTML.replaceAll(",", "")
36 |
37 | //displays fake avatar popup if you have the avvie
38 | if(GM_getValue("hasAvvie")) {
39 | let div = document.createElement("div")
40 | div.style="display:block;position:relative;margin:auto;"
41 | let img = document.createElement("img")
42 | img.src = "https://i.imgur.com/2y2bO34.png"
43 | img.width = "401"
44 | img.height = "90"
45 | img.style = "display:block;position:relative;margin:auto;"
46 | div.appendChild(img)
47 | let disc = document.createElement("div")
48 | disc.innerHTML = "(you can stop pulling now)"
49 | disc.style = "display:block;position:absolute;left:50%;bottom:0px;transform:translateX(-50%);color:black;width:200px;height:20px;"
50 | div.appendChild(disc)
51 | $("#container__2020")[0].insertBefore(div, $("#container__2020 > div.page-title__2020")[0])
52 | console.log("[LoD] Added fake event display.")
53 | }
54 |
55 | //doesnt count as refresh if any link on the page is clicked
56 | let linkClicked = false
57 | $("body").on("click", "a", function () {
58 | linkClicked = true
59 | })
60 |
61 | //each unload (aka refresh), count a pull
62 | window.addEventListener("beforeunload", (e) => {
63 | if(!linkClicked) {
64 | //increments refresh count
65 | let count = GM_getValue("pullcount", 1)
66 | count++
67 | GM_setValue("pullcount", count)
68 | //updates count right then
69 | $("#container__2020 > p > p:nth-child(3)")[0].innerHTML = "Lever Pulls: "+count+" | "+"Neopoints Lost: "+(count*100).toLocaleString("en-US")
70 | }
71 | })
72 |
73 | //checks neoboards settings for if you have the avatar
74 | if(!GM_getValue("hasAvvie")) {
75 | console.log("Checking for avatar...")
76 | $.get("https://www.neopets.com/settings/neoboards/", function(data, status){
77 | let doc = new DOMParser().parseFromString(data, "text/html")
78 | //sometimes stackpath blocks the page, in which case open a newtab and close it after
79 | if(doc.title != "Neoboard Settings") {
80 | console.log("[APS] Neoboard Settings request blocked by stackpath. :(")
81 | }
82 | //find avvie from list
83 | let avvie = Array.from(doc.querySelectorAll("#SelectAvatarPopup > div.popup-body__2020 > div:nth-child(3) > div.settings-av")).find((div) => {return div.innerHTML.includes("Lever of Doom")})
84 | if(avvie) {
85 | GM_setValue("hasAvvie", true)
86 | console.log("[LoD] Lever of Doom avatar earned! Congrats!")
87 | }
88 | else console.log("[LoD] No avatar yet. Keep trying!")
89 | })
90 | }
91 | }
92 | else {
93 | GM_setValue("firstpull", true)
94 | }
--------------------------------------------------------------------------------
/Neopets - Mystery Pic Helper.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Neopets - Mystery Pic Helper Auto-Solve Sometimes small things on Neopets annoy me so I write userscripts to make things a bit easier and share them to the world to hopefully make everyone's life a bit easier.`
12 |
13 | let links = document.createElement("div")
14 | links.classList.add("links")
15 | links.innerHTML = `${SLOTH_ICON}Shopkeeper Banners|
16 | ${SLOTH_ICON}Game Icons |
17 | ${SLOTH_ICON}World Images`
18 | $("#content > table > tbody > tr > td.content")[0].insertBefore(links, $("#content > table > tbody > tr > td.content > center:nth-child(5)")[0])
19 |
20 | document.head.appendChild(document.createElement("style")).innerHTML = `
21 | .links {
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | }
26 | .links > * {
27 | padding: 0px 4px;
28 | display: flex;
29 | justify-content: space-between;
30 | align-items: center;
31 | }
32 | .links > a > img {
33 | padding-right: 4px;
34 | }
35 | `
36 |
37 | console.log("[MPH] Quicklinks added.")
--------------------------------------------------------------------------------
/Neopets - Negg Cave Solver.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Neopets - Negg Cave Solver
439 |
440 | `
441 | if(!hideRun) {
442 | countdiv.innerHTML += `
443 | Starting Tokens: ${count.toLocaleString("en-US")}
444 |
445 | Tokens Used: ${used.toLocaleString("en-US")}
446 |
447 | Tokens Left: ${left.toLocaleString("en-US")}
448 |
449 | `
450 | }
451 | countdiv.innerHTML += `
452 | ${color.charAt(0).toUpperCase() + color.substr(1)} Total Stats:
453 |
454 | Tokens Used: ${(res.count).toLocaleString("en-US")}
455 |
456 | Avg. NP: ${Math.round(res.avg).toLocaleString("en-US")} NP
457 |
458 | Highest NP: ${Math.max(res.list.slice(-1)[0]).toLocaleString("en-US")} NP
459 | `
460 | console.log(`[NCE] Token display added.`)
461 | return countdiv
462 | }
463 | }
464 |
465 | //shows # of item collected
466 | function displayItemStats() {
467 | const item_res = GM_getValue("item_res")
468 | if(item_res) { //schizochecking
469 | let nameelement = $("#content td.content > div[align='center'] > b:last-of-type")[0]
470 | let count = item_res[nameelement.innerHTML].count
471 | let cdiv = document.createElement("small")
472 | cdiv.innerHTML = `
(in case you're wondering, you've collected ${count} of these!)`
473 | nameelement.after(cdiv)
474 | }
475 | }
476 |
477 | function displayTransmogStats() {
478 | const results = GM_getValue("transmog_res")
479 | let odddiv = document.createElement("div")
480 | odddiv.classList.add("estodds")
481 |
482 | let itemname = $("#content td.content > div[align='center'] > b:last-of-type")[0].innerHTML
483 | let img = GM_getValue("lasttransmog", "//images.neopets.com/items/pot_acara_mutant.gif")
484 |
485 | //to make sure we display the right numbers since we reset them prior
486 | if(itemname.includes("Transmogrification")) {
487 | let data = results.list.slice(-1)[0]
488 | odddiv.innerHTML = `
489 |
490 |
491 | Tokens Used: ${data.n_since.toLocaleString("en-US")}
492 |
493 | Total Odds: ${(data.prob*100.0).toLocaleString("en-US")}%
494 | `
495 | }
496 | else {
497 | odddiv.innerHTML = `
498 |
499 |
500 | Tokens Used: ${results.n_since.toLocaleString("en-US")}
501 |
502 | Total Odds: ${(results.prob*100.0).toLocaleString("en-US")}%
503 | `
504 | }
505 | return odddiv
506 | }
507 |
508 | function displayTransmogWinnings() {
509 | let res = GM_getValue("transmog_res")[list]
510 | }
511 |
512 |
513 | //===================
514 | // token use overhaul
515 | //===================
516 |
517 | function addSendButton() {
518 | let button = document.createElement("button")
519 | button.innerHTML = "Use Another Token!"
520 | button.addEventListener("click", () => {sendRequest(RESUBMIT_TIMEOUT)})
521 | $("#content td.content > div:first-of-type")[0].insertBefore(button, $(`#content td.content > div:first-of-type > form`)[0])
522 | button.after(document.createElement("br"), document.createElement("br"))
523 | button.focus()
524 | console.log("[NCE] Resubmit button added.")
525 | }
526 |
527 | function sendRequest(timeout) {
528 | let input = GM_getValue("input")
529 | if(input == null) return
530 |
531 | console.log("[NCE] Attempting to use token...")
532 | reqQueue += 1
533 | $("#token_queue")[0].innerHTML = reqQueue
534 |
535 | $.ajax({
536 | type: "POST",
537 | url: "/moon/neocola3.phtml",
538 | data: {token_id:TOKEN_IDS[input.color], neocola_flavor:input.flavor, red_button:input.button},
539 | timeout: timeout,
540 | success: function(data) {
541 | reqQueue -= 1
542 | $("#token_queue")[0].innerHTML = reqQueue
543 | let doc = new DOMParser().parseFromString(data, "text/html")
544 | if(doc.title != "NeoCola Machine") console.log("[NCE] POST request blocked by stackpath. :(")
545 | else readResponse(doc)
546 | },
547 | error: function(xhr, status, error) {
548 | console.log(status + error)
549 | reqQueue -= 1
550 | $("#token_queue")[0].innerHTML = reqQueue
551 | }
552 | }, {token_id:TOKEN_IDS[input.color], neocola_flavor:input.flavor, red_button:input.button}, function(data, status){
553 | let doc = new DOMParser().parseFromString(data, "text/html")
554 | //sometimes stackpath blocks the page :/
555 | if(doc.title != "NeoCola Machine") console.log("[NCE] POST request blocked by stackpath. :(")
556 | })
557 | }
558 |
559 | function readResponse(doc) {
560 | console.log("[NCE] Response received, recording and updating display.")
561 | console.log(doc)
562 | //on error
563 | if(doc.querySelector(".errorMessage")) {
564 | console.log(doc.querySelector(".errorMessage").innerHTML)
565 | }
566 | else {
567 | $("#content td.content")[0].innerHTML = doc.querySelector("#content td.content").innerHTML
568 | modifyPrizePage()
569 | addQueueDisplay($("#content td.content button")[0])
570 | }
571 | }
572 |
573 | function addQueueDisplay(before) {
574 | let d = document.createElement("div")
575 | d.id = "token_queue"
576 | d.innerHTML = reqQueue
577 | before.after(d)
578 | console.log("[NCE] Queue display added")
579 | }
580 |
581 | //=========
582 | // css
583 | //=========
584 |
585 | function addSelectCSS() {
586 | document.head.appendChild(document.createElement("style")).innerHTML = `
587 | div.token_img.inactive::after {
588 | content: "";
589 | width: 80px;
590 | height: 80px;
591 | display: block;
592 | position: relative;
593 | left: 0;
594 | top: 0;
595 | background-color: white;
596 | opacity: 0.3;
597 | }
598 | .token_img {
599 | display: inline-block;
600 | width: 80px;
601 | height: 80px;
602 | border: 1px solid black;
603 | position: relative;
604 | }
605 | .token_count {
606 | font-size: 10pt;
607 | font-weight: bold;
608 | display: block;
609 | position: absolute;
610 | right: 2px;
611 | bottom: 2px;
612 | }
613 |
614 | #content > table > tbody > tr > td.content > br:nth-last-of-type(2) {
615 | display: none;
616 | }
617 | #content > table > tbody > tr > td.content > br:nth-last-of-type(3) {
618 | display: none;
619 | }
620 | `
621 | }
622 |
623 | function addPrizeCSS() {
624 | document.head.appendChild(document.createElement("style")).innerHTML = `
625 | #nce {
626 | display: flex;
627 | position: relative;
628 | }
629 | #totalstats {
630 | display: flex;
631 | flex-direction: column;
632 | position: relative;
633 | text-align: center;
634 | padding: 6px;
635 | border: 1px solid black;
636 | background-color: white;
637 | height: auto;
638 | margin: auto;
639 | }
640 |
641 | #np_stats, #transmog_stats {
642 | position: relative;
643 | display: block;
644 | width: fit-content;
645 | height: fit-content;
646 | padding: 8px;
647 | }
648 |
649 | #totalstats > div {
650 | display: flex;
651 | position: relative;
652 | flex-direction: row;
653 | gap: 4px;
654 | }
655 | #totalstats .tokensused, #totalstats .estodds {
656 | position: relative;
657 | left: auto;
658 | top: auto;
659 | }
660 |
661 | .tokensused {
662 | display: block;
663 | position: absolute;
664 | text-align: center;
665 | padding: 6px;
666 | border: 1px solid black;
667 | left: 7px;
668 | top: 30px;
669 | background-color: white;
670 | }
671 | .estodds {
672 | display: block;
673 | position: absolute;
674 | text-align: center;
675 | padding: 6px;
676 | border: 1px solid black;
677 | left: 7px;
678 | top: 250px;
679 | background-color: white;
680 | }
681 | #content td.content {
682 | position: relative;
683 | }
684 | #token_queue {
685 | position: absolute;
686 | display: inline-block;
687 | left: 61%;
688 | font-size: 17px;
689 | }
690 | `
691 | }
--------------------------------------------------------------------------------
/Neopets - NeoFoodClub+.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Neopets - NeoFoodClub+ div
44 | const NFC_MAX_BET_INPUT = "#root > header > div > div:nth-child(3) > div > div:nth-child(1) > div:nth-child(2) > div > div:nth-child(2) > input"
45 | const NFC_ROUND_NUMBER = "#root > header > div > div:nth-child(3) > div > div:nth-child(1) > div:nth-child(1) > div:nth-child(2) > input"
46 | const NFC_BET_BAR = "#root > div.css-1yysssr > div.css-1s00nyj"
47 | const NFC_NO_BET_BAR = "#root > div.css-1yysssr > div:nth-child(2) > div.css-1p24gq2 > div"
48 | const NFC_APPLY_BET_VALUE = "#root > div.css-1yysssr > div.css-1s00nyj > div.chakra-stack.css-8g8ihq > div > button.chakra-button.css-148ck9i"
49 |
50 | //classes
51 | const BUTTON_CONTAINER = "css-cpjzy9"
52 | const BUTTON_CONTAINER_2 = "css-8g8ihq"
53 | const NFC_BUTTON = "css-1873r7a"
54 | const NFC_BUTTON_NOBETS = "css-igc3ti"
55 | const GREEN_BET_BUTTON = "css-13ncfhw"
56 | const GRAY_BET_BUTTON = "css-n1yvyo"
57 | const RED_BET_BUTTON = "css-1j410sj"
58 |
59 | if(DEBUG == true) debug()
60 | function debug() {
61 | console.log($(NFC_BET_TABLE)[0])
62 | console.log($(NFC_MAX_BET_INPUT)[0])
63 | console.log($(NFC_ROUND_NUMBER)[0])
64 | console.log($(NFC_BET_BAR)[0])
65 | console.log($(NFC_NO_BET_BAR)[0])
66 | console.log($(NFC_APPLY_BET_VALUE)[0])
67 | }
68 |
69 |
70 | //================
71 | // food club dicts
72 | //================
73 |
74 | const ARENA_NAMES = {
75 | 1: "Shipwreck",
76 | 2: "Lagoon",
77 | 3: "Treasure Island",
78 | 4: "Hidden Cove",
79 | 5: "Harpoon Harry's"
80 | }
81 | const ARENA_IDS = Object.fromEntries(Object.entries(ARENA_NAMES).map(a => a.reverse()))
82 |
83 | const PIRATE_NAMES = {
84 | 1: "Dan",
85 | 2: "Sproggie",
86 | 3: "Orvinn",
87 | 4: "Lucky",
88 | 5: "Edmund",
89 | 6: "Peg Leg",
90 | 7: "Bonnie",
91 | 8: "Puffo",
92 | 9: "Stuff",
93 | 10: "Squire",
94 | 11: "Crossblades",
95 | 12: "Stripey",
96 | 13: "Ned",
97 | 14: "Fairfax",
98 | 15: "Gooblah",
99 | 16: "Franchisco",
100 | 17: "Federismo",
101 | 18: "Blackbeard",
102 | 19: "Buck",
103 | 20: "Tailhook",
104 | };
105 | const PIRATE_IDS = Object.fromEntries(Object.entries(PIRATE_NAMES).map(a => a.reverse()))
106 |
107 | //===============
108 | // main functions
109 | //===============
110 |
111 | //applies changes to neofood.club
112 | if(window.location.href.includes("neofood.club")) {
113 | if(GM_getValue("toprocess") == null) GM_setValue("toprocess", 0)
114 | addCSS()
115 | waitForBetTable()
116 | }
117 | //records max bet value from neopets food club page
118 | else if(window.location.href.includes("neopets.com/pirates/foodclub.phtml?type=bet") && AUTOCOLLECTMAXBET) {
119 | let maxbet = $("#content > table > tbody > tr > td.content > p:nth-child(7) > b")[0].innerHTML
120 | let date = new Date().toLocaleString("en-US", {timeZone: "PST"})
121 | GM_setValue("maxbet", {maxbet:maxbet, date:date})
122 | console.log("[NFC+] Max bet value recorded.")
123 | }
124 |
125 | //processing bet page - if the page loads at this url it's an error
126 | else if(window.location.href.includes("neopets.com/pirates/process_foodclub.phtml")) {
127 | if($(".errorMessage").length > 0) {
128 | let url = window.location.href
129 | let tabinfo = GM_getValue("tabinfo", {})
130 |
131 | //if url is marked, return error and close tab
132 | if(url in tabinfo) {
133 | let error = GM_getValue("betstatus", {})
134 | let betnum = tabinfo[url]
135 | if($(".errorMessage")[0].innerHTML.includes("you cannot place the same bet more than once!")) {
136 | error[tabinfo[url]] = "Already placed!"
137 | }
138 | else {
139 | error[tabinfo[url]] = "Invalid bet!"
140 | }
141 | GM_setValue("betstatus", error)
142 | console.log("[NFC+] Bet error status returned.")
143 | if(AUTOCLOSETABS) window.close()
144 | }
145 | }
146 | }
147 |
148 | //result page - record bets
149 | else if(window.location.href.includes("neopets.com/pirates/foodclub.phtml?type=current_bets")) {
150 | parseCurrentBets()
151 | let toProcess = GM_getValue("toprocess", 0)
152 | if(AUTOCLOSETABS && toProcess > 0) {
153 | GM_setValue("toprocess", toProcess-1)
154 | window.close()
155 | }
156 | //appends current bet count to current bets page for convenience
157 | let betCount = Array.from($("#content > table > tbody > tr > td.content > center:nth-child(6) > center:nth-child(2) > table tr[bgcolor='white']")).filter(r => r.children.length == 5).length
158 | $("#content > table > tbody > tr > td.content > center:nth-child(6) > center:nth-child(2) > table > tbody > tr:nth-child(1) > td > font")[0].innerHTML += ` (${betCount})`
159 | }
160 |
161 | //==================
162 | // program launching
163 | //==================
164 |
165 | //waits for the right conditions to apply the changes of the script - aka when to start
166 | function waitForBetTable() {
167 | //the table we want already exists, wait for it to finish populating
168 | let table = getBetTable()
169 | console.log(table)
170 | if(table) {
171 | const settableobs = new MutationObserver(mutations => {
172 | if(table.rows[1].children.length == 14) {
173 | console.log("[NFC+] Applying userscript to bet table...")
174 | handleNeoFoodMods()
175 | watchForBetTable()
176 | }
177 | })
178 | settableobs.observe(table, {subTree: true, childList: true});
179 | }
180 | //the table we want doesn't exist, wait for it to exist
181 | else {
182 | if(ADD_NEO_LINKS) addNeoLinks($(NFC_NO_BET_BAR)[0])
183 | watchForBetTable()
184 | }
185 | }
186 |
187 | //watches for the addition/removal of the main bet table on the page - aka when to re-apply the script
188 | function watchForBetTable() {
189 | let page = $("#root > div")[0]
190 | const pageobs = new MutationObserver(mutations => {
191 | //if table is added, apply
192 | for(const mutation of mutations) {
193 | if(mutation.addedNodes.length > 0) {
194 | //if the bet table is added, re-apply
195 | if(mutation.addedNodes[0] == getBetTable()?.parentElement) {
196 | console.log("[NFC+] Applying userscript to bet table...")
197 | handleNeoFoodMods()
198 | break
199 | }
200 | //otherwise, check if we need to add the bet links
201 | else if($("#quicklink-cont").length == 0 && ADD_NEO_LINKS) { //if bet links don't exist
202 | addNeoLinks($(NFC_BET_BAR)[0])
203 | }
204 | }
205 | }
206 | })
207 | pageobs.observe(page, {subTree: true, childList: true});
208 | }
209 |
210 | //runs the main functionality of the script
211 | function handleNeoFoodMods() {
212 | updateRound() //updates stored round #, which resets some things
213 | updateSetStatus() //updates set status if shit changes
214 | handleBetButtons() //updates place bet buttons
215 | addBetAllButton() //adds a button to place all bets at once
216 | updateMaxBet() //updates the max bet value in the header
217 | applyMaxBetValue() //presses the set all max bet button
218 | if(ADD_NEO_LINKS) addNeoLinks($(NFC_BET_BAR)[0]) //adds quick links
219 | }
220 |
221 | function clickAllBets() {
222 | for(let row of Array.from(getBetTable().children[1].getElementsByTagName("tr"))) {
223 | setTimeout(() => {
224 | let button = row.children[13].children[0]
225 | button.click()
226 | }, 500)
227 | }
228 | }
229 |
230 | function addBetAllButton() {
231 | let div = document.createElement("div")
232 | div.classList.add(BUTTON_CONTAINER)
233 | div.style.marginRight = "20px"
234 | div.color = "white"
235 | let button = document.createElement("button")
236 | button.type = "button"
237 | button.classList.add("chakra-button", NFC_BUTTON, NFC_BUTTON_NOBETS)
238 | button.style.userSelect = "auto"
239 | button.addEventListener("click", clickAllBets)
240 | button.innerHTML = "Place all bets"
241 | let reminder = document.createElement("div")
242 | reminder.classList.add(BUTTON_CONTAINER_2)
243 | reminder.style.fontSize = "9pt"
244 | reminder.style.marginTop = "2px"
245 | reminder.innerHTML = "(enable pop-ups)"
246 |
247 |
248 | div.appendChild(button)
249 | div.appendChild(reminder)
250 | $(NFC_BET_BAR)[0].appendChild(div)
251 | }
252 |
253 | function updateRound() {
254 | let resetMsg = null
255 |
256 | //if new round
257 | let prevRound = GM_getValue("round", null)
258 | let currRound = $(NFC_ROUND_NUMBER)[0].getAttribute("value")
259 | if(currRound != prevRound) {
260 | GM_setValue("round", currRound) //and update the current round
261 | resetMsg = "New round detected, cleared stored bet info."
262 | }
263 |
264 | //if new bet url
265 | let prevURL = GM_getValue("beturl", null)
266 | let currURL = window.location.href.match(/.*&b=(\w*).*?/)[1]
267 | if(prevURL != currURL) {
268 | GM_setValue("beturl", currURL) //and update the current round
269 | resetMsg = "New bet URL detected, cleared stored bet info."
270 | }
271 |
272 | //if either changed, reset stuff
273 | if(resetMsg != null) {
274 | GM_deleteValue("tabinfo")
275 | GM_deleteValue("betstatus")
276 | GM_deleteValue("placedbets")
277 | GM_setValue("toprocess", 0)
278 | console.log("[NFC+] "+ resetMsg)
279 | }
280 | }
281 |
282 | //reads the current bets page to check whether a bet was placed or not
283 | function parseCurrentBets() {
284 | let currentbets = Array.from($("#content > table > tbody > tr > td.content > center:nth-child(6) > center:nth-child(2) > table tr[bgcolor='white']")).filter(r => r.children.length == 5)
285 | let newBets = currentbets.length - GM_getValue("placedbets", []).length
286 | //only updates stored bet list if there are new bets detected (to deal with out-of-order loading)
287 | if(newBets > 0) {
288 | let betList = []
289 | let closeTab = false
290 | //parse each row
291 | for(const row of currentbets) {
292 | //dict of 5 arenas and their betters
293 | let bets = row.children[1].innerHTML
294 | .replaceAll('"', "").replaceAll("", "").replaceAll("", "").split("
").slice(0, -1)
295 | //parses bet info
296 | let betmap = {1:null,2:null,3:null,4:null,5:null}
297 | for(const bet of bets) {
298 | let s = bet.split(":")
299 | //get arena number
300 | let arena = ARENA_IDS[s[0]]
301 | //get pirates partial name
302 | for(const piratename of Object.values(PIRATE_NAMES)) {
303 | if(s[1].includes(piratename)) {
304 | var pirate = piratename
305 | break
306 | }
307 | }
308 | //add to betmap
309 | betmap[arena] = pirate
310 | }
311 | betList.push(betmap)
312 | }
313 | //update global value
314 | GM_setValue("placedbets", betList)
315 | console.log("[NFC+] Current bets list updated.")
316 | //updates # of tabs to close
317 | GM_setValue("toprocess", GM_getValue("toprocess", 0) + newBets)
318 | }
319 | }
320 |
321 |
322 | //==============
323 | // apply max bet
324 | //==============
325 |
326 | //updates max bet value in header field
327 | function updateMaxBet() {
328 | if(AUTOCOLLECTMAXBET) {
329 | let maxbetdata = GM_getValue("maxbet", null)
330 | if(maxbetdata != null) {
331 | let currDate = new Date()
332 | let collectionDate = new Date(maxbetdata.date)
333 | let mindiff = (currDate.getTime() - collectionDate.getTime())/1000/60
334 |
335 | if(mindiff < AUTOCOLLECT_TIMEOUT) {
336 | let input = $(NFC_MAX_BET_INPUT)[0]
337 | console.log(input.value)
338 | if(input.value < maxbetdata.maxbet) {
339 | input.focus()
340 | input.value = maxbetdata.maxbet
341 | input.setAttribute("aria-valuenow", maxbetdata.maxbet)
342 | input.setAttribute("aria-valuetext", maxbetdata.maxbet)
343 | input.parentElement.value = maxbetdata.maxbet
344 | input.blur()
345 | console.log("[NFC+] Stored max bet value applied.")
346 | }
347 | //otherwise does nothing.
348 | else console.log("[NFC+] Max bet value less than current value, no update.")
349 | }
350 | //timed out
351 | else {
352 | console.log("[NFC+] Max bet value timed out, data invalidated.")
353 | }
354 | GM_deleteValue("maxbet")
355 | }
356 | }
357 | }
358 |
359 | //presses the 'set bet amounts to max' button
360 | function applyMaxBetValue() {
361 | if(AUTOMAXBET) {
362 | let button = $(NFC_APPLY_BET_VALUE)[0]
363 | if(button) {
364 | button.click()
365 | console.log("[NFC+] Bets automatically set to max value.")
366 | }
367 | }
368 | }
369 |
370 | //note: designed with dark mode in mind, too lazy to make a light mode one too.
371 | function addNeoLinks(cont) {
372 | let linkCont = document.createElement("div")
373 | linkCont.classList.add(BUTTON_CONTAINER)
374 | linkCont.style.marginRight = "20px"
375 | linkCont.style.color = "white"
376 | linkCont.id = "quicklink-cont"
377 | let linkCont2 = document.createElement("div")
378 | linkCont2.classList.add("chakra-stack", BUTTON_CONTAINER_2)
379 |
380 | let button1 = document.createElement("button")
381 | button1.type = "button"
382 | button1.classList.add("chakra-button", NFC_BUTTON, NFC_BUTTON_NOBETS)
383 | let button2 = button1.cloneNode()
384 | button1.innerHTML = "Current Bets"
385 | button1.addEventListener("click", () => { window.open('https://www.neopets.com/pirates/foodclub.phtml?type=current_bets'); GM_setValue("toprocess", 0) })
386 | button2.innerHTML = "Collect Winnings"
387 | button2.addEventListener("click", () => { window.open('https://www.neopets.com/pirates/foodclub.phtml?type=collect') })
388 |
389 | linkCont2.appendChild(button1)
390 | linkCont2.appendChild(button2)
391 | linkCont.appendChild(linkCont2)
392 | cont.appendChild(linkCont)
393 |
394 | }
395 |
396 |
397 | //===========
398 | // bet button
399 | //===========
400 |
401 | function handleBetButtons() {
402 | //updates button status
403 | updateButtonStatus()
404 |
405 | //updates existing table
406 | for(let row of Array.from(getBetTable().children[1].getElementsByTagName("tr"))) {
407 | let button = row.children[13].children[0]
408 | addButtonObserver(button.parentElement)
409 | if(!button.disabled) addButtonListener(button)
410 | }
411 |
412 | //observes updates in table to apply updates
413 | addRowObserver()
414 | //listens for outcomes after placing bets
415 | addResultsListeners()
416 | }
417 |
418 | //listens for added/removed rows
419 | function addRowObserver() {
420 | let tbody = getBetTable().children[1]
421 | const tbodyobs = new MutationObserver(mutations => {
422 | for(const mutation of mutations) {
423 | if(mutation.addedNodes.length > 0) {
424 | let button = mutation.addedNodes[0].children[13].children[0]
425 | addButtonObserver(button.parentElement)
426 | if(!button.disabled) addButtonListener(button)
427 | applyMaxBetValue()
428 | }
429 | }
430 | })
431 | tbodyobs.observe(tbody, {childList: true})
432 | }
433 |
434 | function addResultsListeners() {
435 | //updates successful bet statuses based on current bet page
436 | GM_addValueChangeListener("placedbets", function(key, oldValue, newValue, remote) {
437 | updateSetStatus()
438 | })
439 |
440 | //updates bet buttons based on bet status updates
441 | GM_addValueChangeListener("betstatus", function(key, oldValue, newValue, remote) {
442 | updateButtonStatus()
443 | })
444 | }
445 |
446 | //updates set statuses to match the current bets screen
447 | function updateSetStatus() {
448 | let placedBets = GM_getValue("placedbets", [])
449 | let betStatus = GM_getValue("betstatus", {})
450 | for(const bet of placedBets) {
451 | let row = getRowFromCurrentBet(bet) //finds the row of the bet
452 | let betNum = row.children[0].getElementsByTagName("p")[0].innerHTML //finds the number from the row
453 |
454 | betStatus[betNum] = "Bet placed!"
455 | }
456 | GM_setValue("betstatus", betStatus)
457 | }
458 |
459 | //updates the buttons to match their bet status
460 | function updateButtonStatus() {
461 | let betStatus = GM_getValue("betstatus", {})
462 | for(const betNum in betStatus) {
463 | let button = getBetRow(betNum).children[13].children[0]
464 | let statusMsg = betStatus[betNum]
465 | button.firstChild.data = statusMsg
466 | if(statusMsg == "Already placed!" || statusMsg == "Invalid bet!") {
467 | button.classList.add(RED_BET_BUTTON)
468 | button.classList.remove(GRAY_BET_BUTTON)
469 | button.classList.remove(GREEN_BET_BUTTON)
470 | }
471 | if(statusMsg == "Bet placed!") {
472 | button.classList.add(GRAY_BET_BUTTON)
473 | button.disabled = true
474 | }
475 | }
476 | }
477 |
478 | function addButtonObserver(buttonCell) {
479 | const rowobs = new MutationObserver(mutations => {
480 | for(const mutation of mutations) {
481 | //button updates by removing and creating a new one. apply updates to the new button.
482 | if(mutation.addedNodes.length > 0) {
483 | let newButton = mutation.addedNodes[0]
484 | if(!newButton.disabled) {
485 | addButtonListener(newButton)
486 | }
487 | }
488 | }
489 | })
490 | rowobs.observe(buttonCell, {childList: true, subtree: true})
491 | }
492 |
493 | function addButtonListener(button) {
494 | let betNum = button.parentElement.parentElement.getElementsByTagName("p")[0].innerHTML
495 | button.addEventListener("click", function(event){onButtonClick(event, betNum, button)})
496 | }
497 |
498 | function onButtonClick(event, betNum, button) {
499 | //overrides behavior
500 | event.stopPropagation()
501 | //updates button
502 | button.firstChild.data = "Processing..."
503 | button.classList.remove(GREEN_BET_BUTTON)
504 | button.classList.add(GRAY_BET_BUTTON)
505 | button.disabled = true
506 | //generates link again... manually... and opens it in background
507 | let link = generate_bet_link(betNum)
508 | openBackgroundTab(link, betNum)
509 | }
510 |
511 | function generate_bet_link(betNum) {
512 | let urlString =
513 | "https://www.neopets.com/pirates/process_foodclub.phtml?";
514 | let betData = getBetData(betNum)
515 | let bet = betData.winners
516 | for (let i = 0; i < 5; i++) {
517 | if (bet[i] != null) {
518 | urlString += `winner${i + 1}=${bet[i]}&`;
519 | }
520 | }
521 | for (let i = 0; i < 5; i++) {
522 | if (bet[i] != null) {
523 | urlString += `matches[]=${i + 1}&`;
524 | }
525 | }
526 | urlString += `bet_amount=${betData.bet_amount}&`;
527 | urlString += `total_odds=${betData.total_odds}&`;
528 | urlString += `winnings=${betData.winnings}&`;
529 | urlString += "type=bet";
530 | return urlString;
531 | }
532 |
533 | function openBackgroundTab(url, betNum) {
534 | //opens tab
535 | if(FORCEBGTAB) {
536 | window.open(url, "_blank")
537 | window.focus()
538 | console.log("[NFC+] Forced new tab to back.")
539 | }
540 | else window.open(url)
541 |
542 | //records info abt the tab so the program knows it was pressed from here
543 | let tabinfo = GM_getValue("tabinfo", {})
544 | tabinfo[url] = betNum
545 | GM_setValue("tabinfo", tabinfo)
546 | }
547 |
548 |
549 | //=================
550 | // helper functions
551 | //=================
552 |
553 | function getBetTable() {
554 | let t = document.querySelectorAll(NFC_BET_TABLE)
555 | if(t.length > 0) {
556 | let last = Array.from(t).slice(-1)[0]
557 | if(last.querySelector("th").innerHTML == "Bet #") return last
558 | }
559 | }
560 |
561 | function getBetData(betNum) {
562 | let betRow = getBetRow(betNum)
563 |
564 | let bet_amount = betRow.children[1].children[0].getAttribute("value")
565 | let total_odds = betRow.children[2].innerHTML.split(":")[0]
566 | let winnings = betRow.children[3].innerHTML.replace(",", "")
567 | let winners = []
568 | for(let i=8; i<13; i++) {
569 | let pirate = betRow.children[i].innerHTML || null
570 | if(pirate != null) pirate = PIRATE_IDS[pirate]
571 | winners.push(pirate)
572 | }
573 |
574 | return {bet_amount:bet_amount, total_odds:total_odds, winnings:winnings, winners:winners}
575 | }
576 |
577 |
578 | function getBetRow(betNum) {
579 | let table = getBetTable()
580 | let tbody = table.children[1]
581 | let rows = Array.from(tbody.getElementsByTagName("tr"))
582 | let betRow = rows.filter(row=>{
583 | let num = row.children[0].getElementsByTagName("p")[0].innerHTML
584 | return num == betNum
585 | })[0]
586 | return betRow
587 | }
588 |
589 | function getRowFromCurrentBet(bet) {
590 | let rows = Array.from(getBetTable().children[1].getElementsByTagName("tr"))
591 | let i = 0
592 | for(const row of rows) {
593 | let match = true
594 | for(let i = 0; i < 5; i++) {
595 | if((row.children[8+i].innerHTML || null) != bet[i+1]) {
596 | match = false
597 | break
598 | }
599 | }
600 | if(match) return row
601 | }
602 | return null
603 | }
604 |
605 | //============================
606 | // css because react is stupid
607 | //============================
608 |
609 | function addCSS() {
610 | document.head.appendChild(document.createElement("style")).innerHTML = `
611 | /* green button */
612 | .${GREEN_BET_BUTTON}[disabled], .${GREEN_BET_BUTTON}[aria-disabled="true"], .${GREEN_BET_BUTTON}[data-disabled] {
613 | opacity: 0.4;
614 | box-shadow: var(--chakra-shadows-none);
615 | cursor: auto;
616 | }
617 |
618 | /*grey button*/
619 | .${GRAY_BET_BUTTON} {
620 | display: inline-flex;
621 | appearance: none;
622 | -webkit-box-align: center;
623 | align-items: center;
624 | -webkit-box-pack: center;
625 | justify-content: center;
626 | user-select: none;
627 | position: relative;
628 | white-space: nowrap;
629 | vertical-align: middle;
630 | outline: transparent solid 2px;
631 | outline-offset: 2px;
632 | width: 100%;
633 | line-height: 1.2;
634 | border-radius: var(--chakra-radii-md);
635 | font-weight: var(--chakra-fontWeights-semibold);
636 | transition-property: var(--chakra-transition-property-common);
637 | transition-duration: var(--chakra-transition-duration-normal);
638 | height: var(--chakra-sizes-8);
639 | min-width: var(--chakra-sizes-8);
640 | font-size: var(--chakra-fontSizes-sm);
641 | padding-inline-start: var(--chakra-space-3);
642 | padding-inline-end: var(--chakra-space-3);
643 | background: var(--chakra-colors-whiteAlpha-200);
644 | }
645 | .${GRAY_BET_BUTTON}:disabled, .${GRAY_BET_BUTTON}[data-hover] {
646 | background: var(--chakra-colors-whiteAlpha-300);
647 | cursor: not-allowed;
648 | }
649 | /* red button*/
650 | .${RED_BET_BUTTON}[disabled], .${RED_BET_BUTTON}[aria-disabled="true"], .${RED_BET_BUTTON}[data-disabled] {
651 | opacity: 0.4;
652 | cursor: not-allowed;
653 | box-shadow: var(--chakra-shadows-none);
654 | }
655 | .${RED_BET_BUTTON} {
656 | display: inline-flex;
657 | appearance: none;
658 | -webkit-box-align: center;
659 | align-items: center;
660 | -webkit-box-pack: center;
661 | justify-content: center;
662 | user-select: none;
663 | position: relative;
664 | white-space: nowrap;
665 | vertical-align: middle;
666 | outline: transparent solid 2px;
667 | outline-offset: 2px;
668 | width: 100%;
669 | line-height: 1.2;
670 | border-radius: var(--chakra-radii-md);
671 | font-weight: var(--chakra-fontWeights-semibold);
672 | transition-property: var(--chakra-transition-property-common);
673 | transition-duration: var(--chakra-transition-duration-normal);
674 | height: var(--chakra-sizes-8);
675 | min-width: var(--chakra-sizes-8);
676 | font-size: var(--chakra-fontSizes-sm);
677 | padding-inline-start: var(--chakra-space-3);
678 | padding-inline-end: var(--chakra-space-3);
679 | background: var(--chakra-colors-red-200);
680 | color: var(--chakra-colors-gray-800);
681 | }
682 | `
683 | }
--------------------------------------------------------------------------------
/Neopets - Quick Exploration Links.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Neopets - Quick Exploration Links "
131 | LINK_LIST.forEach(row => {
132 | html += `
133 |
"
140 | list.innerHTML = html
141 | explore.appendChild(list)
142 | }
143 |
144 | document.head.appendChild(document.createElement("style")).innerHTML = `
145 | ${explorePath} .nav-dropdown__2020 {
146 | background-color: ${bgcolor};
147 | color: ${color};
148 | border: ${border};
149 | }
150 | ${explorePath} .nav-dropdown__2020 ul {
151 | background-color: ${bgcolor};
152 | border: ${border};
153 | }
154 | ${explorePath} .nav-dropdown__2020 ul h4 {
155 | color: ${color};
156 | }
157 | `
158 | })
--------------------------------------------------------------------------------
/Neopets - Quick Inventory Deposit.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Neopets - Quick Inventory Deposit ${row.name}
136 |
\t- ${item.name} x ${item.count}`
89 | }
90 | let display = document.createElement("p")
91 | display.innerHTML = str
92 | $("#content td.content")[0].insertBefore(display, $("#content > table > tbody > tr > td.content > table")[0])
93 | console.log("Items displayed.")
94 | }
95 |
96 | //this is technically illegal btw
97 | //todo: remove this
98 | function autoNavigate() {
99 | console.log("Attempting to navigate to next page...")
100 | $("#content > table > tbody > tr > td.content > table > tbody > tr > td:nth-child(3) > a")?.[0]?.click()
101 | }
102 |
103 | //==============
104 | // parsing pages
105 | //==============
106 |
107 | function recordSDBPage() {
108 | let rows = Array.from($("#content td.content > form > table:nth-of-type(2) > tbody > tr:not(:first-child):not(:last-child)"))
109 | console.log(`[SDB] Reading data for ${rows.length} items.`)
110 | let pagedata = {}
111 | for(const tr of rows) {
112 | let rowdata = parseRowData(tr)
113 | pagedata[JSON.stringify(rowdata[0])] = rowdata[1]
114 | }
115 | getItemDBData(pagedata)
116 | }
117 |
118 | function parseRowData(tr) {
119 | let name = tr.querySelector(" td:nth-child(2) > b").childNodes[0].nodeValue
120 | let img = tr.querySelector("td:nth-child(1) > img").getAttribute("src").match(/.*?images\.neopets\.com\/items\/(.+?)\..+/)[1]
121 | let desc = tr.querySelector("td:nth-child(3) > i").innerHTML
122 | let type = tr.querySelector("td:nth-child(4) > b").innerHTML
123 | let count = parseInt(tr.querySelector("td:nth-child(5) > b").innerHTML)
124 | let id = parseInt(tr.querySelector("td:nth-child(6) > input").getAttribute("name").match(/back_to_inv\[([0-9]+)\]/)[1])
125 | let tag = tr.querySelector("span.medText font") || null
126 | let searchhelper = tr.querySelector("td:nth-child(2) > p.search-helper") || null //for compatibility with dice's search helper script
127 | return [id,{name:name, image_id:img, desc:desc, category:type, count:count, tag: tag, searchhelper:searchhelper, id:id}]
128 | }
129 |
130 | function depositQuickStock() {
131 | let submit = $(`#content td.content > form tr:last-child input[type=submit]:nth-child(1)`)?.[0]
132 | if(submit) {
133 | submit.addEventListener("click", (event) => {
134 | //event.preventDefault()
135 | //event.stopPropagation()
136 | if(!clicked) {
137 | let selected = Array.from($(`form[name="quickstock"] tr:not(:nth-last-child(-n+2)) input:checked`)).slice(0,70) //quick stock can only do 70 at a time!
138 | let deposited = selected.filter((e) => {return e.value == "deposit"}).map((e) => {return e.parentElement.parentElement.querySelector("td:first-child").childNodes[0].nodeValue})
139 | if(deposited.length > 0) {
140 | console.log(`[SDB] Recording deposit of ${deposited.length} items...`)
141 | recordItemListToSDB(deposited)
142 | }
143 | clicked = true
144 | }
145 | })
146 | console.log("[SDB] Watching quick stock for deposits...")
147 | }
148 | }
149 |
150 | function depositInventory() {
151 | const resultObs = new MutationObserver((mutations) => {
152 | if($("#invResult > div.popup-body__2020 > p")[0].innerHTML.includes("into your safety deposit box!")) {
153 | //get name and img to identify item
154 | let name = $("#invResult > div.popup-body__2020 > p > b")[0].innerHTML
155 | let img = $("#invResult > div.popup-body__2020 > div > img")[0].getAttribute("src").match(/.*?images\.neopets\.com\/items\/(.+?)\..+/)[1]
156 | console.log(`[SDB] Recording ${name} deposit...`)
157 |
158 | //check if we already have info on this in sdb
159 | let res = Object.values(GM_getValue("sdbdata", {})).find((i) => {return i.name == name && i.image_id == img})
160 | //already have info, increment count
161 | if(res != null) {
162 | res.count += 1
163 | GM_setValue("sdbdata", res)
164 | console.log(`[SDB] Item count incremented.`)
165 | }
166 | //don't have info, get info from itemdb
167 | else getItemDBDataFromPair([name, img])
168 | }
169 | })
170 | resultObs.observe($("#invResult")[0], {characterData: true, childList: true, subtree: true})
171 | console.log("[SDB] Watching inventory for deposits...")
172 | }
173 |
174 | //necessary for if items are fully removed and need to be removed from the sdbdata list
175 | function removeVanillaSDB() {
176 | //remove one - simply update count
177 | $("#content td.content > form > table:nth-child(3) > tbody").on("click", "td:nth-child(6) > a", function (event) {
178 | //event.stopPropagation()
179 | //event.preventDefault()
180 | if(!clicked) {
181 | let id = parseInt(this.parentElement.querySelector("input").getAttribute("name").match(/back_to_inv\[([0-9]+)\]/)[1])
182 | let sdb = GM_getValue("sdbdata", {})
183 | sdb[id].count -= 1
184 | console.log(`[SDB] Removed 1 ${sdb[id].name} from SDB.`)
185 | if(sdb[id].count == 0) delete sdb[id]
186 | GM_setValue("sdbdata", sdb)
187 | clicked = true
188 | }
189 | })
190 | //move selected items
191 | $("#content td.content > form input.submit_data")[0].addEventListener("click", (event) => {
192 | if(!clicked) {
193 | let changeditems = []
194 | for(const input of Array.from($("input.remove_safety_deposit"))) {
195 | let count = input.value
196 | //for each input that we're removing something from, decrease the count
197 | if(count > 0) {
198 | let sdb = GM_getValue("sdbdata", {})
199 | let id = input.getAttribute("name").match(/back_to_inv\[([0-9]+)\]/)[1]
200 | sdb[id].count -= count
201 | changeditems.push([sdb[id].name, count])
202 | if(sdb[id].count == 0) delete sdb[id]
203 | GM_setValue("sdbdata", sdb)
204 | }
205 | }
206 | if(changeditems.length > 0) {
207 | console.log("[SDB] The following items were removed:")
208 | for(const item of changeditems) console.log(`\t- ${item[0]} (x${item[1]})`)
209 | }
210 | clicked = true
211 | }
212 | })
213 | }
214 |
215 | //==============
216 | // sdb item data
217 | //==============
218 |
219 | function savePageData(pagedata) {
220 | //saves data
221 | let sdbdata = GM_getValue("sdbdata", {})
222 | for(const item of Object.keys(pagedata)) {
223 | sdbdata[item] = pagedata[item]
224 | }
225 | GM_setValue("sdbdata", sdbdata)
226 |
227 | let items = Object.keys(sdbdata).length
228 | let qty = Object.values(sdbdata).reduce((a, b) => {return a + b.count}, 0)
229 |
230 | //shows item count recorded so far
231 | //todo: store these counts as a variable before page modifications
232 | if(url.includes("safetydeposit.phtml")) {
233 | let itemcounts = $("#content td.content > table > tbody > tr > td:nth-child(2)")[0].innerHTML.match(/Items:<\/b> (.+?) \| Qty:<\/b> (.+)/)
234 | console.log(`[SDB] SDB page recorded. (Items: ${items}/${itemcounts[1].replaceAll(",","")}, Qty: ${qty}/${itemcounts[2].replaceAll(",","")})`)
235 | //autoNavigate()
236 | }
237 | else console.log(`[SDB] SDB page recorded. (Items: ${items}, Qty: ${qty})`)
238 | }
239 |
240 | function recordItemListToSDB(itemnames) {
241 | let toitemdb = {}
242 | let uncertain = GM_getValue("uncertainitems", {})
243 | let sdbdata = GM_getValue("sdbdata", {})
244 |
245 | //handles each item to be deposited
246 | let updated = 0
247 | for(const name of itemnames) {
248 | let item = getSDBItemByName(name)
249 | if(Object.keys(item).length == 0) { //we don't have item info, let's grab it from itemdb
250 | if(toitemdb[name]) toitemdb[name] += 1
251 | else toitemdb[name] = 1
252 | }
253 | else if(item == null) { //multiple items with the same name, can't tell item without manual checking
254 | uncertain[name] = undefined
255 | console.log(`[SDB] WARNING: Can't determine item ID from item with duplicate name! (${name})`)
256 | }
257 | else {
258 | Object.values(sdbdata).find((i) => {return i.name == name}).count += 1 //increment count in sdb
259 | updated += 1
260 | }
261 | }
262 |
263 | GM_setValue("sdbdata", sdbdata)
264 | GM_setValue("uncertainitems", uncertain)
265 |
266 | if(updated > 1) console.log(`[SDB] Updated SDB item quantity for ${updated} deposited items.`)
267 | else if(updated == 1) console.log(`[SDB] Updated SDB item quantity for ${updated} deposited item.`)
268 |
269 | if(Object.keys(toitemdb).length > 0) getItemDBDataFromNames(toitemdb) //fetches data for items from db
270 | }
271 |
272 | function getSDBItemByName(name) {
273 | //can't be certain of items that share name, eg map pieces
274 | if(isDuplicateName(name)) return null
275 | return Object.values(GM_getValue("sdbdata", {})).find((i) => {return i.name == name}) || {}
276 | }
277 |
278 | function isDuplicateName(name) {
279 | return name.includes("Map Piece") || name.includes("Laboratory Map") || DUPLICATE_NAMES.includes(name)
280 | }
281 |
282 | //================
283 | // itemdb requests
284 | //================
285 |
286 | function parseItemDBResponse(data, itemlist, pair=false) {
287 | if(Object.keys(data).length < itemlist.length) console.log(`[SDB] WARNING: ItemDB only returned ${Object.keys(data).length} items. Make sure to submit data to itemDB using their userscript!`)
288 |
289 | let pagedata = {}
290 | for(let name of Object.keys(data)) {
291 | let key = data[name].item_id
292 | pagedata[key] = {}
293 | pagedata[key].name = data[name].name
294 | pagedata[key].image_id = data[name].image_id
295 | pagedata[key].category = data[name].category
296 | pagedata[key].desc = data[name].description
297 | pagedata[key].tag = data[name].specialType
298 | pagedata[key].rarity = data[name].rarity
299 | pagedata[key].searchhelper = null
300 | pagedata[key].estVal = data[name].estVal
301 | pagedata[key].isNC = data[name].isNC
302 | pagedata[key].isWearable = data[name].isWearable
303 | pagedata[key].isNeohome = data[name].isNeohome
304 | pagedata[key].isNoTrade = data[name].status == "no trade"
305 | pagedata[key].priceData = data[name].price
306 | pagedata[key].owlsData = data[name].owls
307 | if(pair) pagedata[key].count = 1
308 | else pagedata[key].count = itemlist[name]
309 | }
310 | console.log(`[SDB] Item data for ${Object.keys(data).length} items grabbed from ItemDB. Thanks Magnetismo Times!`)
311 | savePageData(pagedata)
312 | }
313 |
314 | function getItemDBData(pagedata) {
315 | let itemlist = Object.keys(pagedata)
316 | $.get("https://itemdb.com.br/api/v1/items/many", {item_id:itemlist}, (data, status)=>{
317 | if(Object.keys(data).length < itemlist.length) console.log(`[SDB] WARNING: ItemDB only returned ${Object.keys(data).length} items, expected ${itemlist.length} items.`)
318 | for(const key of Object.keys(data)) {
319 | //adds data from itemdb
320 | pagedata[key].rarity = data[key].rarity
321 | pagedata[key].estVal = data[key].estVal
322 | pagedata[key].isNC = data[key].isNC
323 | pagedata[key].isWearable = data[key].isWearable
324 | pagedata[key].isNeohome = data[key].isNeohome
325 | pagedata[key].isNoTrade = data[key].status == "no trade"
326 | pagedata[key].priceData = data[key].price
327 | pagedata[key].owlsData = data[key].owls
328 | }
329 | console.log(`[SDB] Additional item data for ${Object.keys(data).length} items grabbed from ItemDB. Thanks Magnetismo Times!`)
330 | savePageData(pagedata)
331 | })
332 | }
333 |
334 | function getItemDBDataFromNames(itemlist) {
335 | console.log(`[SDB] Fetching item data from ItemDB for ${Object.keys(itemlist).length} items.`)
336 | $.get("https://itemdb.com.br/api/v1/items/many", {name:Object.keys(itemlist)}, (data) => {
337 | parseItemDBResponse(data, itemlist)
338 | })
339 | }
340 |
341 | function getItemDBDataFromPair(pair) {
342 | console.log(`[SDB] Fetching item data from ItemDB for ${pair[0]}.`)
343 | $.get("https://itemdb.com.br/api/v1/items/many", {name_image_id:[pair]}, (data) => {
344 | parseItemDBResponse(data, [pair[0]], true)
345 | })
346 | }
347 |
348 | /*
349 | //back_to_inv%5B1%5D=1&back_to_inv%5B192%5D=1&obj_name=&category=0&offset=0
350 | $.post("/process_safetydeposit.phtml?checksub=scan", "back_to_inv%5B1%5D=1&back_to_inv%5B478%5D=1&obj_name=&category=0&offset=0", (data, status) => {
351 | console.log("status: "+status)
352 | })
353 | */
--------------------------------------------------------------------------------
/Neopets - Trans Affirming Neopets (Compat).user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name Neopets - Trans Affirming Neopets (Userscript Compat)