Skip to content

Instantly share code, notes, and snippets.

@diceroll123
Last active July 25, 2026 18:20
Show Gist options
  • Select an option

  • Save diceroll123/e01aeb97cc22ac11f3a296f15c3e9170 to your computer and use it in GitHub Desktop.

Select an option

Save diceroll123/e01aeb97cc22ac11f3a296f15c3e9170 to your computer and use it in GitHub Desktop.
// ==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