Last active
July 25, 2026 18:20
-
-
Save diceroll123/e01aeb97cc22ac11f3a296f15c3e9170 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Neopets - SDB Quick Remove | |
| // @version 1.0 | |
| // @author diceroll123 | |
| // @description Adds a Quick Remove link to each Safety Deposit Box row that moves the chosen quantity straight to Inventory, skipping the confirm popup. | |
| // @match *://*.neopets.com/safetydeposit.phtml* | |
| // @run-at document-idle | |
| // @grant none | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| const ROW_SELECTOR = '.sdb-table tbody tr'; | |
| const ACTION_CELL_SELECTOR = '.sdb-cell-action'; | |
| const CHECKBOX_SELECTOR = 'td.sdb-col-select input[id^="sdb-chk-"]'; | |
| const CHECKBOX_ID_PREFIX = 'sdb-chk-'; | |
| const STEPPER_SELECTOR = '.np-stepper-input'; | |
| const QUICK_REMOVE_CLASS = 'qr-quick-remove'; | |
| const QUICK_REMOVE_TEXT = 'Quick Remove'; | |
| const MOVING_TEXT = 'Moving...'; | |
| const LINK_STYLE = 'display: block; text-align: center; font-size: 14px; margin-top: 4px; color: #0000EE; text-decoration: underline;'; | |
| const MOVE_ITEMS_URL = '/np-templates/ajax/safetydeposit/move-items.php'; | |
| const MOVE_ACTION_INVENTORY = 'inventory'; | |
| const OBSERVER_TARGET_ID = 'sdb-vue-app'; | |
| const INJECT_DEBOUNCE_MS = 50; | |
| function injectButtons() { | |
| const rows = document.querySelectorAll(ROW_SELECTOR); | |
| rows.forEach(function (row) { | |
| if (row.querySelector('.' + QUICK_REMOVE_CLASS)) return; | |
| const actionCell = row.querySelector(ACTION_CELL_SELECTOR); | |
| if (!actionCell) return; | |
| const idInput = row.querySelector(CHECKBOX_SELECTOR); | |
| if (!idInput) return; | |
| const objInfoId = idInput.id.replace(CHECKBOX_ID_PREFIX, ''); | |
| const link = document.createElement('a'); | |
| link.href = '#'; | |
| link.className = QUICK_REMOVE_CLASS; | |
| link.textContent = QUICK_REMOVE_TEXT; | |
| link.style.cssText = LINK_STYLE; | |
| link.addEventListener('click', function (e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| handleQuickRemove(row, objInfoId); | |
| }); | |
| actionCell.appendChild(link); | |
| }); | |
| } | |
| function handleQuickRemove(row, objInfoId) { | |
| if (window.__sdbData && window.__sdbData.pinRequired) { | |
| alert('Quick Remove: PIN required on this account, use the normal Action dropdown.'); | |
| return; | |
| } | |
| if (!window.__sdbData || !window.__sdbData.refCk) { | |
| alert('Quick Remove: page not ready, try again.'); | |
| return; | |
| } | |
| const stepper = row.querySelector(STEPPER_SELECTOR); | |
| let qty = stepper ? parseInt(stepper.value, 10) : 0; | |
| if (!qty || qty <= 0) qty = 1; | |
| const link = row.querySelector('.' + QUICK_REMOVE_CLASS); | |
| link.textContent = MOVING_TEXT; | |
| fetch(MOVE_ITEMS_URL, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| 'x-requested-with': 'XMLHttpRequest', | |
| }, | |
| body: JSON.stringify({ | |
| moves: [{ obj_info_id: objInfoId, quantity: qty, action: MOVE_ACTION_INVENTORY }], | |
| pin: '', | |
| _ref_ck: window.__sdbData.refCk, | |
| }), | |
| }) | |
| .then(function (resp) { return resp.json(); }) | |
| .then(function (data) { | |
| if (data.success) { | |
| location.reload(); | |
| } else { | |
| link.textContent = QUICK_REMOVE_TEXT; | |
| alert('Quick Remove failed: ' + (data.message || data.error || 'unknown error')); | |
| } | |
| }) | |
| .catch(function (err) { | |
| link.textContent = QUICK_REMOVE_TEXT; | |
| alert('Quick Remove failed: network error.'); | |
| console.error('[Quick Remove]', err); | |
| }); | |
| } | |
| let debounceTimer = null; | |
| function scheduleInject() { | |
| if (debounceTimer) clearTimeout(debounceTimer); | |
| debounceTimer = setTimeout(injectButtons, INJECT_DEBOUNCE_MS); | |
| } | |
| const observerTarget = document.getElementById(OBSERVER_TARGET_ID) || document.body; | |
| const observer = new MutationObserver(scheduleInject); | |
| observer.observe(observerTarget, { childList: true, subtree: true }); | |
| injectButtons(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment