Quick Deposit cash to Ghost Trade / Company Vault / Faction Vault / Property Vault / Stocks, with GUI Settings
// ==UserScript==
// @name Torn Quick Deposit
// @namespace http://tampermonkey.net/
// @version 1.5.1
// @description Quick Deposit cash to Ghost Trade / Company Vault / Faction Vault / Property Vault / Stocks, with GUI Settings
// @author e7cf09 [3441977]
// @icon https://editor.torn.com/cd385b6f-7625-47bf-88d4-911ee9661b52-3441977.png
// @supportURL https://www.torn.com/forums.php#/p=threads&f=67&t=16530699
// @match https://www.torn.com/*
// @run-at document-start
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// --- CONFIGURATION MANAGEMENT ---
const STORAGE_KEY = 'torn_quick_deposit';
// SET THIS TO TRUE IF YOU HID THE SETTINGS BUTTON AND CAN'T ACCESS IT!
const I_MESSED_UP = false;
// Note: Changing these values here won't affect your active settings because they are saved in your browser.
// Please use the Settings button (gear icon) in the script to change options.
const DEFAULT_CONFIG = {
MOBILE_MODE: false,
HIDE_SETTINGS_BTN: false,
PANIC_THRESHOLD: 20000000,
STRICT_GHOST_MODE: true,
GHOST_KEYWORD: "ghost",
PRIORITY_HIGH: ["GHOST", "FACTION"],
PRIORITY_HIGH_DISABLED: ["STOCKS", "COMPANY", "PROPERTY"], // Disabled options specifically for High Priority
PRIORITY_LOW: ["FACTION", "GHOST"],
PRIORITY_LOW_DISABLED: ["STOCKS", "COMPANY", "PROPERTY"], // Disabled options specifically for Low Priority
STOCK_ACRONYM: "",
KEYS: {
FACTION: [],
GHOST: [],
COMPANY: [],
STOCKS: [],
PROPERTY: [],
RESET: [],
PANIC: ["KeyP"],
EXECUTE: [],
SETTINGS: ["KeyS"]
},
DEAD_SIGNALS: ["no trade was found", "trade has been accepted", "declined", "cancelled", "locked"]
};
let CONFIG = Object.assign({}, DEFAULT_CONFIG);
function loadConfig() {
try {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
const parsed = JSON.parse(saved);
// Merge saved config with default to ensure new keys exist
CONFIG = { ...DEFAULT_CONFIG, ...parsed, KEYS: { ...DEFAULT_CONFIG.KEYS, ...(parsed.KEYS || {}) } };
}
} catch (e) {
console.error("Torn Quick Deposit: Failed to load config", e);
}
}
function saveConfig() {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(CONFIG));
showToast("Settings Saved! Reloading...");
setTimeout(() => window.location.reload(), 1000);
} catch (e) {
console.error("Torn Quick Deposit: Failed to save config", e);
showToast("Error Saving Settings");
}
}
// Load immediately
loadConfig();
// --- APP STATE ---
const STATE = {
balance: 0,
panic: localStorage.getItem('torn_tactical_panic_enabled') !== 'false',
ghostID: localStorage.getItem('torn_tactical_ghost_id'),
locks: { panic: false, sending: false, jumping: false, dead: false },
els: { overlay: null, btn: null, settingsBtn: null }
};
// --- UTILS ---
const qs = (s, p=document) => p.querySelector(s);
const qsa = (s, p=document) => p.querySelectorAll(s);
const fmt = (n) => "$" + parseInt(n).toLocaleString('en-US');
const setStyle = (el, css) => Object.assign(el.style, css);
const STOCK_LIST = ["", "ASS", "BAG", "CNC", "EWM", "ELT", "EVL", "FHG", "GRN", "CBD", "HRG", "IIL", "IOU", "IST", "LAG", "LOS", "LSC", "MCS", "MSG", "MUN", "PRN", "PTS", "SYM", "SYS", "TCP", "TMI", "TGP", "TCT", "TSB", "TCC", "THS", "TCI", "TCM", "WSU", "WLT", "YAZ"];
// HELPER FOR ROBUST CLICKING
const robustClick = (element) => {
if (!element || element.disabled === true) return;
const rect = element.getBoundingClientRect();
const x = rect.left + (rect.width / 2);
const y = rect.top + (rect.height / 2);
['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click'].forEach(eventType => {
element.dispatchEvent(new MouseEvent(eventType, {
bubbles: true, cancelable: true, view: window,
detail: 1, screenX: x, screenY: y, clientX: x, clientY: y, buttons: 1
}));
});
};
// --- SETTINGS UI ---
function openSettingsModal() {
// Close if exists
if (document.getElementById('torn-deposit-settings-modal')) return;
// Deep copy config for editing to avoid mutating live config until save
const editConfig = JSON.parse(JSON.stringify(CONFIG));
const modal = document.createElement('div');
modal.id = 'torn-deposit-settings-modal';
setStyle(modal, {
position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
background: '#222', color: '#ddd', padding: '20px', borderRadius: '8px',
zIndex: '9999999', boxShadow: '0 0 20px rgba(0,0,0,0.8)',
width: '400px', maxHeight: '90vh', overflowY: 'auto',
fontFamily: 'Arial, sans-serif', fontSize: '13px', border: '1px solid #444'
});
const title = document.createElement('h3');
title.innerText = 'Deposit Settings';
title.style.marginTop = '0';
title.style.borderBottom = '1px solid #555';
title.style.paddingBottom = '10px';
title.style.textAlign = 'center';
modal.appendChild(title);
// --- Helper: Simple Inputs ---
const createInput = (label, type, key, description = "", options = null) => {
const div = document.createElement('div');
div.style.marginBottom = '15px';
div.style.borderBottom = '1px solid #333';
div.style.paddingBottom = '10px';
const lbl = document.createElement('label');
lbl.innerText = label;
lbl.style.display = 'block';
lbl.style.fontWeight = 'bold';
lbl.style.color = '#fff';
if (description) {
const desc = document.createElement('div');
desc.innerText = description;
desc.style.fontSize = '11px';
desc.style.color = '#888';
desc.style.marginBottom = '5px';
desc.style.fontStyle = 'italic';
div.appendChild(desc);
}
let input;
if (type === 'checkbox') {
input = document.createElement('input');
input.type = 'checkbox';
input.checked = editConfig[key];
input.style.float = 'right';
input.style.cursor = 'pointer';
input.onchange = (e) => { editConfig[key] = e.target.checked; };
lbl.appendChild(input);
} else if (type === 'select') {
input = document.createElement('select');
input.style.width = '100%';
input.style.background = '#333';
input.style.color = '#fff';
input.style.border = '1px solid #555';
input.style.padding = '5px';
(options || []).forEach(opt => {
const option = document.createElement('option');
option.value = opt;
option.innerText = opt;
if (opt === editConfig[key]) option.selected = true;
input.appendChild(option);
});
input.onchange = (e) => { editConfig[key] = e.target.value; };
} else {
input = document.createElement('input');
input.type = (type === 'number') ? 'text' : type; // Use text for numbers to remove spinners
if (type === 'number') {
input.inputMode = 'numeric';
input.pattern = '[0-9]*';
}
input.value = editConfig[key];
input.style.width = '96%';
input.style.background = '#333';
input.style.color = '#fff';
input.style.border = '1px solid #555';
input.style.padding = '5px';
input.onchange = (e) => {
if (type === 'number') {
const val = parseInt(e.target.value.replace(/[^0-9]/g, ''));
editConfig[key] = isNaN(val) ? 0 : val;
e.target.value = editConfig[key]; // Format back
} else {
editConfig[key] = e.target.value;
}
};
}
div.insertBefore(lbl, div.firstChild);
if (type !== 'checkbox') div.appendChild(input);
return div;
};
// --- Helper: Sortable List (DnD) ---
const createSortableList = (label, keyEnabled, description, keyDisabled) => {
const container = document.createElement('div');
container.style.marginBottom = '15px';
container.style.borderBottom = '1px solid #333';
container.style.paddingBottom = '10px';
const lbl = document.createElement('label');
lbl.innerText = label;
lbl.style.display = 'block';
lbl.style.fontWeight = 'bold';
lbl.style.color = '#fff';
container.appendChild(lbl);
if (description) {
const desc = document.createElement('div');
desc.innerText = description;
desc.style.fontSize = '11px';
desc.style.color = '#888';
desc.style.marginBottom = '5px';
desc.style.fontStyle = 'italic';
container.appendChild(desc);
}
// Create two lists: Enabled (top) and Disabled (bottom)
const createSubList = (listKey, title) => {
const subContainer = document.createElement('div');
subContainer.style.marginBottom = '10px';
const subTitle = document.createElement('div');
subTitle.innerText = title;
subTitle.style.fontSize = '12px';
subTitle.style.color = '#ccc';
subTitle.style.marginBottom = '3px';
subContainer.appendChild(subTitle);
const listDiv = document.createElement('div');
listDiv.id = 'list-' + listKey; // Unique ID for DnD
listDiv.style.background = title.includes('Disabled') ? '#2a2a2a' : '#333';
listDiv.style.padding = '5px';
listDiv.style.borderRadius = '4px';
listDiv.style.display = 'flex';
listDiv.style.flexDirection = 'column';
listDiv.style.gap = '4px';
listDiv.style.minHeight = '30px'; // Ensure empty list is droppable
subContainer.appendChild(listDiv);
// DnD Logic
listDiv.addEventListener('dragover', (e) => {
e.preventDefault();
const afterElement = getDragAfterElement(listDiv, e.clientY);
const draggingEl = document.querySelector('.dragging');
if (draggingEl) {
// Only allow dragging between related lists (Enabled <-> Disabled for THIS priority type)
const sourceListId = draggingEl.closest('[id^="list-"]').id;
const validSourceIds = ['list-' + keyEnabled, 'list-' + keyDisabled];
if (validSourceIds.includes(sourceListId)) {
if (afterElement == null) {
listDiv.appendChild(draggingEl);
} else {
listDiv.insertBefore(draggingEl, afterElement);
}
// Update config for both lists
[keyEnabled, keyDisabled].forEach(k => {
const el = document.getElementById('list-' + k);
if (el) {
const newOrder = [];
el.querySelectorAll('div.sortable-item').forEach(item => {
newOrder.push(item.dataset.value);
});
editConfig[k] = newOrder;
}
});
}
}
});
return { container: subContainer, listDiv: listDiv };
};
const enabledList = createSubList(keyEnabled, "Enabled (Ordered)");
const disabledList = createSubList(keyDisabled, "Disabled");
container.appendChild(enabledList.container);
container.appendChild(disabledList.container);
let draggingItem = null;
const getDragAfterElement = (container, y) => {
const draggableElements = [...container.querySelectorAll('div[draggable="true"]:not(.dragging)')];
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child };
} else {
return closest;
}
}, { offset: Number.NEGATIVE_INFINITY }).element;
};
const renderList = (listDiv, key) => {
listDiv.innerHTML = '';
if (!editConfig[key]) editConfig[key] = [];
editConfig[key].forEach((item) => {
const itemDiv = document.createElement('div');
itemDiv.innerText = item;
itemDiv.draggable = true;
itemDiv.dataset.value = item;
itemDiv.className = 'sortable-item';
const isEnabled = key === keyEnabled;
setStyle(itemDiv, {
background: isEnabled ? '#444' : '#3a3a3a',
color: isEnabled ? '#fff' : '#aaa',
padding: '8px',
borderRadius: '3px', cursor: 'grab',
border: '1px solid ' + (isEnabled ? '#555' : '#444'),
userSelect: 'none',
display: 'flex', justifyContent: 'space-between', alignItems: 'center'
});
const handle = document.createElement('span');
handle.innerHTML = '☰';
handle.style.color = '#888';
handle.style.cursor = 'grab';
itemDiv.appendChild(handle);
itemDiv.addEventListener('dragstart', (e) => {
draggingItem = itemDiv;
itemDiv.classList.add('dragging');
itemDiv.style.opacity = '0.5';
e.dataTransfer.effectAllowed = 'move';
});
itemDiv.addEventListener('dragend', () => {
itemDiv.classList.remove('dragging');
itemDiv.style.opacity = '1';
draggingItem = null;
// Update config (already handled in dragover, but good for safety)
[keyEnabled, keyDisabled].forEach(k => {
const el = document.getElementById('list-' + k);
if (el) {
const newOrder = [];
el.querySelectorAll('div.sortable-item').forEach(item => {
newOrder.push(item.dataset.value);
});
editConfig[k] = newOrder;
}
});
// Re-render to update styles if moved between lists
renderAll();
});
listDiv.appendChild(itemDiv);
});
};
const renderAll = () => {
renderList(enabledList.listDiv, keyEnabled);
renderList(disabledList.listDiv, keyDisabled);
};
renderAll();
return container;
};
// --- Helper: Interactive Keybinder ---
const createKeybinder = () => {
const container = document.createElement('div');
container.style.marginBottom = '15px';
const lbl = document.createElement('label');
lbl.innerText = "Keybindings";
lbl.style.display = 'block';
lbl.style.fontWeight = 'bold';
lbl.style.color = '#fff';
lbl.style.marginBottom = '5px';
container.appendChild(lbl);
const desc = document.createElement('div');
desc.innerText = "Click a button to bind a key. Press 'Backspace' to clear. 'Escape' to cancel.";
desc.style.fontSize = '11px';
desc.style.color = '#888';
desc.style.marginBottom = '10px';
desc.style.fontStyle = 'italic';
container.appendChild(desc);
const grid = document.createElement('div');
grid.style.display = 'grid';
grid.style.gridTemplateColumns = '1fr 1fr';
grid.style.gap = '8px';
container.appendChild(grid);
Object.keys(editConfig.KEYS).forEach(action => {
const row = document.createElement('div');
row.style.display = 'flex';
row.style.alignItems = 'center';
row.style.justifyContent = 'space-between';
row.style.background = '#333';
row.style.padding = '5px 10px';
row.style.borderRadius = '4px';
row.style.border = '1px solid #444';
const name = document.createElement('span');
name.innerText = action;
name.style.fontWeight = 'bold';
name.style.fontSize = '12px';
const btn = document.createElement('button');
const updateBtnText = () => {
const keys = editConfig.KEYS[action];
btn.innerText = (keys && keys.length) ? keys[0] : '[None]';
btn.style.background = (keys && keys.length) ? '#555' : '#222';
btn.style.color = (keys && keys.length) ? '#fff' : '#888';
};
updateBtnText();
setStyle(btn, {
border: '1px solid #666', padding: '4px 8px',
cursor: 'pointer', borderRadius: '3px', fontSize: '11px', minWidth: '80px',
textAlign: 'center'
});
btn.onclick = () => {
const originalText = btn.innerText;
btn.innerText = 'Press Key...';
btn.style.background = '#d32f2f'; // Red to indicate recording
btn.style.color = 'white';
const handler = (e) => {
e.preventDefault();
e.stopPropagation();
if (e.code === 'Escape') {
// Cancel, do nothing to config
} else if (e.code === 'Backspace' || e.code === 'Delete') {
editConfig.KEYS[action] = [];
} else {
editConfig.KEYS[action] = [e.code];
}
document.removeEventListener('keydown', handler, true);
updateBtnText();
};
document.addEventListener('keydown', handler, { once: true, capture: true });
};
row.appendChild(name);
row.appendChild(btn);
grid.appendChild(row);
});
return container;
};
// --- Build UI ---
modal.appendChild(createInput('Panic Threshold ($)', 'number', 'PANIC_THRESHOLD',
"Panic Button triggers if cash exceeds this amount."));
modal.appendChild(createInput('Mobile Panic Button', 'checkbox', 'MOBILE_MODE',
"Fixes the Panic Button to the bottom-center of the screen for easier mobile access."));
modal.appendChild(createInput('Hide Settings Button', 'checkbox', 'HIDE_SETTINGS_BTN',
"Hides the ⚙️ button from the sidebar. You can still open settings with the configured shortcut (Default: 's')."));
modal.appendChild(createInput('Strict Ghost Mode', 'checkbox', 'STRICT_GHOST_MODE',
"If enabled, only trades with the specified keyword (default: 'Ghost') in chat are used. If disabled, ANY trade you visit is saved."));
modal.appendChild(createInput('Ghost Keyword', 'text', 'GHOST_KEYWORD',
"The keyword to search for in trade (case-insensitive) when Strict Ghost Mode is enabled."));
modal.appendChild(createInput('Stock', 'select', 'STOCK_ACRONYM',
"The stock to buy when using Stocks deposit.", STOCK_LIST));
modal.appendChild(createSortableList('High Priority (Panic)', 'PRIORITY_HIGH',
"Drag to reorder. Priority when Cash >= Threshold.", 'PRIORITY_HIGH_DISABLED'));
modal.appendChild(createSortableList('Low Priority (Button)', 'PRIORITY_LOW',
"Drag to reorder. Priority when Cash < Threshold (Manual).", 'PRIORITY_LOW_DISABLED'));
modal.appendChild(createKeybinder());
// Footer Buttons
const btnDiv = document.createElement('div');
btnDiv.style.marginTop = '20px';
btnDiv.style.textAlign = 'right';
btnDiv.style.borderTop = '1px solid #444';
btnDiv.style.paddingTop = '10px';
const saveBtn = document.createElement('button');
saveBtn.innerText = 'Save & Reload';
setStyle(saveBtn, { background: '#4CAF50', color: 'white', border: 'none', padding: '8px 16px', cursor: 'pointer', borderRadius: '4px', fontWeight: 'bold' });
const cancelBtn = document.createElement('button');
cancelBtn.innerText = 'Cancel';
setStyle(cancelBtn, { background: '#666', color: 'white', border: 'none', padding: '8px 16px', cursor: 'pointer', marginRight: '10px', borderRadius: '4px' });
cancelBtn.onclick = () => modal.remove();
saveBtn.onclick = () => {
CONFIG = editConfig;
saveConfig();
modal.remove();
};
btnDiv.appendChild(cancelBtn);
btnDiv.appendChild(saveBtn);
modal.appendChild(btnDiv);
document.body.appendChild(modal);
}
// --- NETWORK INTERCEPT ---
const intercept = (proto, method, handler) => {
const orig = proto[method];
proto[method] = function(...args) {
handler(this, args);
return orig.apply(this, args);
};
};
intercept(XMLHttpRequest.prototype, 'open', (xhr, args) => xhr._url = args[1]);
intercept(XMLHttpRequest.prototype, 'send', (xhr) => {
xhr.addEventListener('load', () => {
if (xhr._url?.includes('trade.php')) checkDeadTrade(xhr.responseText, xhr._url);
});
});
const origFetch = window.fetch;
window.fetch = async (...args) => {
const res = await origFetch(...args);
const url = args[0]?.toString() || '';
if (url.match(/sidebar|user/) && res.headers.get('content-type')?.includes('json')) {
try {
res.clone().json().then(d => updateBalance(d?.user?.money || d?.sidebarData?.user?.money)).catch(()=>{});
} catch (e) {}
}
return res;
};
// --- LOGIC ---
function checkDeadTrade(text, url) {
if (!text || text.length < 20) return;
if (CONFIG.DEAD_SIGNALS.some(s => text.toLowerCase().includes(s))) {
let id = null;
if (url && url.includes('ID=')) {
const match = url.match(/ID=(\d+)/);
if (match) id = match[1];
} else {
const params = new URLSearchParams(window.location.hash.substring(1) || window.location.search);
id = params.get('ID');
}
if (STATE.ghostID && id === STATE.ghostID) {
localStorage.removeItem('torn_tactical_ghost_id');
STATE.ghostID = null;
injectBtn();
}
}
}
function getStatus() {
const icons = qsa('ul[class*="status-icons"] > li');
if (!icons.length) {
return { faction: true, ghost: true, company: false, stocks: true, property: true, reason: "LOADING" };
}
let s = { faction: true, ghost: true, company: false, stocks: true, property: true, reason: "" };
let isHosp = false, isTravel = false;
icons.forEach(li => {
if (li.className.match(/icon1[56]/)) isHosp = true;
if (li.className.includes('icon71')) isTravel = true;
if (li.className.includes('icon73')) s.company = true;
});
if (isHosp) { s.faction = s.company = s.stocks = s.property = false; s.reason = "HOSP/JAIL"; }
else if (isTravel) { s.faction = s.ghost = s.company = s.stocks = s.property = false; s.reason = "TRAVEL"; }
return s;
}
function updateBalance(val) {
if (!val) return;
const num = parseInt(typeof val === 'object' ? val.value : val);
if (isNaN(num)) return;
STATE.balance = num;
const status = getStatus();
// Check availability - Hide button if no options available (e.g. traveling)
const availableOptions = Object.values(status).filter(v => v === true).length;
if (STATE.els.btn) {
if (availableOptions === 0 && (status.reason === "HOSP/JAIL" || status.reason === "TRAVEL")) {
STATE.els.btn.style.display = 'none';
} else {
STATE.els.btn.style.display = 'inline-block';
}
}
// Check for Stocks Back button
const onStocks = window.location.href.includes('sid=stocks');
let hasBackBtn = false;
if (onStocks) {
const buyBlock = qs('.buyBlock___bIlBS');
if (buyBlock) {
const btns = buyBlock.querySelectorAll('button');
hasBackBtn = Array.from(btns).some(b => b.innerText.trim().toLowerCase() === 'back');
const confirmBtn = buyBlock.querySelector('button.buy___fcM6a');
const isConfirming = confirmBtn && confirmBtn.innerText.toLowerCase().includes('confirm');
if (isConfirming) hasBackBtn = false;
}
}
if (STATE.balance <= 0) {
STATE.locks.sending = false;
dismissPanic();
} else if (STATE.balance >= CONFIG.PANIC_THRESHOLD && STATE.panic) {
if (!STATE.locks.panic) triggerPanic();
else updateOverlay();
} else if (STATE.locks.panic) {
dismissPanic();
}
if (STATE.locks.panic) updateOverlay();
}
function triggerPanic() {
if (!STATE.panic || STATE.balance < CONFIG.PANIC_THRESHOLD) return;
const s = getStatus();
if (s.reason === 'LOADING') {
setTimeout(triggerPanic, 500);
return;
}
let available = false;
for (const type of CONFIG.PRIORITY_HIGH) {
if (type === 'GHOST' && STATE.ghostID && s.ghost) { available = true; break; }
if (type === 'COMPANY' && s.company) { available = true; break; }
if (type === 'FACTION' && s.faction) { available = true; break; }
if (type === 'STOCKS' && s.stocks) { available = true; break; }
if (type === 'PROPERTY' && s.property) { available = true; break; }
}
if (!available) return;
STATE.locks.panic = true;
updateOverlay();
}
function dismissPanic() {
STATE.locks.panic = false;
STATE.locks.sending = false;
if (STATE.els.overlay) STATE.els.overlay.style.display = 'none';
}
async function executeDeposit(mode = 'auto') {
const box = Array.from(qsa('.cash-confirm')).find(b => b.offsetParent && b.style.display !== 'none');
if (box) {
if (box.querySelector('.yes')) {
if (STATE.els.overlay) STATE.els.overlay.innerHTML = "DEPOSITING...";
box.querySelector('.yes').click();
STATE.locks.sending = false;
return;
}
}
if (!box && STATE.locks.sending) STATE.locks.sending = false;
if (STATE.locks.sending) return;
const status = getStatus();
const currentPriority = (STATE.balance >= CONFIG.PANIC_THRESHOLD) ? CONFIG.PRIORITY_HIGH : CONFIG.PRIORITY_LOW;
let target = mode;
if (mode === 'auto') {
for (const type of currentPriority) {
if (type === 'GHOST' && STATE.ghostID && status.ghost) { target = 'ghost'; break; }
if (type === 'COMPANY' && status.company) { target = 'company'; break; }
if (type === 'FACTION' && status.faction) { target = 'faction'; break; }
if (type === 'STOCKS' && status.stocks) { target = 'stocks'; break; }
if (type === 'PROPERTY' && status.property) { target = 'property'; break; }
}
if (!target) return;
}
if (target === 'ghost') {
if (!STATE.ghostID || !status.ghost) {
if (mode === 'ghost') return;
target = status.company ? 'company' : (status.faction ? 'faction' : (status.stocks ? 'stocks' : (status.property ? 'property' : null)));
}
}
if (target === 'company' && !status.company) {
if (mode !== 'auto') return;
target = status.faction ? 'faction' : (status.stocks ? 'stocks' : (status.property ? 'property' : null));
}
if (target === 'faction' && !status.faction) {
if (mode !== 'auto') return;
target = status.stocks ? 'stocks' : (status.property ? 'property' : null);
}
if (target === 'property' && !status.property) {
if (mode !== 'auto') return;
target = null;
}
if (!target) return;
const setVal = (input, val) => {
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
if (setter) setter.call(input, val);
else input.value = val;
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
input.dispatchEvent(new Event('blur', { bubbles: true }));
};
const jump = (url, msg) => {
if (STATE.locks.jumping) return;
STATE.locks.jumping = true;
if (STATE.els.overlay) STATE.els.overlay.innerHTML = msg;
setTimeout(() => STATE.locks.jumping = false, 1500);
window.location.href = url;
};
if (target === 'ghost') {
const url = window.location.href;
const onPageWithID = url.includes('trade.php') && url.includes(STATE.ghostID);
const input = qs('.input-money[type="text"]');
if (!onPageWithID || !input) {
return jump(`https://www.torn.com/trade.php#step=addmoney&ID=${STATE.ghostID}`, "JUMPING<br>TO GHOST");
}
STATE.locks.sending = true;
if (STATE.els.overlay) STATE.els.overlay.innerHTML = "DEPOSITING<br>TO GHOST";
setVal(input, input.getAttribute('data-money') || STATE.balance);
const btn = input.form?.querySelector('input[type="submit"], button[type="submit"]') || qs('input[type="submit"][value="Change"], button[type="submit"][value="Change"]');
if (btn) {
btn.disabled = false;
btn.classList.remove('disabled');
btn.click();
STATE.locks.jumping = false;
} else STATE.locks.sending = false;
} else if (target === 'company') {
const url = window.location.href;
const onPage = url.includes('companies.php') && url.includes('option=funds');
const input = qs('input[aria-labelledby="deposit-label"][type="text"]');
if (!input) {
if (!onPage) return jump(`https://www.torn.com/companies.php?step=your&type=1#/option=funds`, "JUMPING<br>TO COMPANY");
return;
}
STATE.locks.sending = true;
if (STATE.els.overlay) STATE.els.overlay.innerHTML = "DEPOSITING<br>TO COMPANY";
setVal(input, input.getAttribute('data-money') || STATE.balance);
const container = input.closest('.funds-cont');
const btn = container ? container.querySelector('.deposit.btn-wrap button') : null;
if (btn) {
btn.disabled = false;
btn.click();
} else STATE.locks.sending = false;
} else if (target === 'stocks') {
const url = window.location.href;
const onPage = url.includes('page.php') && url.includes('sid=stocks');
const acronym = CONFIG.STOCK_ACRONYM || "EVL";
const stockRow = Array.from(qsa('li.stockName___tLa3y')).find(li => li.querySelector(`span[data-acronym="${acronym}"]`));
if (!stockRow) {
if (!onPage) return jump(`https://www.torn.com/page.php?sid=stocks`, "JUMPING<br>TO STOCKS");
return;
}
const parentUl = stockRow.closest('ul.stock___ElSDB');
if (!parentUl) return;
const ownedTab = parentUl.querySelector('li.stockOwned___eXJed');
if (!ownedTab) return;
const expanded = ownedTab.getAttribute('aria-expanded') === 'true' || ownedTab.classList.contains('active___IUYLC');
if (!expanded) {
if (STATE.locks.sending) return;
STATE.locks.sending = true;
ownedTab.click();
setTimeout(() => STATE.locks.sending = false, 500);
return;
}
let visibleBuyBlock = null;
const panelId = ownedTab.getAttribute('aria-controls');
if (panelId) {
const panel = document.getElementById(panelId);
if (panel && panel.offsetParent !== null) visibleBuyBlock = panel.querySelector('.buyBlock___bIlBS');
}
if (!visibleBuyBlock && parentUl) {
let next = parentUl.nextElementSibling;
for (let i = 0; i < 3 && next; i++) {
if (next.classList.contains('stockDropdown___Y2X_v')) {
if (next.offsetParent !== null) visibleBuyBlock = next.querySelector('.buyBlock___bIlBS');
if (visibleBuyBlock) break;
}
next = next.nextElementSibling;
}
}
if (!visibleBuyBlock) {
let next = ownedTab.nextElementSibling;
for (let i = 0; i < 3 && next; i++) {
if (next.classList.contains('stockDropdown___Y2X_v') || next.querySelector('.buyBlock___bIlBS')) {
if (next.offsetParent !== null) visibleBuyBlock = next.querySelector('.buyBlock___bIlBS');
if (visibleBuyBlock) break;
}
next = next.nextElementSibling;
}
}
if (!visibleBuyBlock) {
if (STATE.els.overlay) STATE.els.overlay.innerHTML = "OPENING...";
return;
}
const backBtn = Array.from(visibleBuyBlock.querySelectorAll('button')).find(b => b.innerText.trim().toLowerCase() === 'back');
const confirmBtn = visibleBuyBlock.querySelector('button.buy___fcM6a');
const isConfirming = confirmBtn && confirmBtn.innerText.toLowerCase().includes('confirm');
if (backBtn && !isConfirming) {
if (STATE.balance <= 0) {
dismissPanic();
return;
}
STATE.locks.sending = true;
if (STATE.els.overlay) STATE.els.overlay.innerHTML = "CONFIRMED";
robustClick(backBtn);
setTimeout(() => STATE.locks.sending = false, 200);
return;
}
const currentBtn = visibleBuyBlock.querySelector('button.buy___fcM6a');
if (currentBtn) {
const text = (currentBtn.innerText || currentBtn.textContent).trim();
if (text.toLowerCase().includes("price updating")) {
if (STATE.els.overlay) STATE.els.overlay.innerHTML = "UPDATING...";
return;
}
if (text.toLowerCase().includes("confirm")) {
STATE.locks.sending = true;
if (STATE.els.overlay) STATE.els.overlay.innerHTML = "CONFIRMING...";
robustClick(currentBtn);
return;
}
}
const maxBtn = visibleBuyBlock.querySelector('.wai-btn') || visibleBuyBlock.querySelector('.input-money-symbol');
const buyInput = visibleBuyBlock.querySelector('input.input-money[type="text"]') || visibleBuyBlock.querySelector('input.input-money:not([type="hidden"])');
if (!currentBtn) return;
STATE.locks.sending = true;
if (STATE.els.overlay) STATE.els.overlay.innerHTML = `BUYING<br>${acronym}`;
if (maxBtn) {
maxBtn.click();
} else if (buyInput) {
const maxShares = buyInput.getAttribute('data-money');
if (maxShares) setVal(buyInput, maxShares);
}
robustClick(currentBtn);
setTimeout(() => STATE.locks.sending = false, 200);
return;
} else if (target === 'property') {
const url = window.location.href;
const onPage = url.includes('properties.php') && url.includes('tab=vault');
if (!onPage) {
if (url.includes('properties.php')) return jump(url.split('#')[0] + '#/p=options&tab=vault', "JUMPING<br>TO PROPERTY");
return jump(`https://www.torn.com/properties.php#/p=options&tab=vault`, "JUMPING<br>TO PROPERTY");
}
const inputs = Array.from(qsa('input.input-money'));
let input = null;
for (const inp of inputs) {
const form = inp.closest('form');
if (form && form.innerText.toLowerCase().includes('deposit')) {
input = inp;
break;
}
const container = inp.closest('div');
if (container && container.parentElement.innerText.toLowerCase().includes('deposit')) {
input = inp;
break;
}
}
if (!input && inputs.length > 0) input = inputs[0];
if (!input) return;
STATE.locks.sending = true;
if (STATE.els.overlay) STATE.els.overlay.innerHTML = "DEPOSITING<br>TO PROPERTY";
setVal(input, STATE.balance);
const btn = input.form?.querySelector('button, input[type="submit"]') || input.closest('div').parentElement.querySelector('button');
if (btn) {
btn.disabled = false;
btn.click();
} else STATE.locks.sending = false;
} else { // Faction
const form = qs('.give-money-form') || qs('.input-money')?.closest('form');
const currentUrl = window.location.href;
const isFactionArmoury = currentUrl.includes('factions.php') && currentUrl.includes('step=your') && currentUrl.includes('tab=armoury');
const isDonateSub = currentUrl.includes('sub=donate');
if (!form) {
if (!isFactionArmoury || !isDonateSub) {
return jump(`https://www.torn.com/factions.php?step=your#/tab=armoury&sub=donate`, "JUMPING<br>TO FACTION");
}
return;
}
const input = form.querySelector('.input-money');
if (!input) return;
STATE.locks.sending = true;
if (STATE.els.overlay) STATE.els.overlay.innerHTML = "DEPOSITING<br>TO FACTION";
let amt = input.getAttribute('data-money');
if (!amt) {
const txt = form.querySelector('.i-have')?.innerText.replace(/[$,]/g, '');
if (txt && !isNaN(txt)) amt = txt;
}
setVal(input, amt || STATE.balance);
const btn = form.querySelector('button.torn-btn');
if (btn) {
btn.disabled = false;
btn.click();
} else STATE.locks.sending = false;
}
}
// UI COMPONENTS
function showToast(html) {
const t = document.createElement('div');
t.innerHTML = `<div style="font-size:11px;color:#aaa;margin-bottom:4px;text-transform:uppercase;">Quick Deposit</div>${html}`;
setStyle(t, {
position: 'fixed', top: '15%', left: '50%', transform: 'translate(-50%, -50%)',
zIndex: 2147483647, background: 'rgba(0,0,0,0.85)', color: 'white',
padding: '12px 24px', borderRadius: '8px', pointerEvents: 'none',
opacity: 0, transition: 'opacity 0.3s', textAlign: 'center', border: '1px solid #ffffff33'
});
document.body.appendChild(t);
requestAnimationFrame(() => t.style.opacity = '1');
setTimeout(() => { t.style.opacity = '0'; setTimeout(() => t.remove(), 300); }, 2000);
}
function updateOverlay() {
if (!STATE.locks.panic || STATE.balance <= 0) return dismissPanic();
if (!STATE.els.overlay) {
const d = document.createElement('div');
d.id = 'torn-panic-overlay';
let css = `position: fixed; z-index: 2147483647; background: rgba(255, 0, 0, 0.9); color: white;
font-family: 'Arial Black', sans-serif; font-size: 14px; text-align: center;
padding: 10px 20px; border-radius: 30px; border: 3px solid white;
box-shadow: 0 0 20px red; cursor: pointer; white-space: nowrap; user-select: none;`;
if (CONFIG.MOBILE_MODE) {
css += `bottom: 80px; left: 50%; transform: translateX(-50%);`;
} else {
css += `top: -999px; left: -999px; transform: translate(-50%, -50%);`;
}
d.style.cssText = css;
d.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); executeDeposit('auto'); });
if (!CONFIG.MOBILE_MODE) {
document.addEventListener('mousemove', e => {
if (STATE.locks.panic) {
d.style.left = e.clientX + 'px';
d.style.top = e.clientY + 'px';
}
});
}
document.body.appendChild(d);
STATE.els.overlay = d;
}
STATE.els.overlay.style.display = 'block';
if (STATE.locks.sending || STATE.locks.jumping) return;
let txt = "JUMP TO<br>FACTION";
if (qs('.cash-confirm')) {
txt = "CONFIRM<br>DEPOSIT";
} else {
const s = getStatus();
let target = null;
const currentPriority = (STATE.balance >= CONFIG.PANIC_THRESHOLD) ? CONFIG.PRIORITY_HIGH : CONFIG.PRIORITY_LOW;
for (const type of currentPriority) {
if (type === 'GHOST' && STATE.ghostID && s.ghost) { target = 'GHOST'; break; }
if (type === 'COMPANY' && s.company) { target = 'COMPANY'; break; }
if (type === 'FACTION' && s.faction) { target = 'FACTION'; break; }
if (type === 'STOCKS' && s.stocks) { target = 'STOCKS'; break; }
if (type === 'PROPERTY' && s.property) { target = 'PROPERTY'; break; }
}
if (target === 'GHOST') {
const hashParams = new URLSearchParams(window.location.hash.substring(1));
const searchParams = new URLSearchParams(window.location.search);
const currentStep = hashParams.get('step') || searchParams.get('step');
const currentID = hashParams.get('ID') || searchParams.get('ID');
const onGhostPage = window.location.href.includes('trade.php') && currentStep === 'addmoney' && currentID === STATE.ghostID;
txt = onGhostPage ? `DEPOSIT<br>${fmt(STATE.balance)}<br><span style='font-size:10px'>GHOST</span>` : "JUMP TO<br>GHOST";
} else if (target === 'COMPANY') {
const onCompanyPage = window.location.href.includes('companies.php') && window.location.href.includes('option=funds');
const container = qs('.funds-cont');
const ready = onCompanyPage && (container || document.readyState === 'loading');
txt = ready ? `DEPOSIT<br>${fmt(STATE.balance)}<br><span style='font-size:10px'>COMPANY</span>` : "JUMP TO<br>COMPANY";
} else if (target === 'STOCKS') {
const onStocksPage = window.location.href.includes('page.php') && window.location.href.includes('sid=stocks');
const acronym = CONFIG.STOCK_ACRONYM || "EVL";
const stockRow = onStocksPage ? Array.from(qsa('li.stockName___tLa3y')).find(li => li.querySelector(`span[data-acronym="${acronym}"]`)) : null;
const ready = onStocksPage;
if (ready) {
if (stockRow) {
const buyBlock = qs('.buyBlock___bIlBS');
let isBack = false;
let isConfirm = false;
if (buyBlock) {
const btns = buyBlock.querySelectorAll('button');
isBack = Array.from(btns).some(b => b.innerText.trim().toLowerCase() === 'back');
const confirmBtn = buyBlock.querySelector('button.buy___fcM6a');
isConfirm = confirmBtn && confirmBtn.innerText.toLowerCase().includes('confirm');
}
if (isConfirm) {
txt = "CONFIRM<br>STOCKS";
} else {
txt = (isBack && !isConfirm) ? "CONFIRMED" : `BUY ${acronym}<br>${fmt(STATE.balance)}`;
}
} else {
txt = `LOADING<br>STOCKS`;
}
} else {
txt = "JUMP TO<br>STOCKS";
}
} else if (target === 'PROPERTY') {
const onPropertyPage = window.location.href.includes('properties.php') && window.location.href.includes('tab=vault');
const hasInput = !!qs('input.input-money');
txt = (onPropertyPage && hasInput) ? `DEPOSIT<br>${fmt(STATE.balance)}<br><span style='font-size:10px'>PROPERTY</span>` : "JUMP TO<br>PROPERTY";
} else {
const current = window.location.href;
const onFactionPage = current.includes('factions.php') && current.includes('step=your') && current.includes('tab=armoury') && current.includes('sub=donate');
const form = qs('.give-money-form');
const ready = form || onFactionPage;
txt = ready ? `DEPOSIT<br>${fmt(STATE.balance)}` : "JUMP TO<br>FACTION";
}
}
if (STATE.els.overlay.innerHTML !== txt) STATE.els.overlay.innerHTML = txt;
}
function injectBtn() {
const moneyEl = document.getElementById('user-money');
if (!moneyEl) return;
// Cleanup
const existing = document.querySelectorAll('#torn-tactical-deposit');
existing.forEach(el => { if (el !== STATE.els.btn) el.remove(); });
const existingSet = document.querySelectorAll('#torn-tactical-settings');
existingSet.forEach(el => { if (el !== STATE.els.settingsBtn) el.remove(); });
if (!STATE.els.btn) {
const b = document.createElement('a');
b.id = 'torn-tactical-deposit';
b.href = '#';
b.style.marginLeft = '5px';
b.style.cursor = 'pointer';
b.className = 'use___wM1PI';
b.addEventListener('click', (e) => { e.preventDefault(); executeDeposit('auto'); });
STATE.els.btn = b;
}
if (!STATE.els.settingsBtn) {
const s = document.createElement('a');
s.id = 'torn-tactical-settings';
s.href = '#';
s.innerHTML = '⚙️';
s.style.marginLeft = '10px';
s.style.cursor = 'pointer';
s.style.fontSize = '12px';
s.style.textDecoration = 'none';
s.title = 'Quick Deposit Settings';
s.addEventListener('click', (e) => {
e.preventDefault();
const m = document.getElementById('torn-deposit-settings-modal');
if (m) m.remove();
else openSettingsModal();
});
STATE.els.settingsBtn = s;
}
if (moneyEl.parentNode) {
// 1. Handle Settings Button
const showSettings = !CONFIG.HIDE_SETTINGS_BTN || I_MESSED_UP;
if (showSettings) {
// Ensure settingsBtn is strictly after moneyEl
if (moneyEl.nextSibling !== STATE.els.settingsBtn) {
moneyEl.parentNode.insertBefore(STATE.els.settingsBtn, moneyEl.nextSibling);
}
} else {
if (STATE.els.settingsBtn.parentNode) STATE.els.settingsBtn.remove();
}
// 2. Handle Deposit Button
// It should be after the "Anchor" (which is settingsBtn if visible, else moneyEl)
const anchor = (showSettings && STATE.els.settingsBtn.parentNode) ? STATE.els.settingsBtn : moneyEl;
// We want STATE.els.btn to be immediately after anchor.
if (anchor.nextSibling !== STATE.els.btn) {
anchor.parentNode.insertBefore(STATE.els.btn, anchor.nextSibling);
}
}
const s = getStatus();
let txt = '[deposit]', title = 'Vault Deposit';
const currentPriority = (STATE.balance >= CONFIG.PANIC_THRESHOLD) ? CONFIG.PRIORITY_HIGH : CONFIG.PRIORITY_LOW;
for (const type of currentPriority) {
if (type === 'GHOST' && STATE.ghostID) { txt = '[ghost]'; title = `Ghost ID: ${STATE.ghostID}`; break; }
if (type === 'COMPANY' && s.company) { txt = '[company]'; title = "Company Vault"; break; }
if (type === 'FACTION' && s.faction) { txt = '[deposit]'; title = "Faction Vault"; break; }
if (type === 'STOCKS' && s.stocks) { txt = '[stocks]'; title = `Buy ${CONFIG.STOCK_ACRONYM || "EVL"} Stocks`; break; }
if (type === 'PROPERTY' && s.property) { txt = '[property]'; title = "Property Vault"; break; }
}
if (STATE.els.btn.innerText !== txt) STATE.els.btn.innerText = txt;
if (STATE.els.btn.title !== title) STATE.els.btn.title = title;
}
// EVENT LISTENERS
window.addEventListener('keydown', e => {
if (!e.isTrusted || e.target.matches('input, textarea') || e.target.isContentEditable) return;
const k = e.code;
const isKey = (type) => CONFIG.KEYS[type] && CONFIG.KEYS[type].includes(k);
if (Object.values(CONFIG.KEYS).flat().includes(k)) e.preventDefault();
if (isKey('FACTION')) executeDeposit('faction');
if (isKey('GHOST') && STATE.ghostID) executeDeposit('ghost');
if (isKey('COMPANY')) executeDeposit('company');
if (isKey('STOCKS')) executeDeposit('stocks');
if (isKey('PROPERTY')) executeDeposit('property');
if (isKey('EXECUTE')) executeDeposit('auto');
if (isKey('SETTINGS')) {
const m = document.getElementById('torn-deposit-settings-modal');
if (m) m.remove();
else openSettingsModal();
}
if (isKey('RESET')) {
localStorage.removeItem('torn_tactical_ghost_id');
STATE.ghostID = null;
injectBtn();
showToast("GHOST ID CLEARED");
}
if (isKey('PANIC')) {
STATE.panic = !STATE.panic;
localStorage.setItem('torn_tactical_panic_enabled', STATE.panic);
showToast(`PANIC MODE <span style="color:${STATE.panic?'#4dff4d':'#ff4d4d'}">${STATE.panic?'ON':'OFF'}</span>`);
if (STATE.panic) updateBalance(STATE.balance);
else dismissPanic();
}
});
window.addEventListener('hashchange', () => {
if (STATE.locks.jumping && STATE.ghostID && window.location.href.includes(STATE.ghostID)) STATE.locks.jumping = false;
if (STATE.locks.panic) updateOverlay();
});
const scanTrades = () => {
if (!window.location.href.includes('trade.php')) return;
const params = new URLSearchParams(window.location.hash.substring(1) || window.location.search);
const currentID = params.get('ID');
if (qs('.info-msg, .error-msg')?.innerText.match(new RegExp(CONFIG.DEAD_SIGNALS.join('|'), 'i'))) {
if (STATE.ghostID && currentID === STATE.ghostID) {
STATE.ghostID = null; localStorage.removeItem('torn_tactical_ghost_id'); injectBtn();
}
return;
}
if (currentID) {
const logs = qsa('ul.log .msg');
const keyword = String(CONFIG.GHOST_KEYWORD || "ghost").toLowerCase();
let isGhost = false;
if (!CONFIG.STRICT_GHOST_MODE) {
isGhost = true;
} else {
isGhost = Array.from(logs).some(msg => {
const clone = msg.cloneNode(true);
clone.querySelector('a')?.remove();
return clone.innerText.toLowerCase().includes(keyword);
});
}
if (isGhost && !STATE.ghostID) {
STATE.ghostID = currentID;
localStorage.setItem('torn_tactical_ghost_id', currentID);
injectBtn();
if (STATE.panic && STATE.balance >= CONFIG.PANIC_THRESHOLD) triggerPanic();
}
}
if (!currentID && !STATE.ghostID) {
const keyword = String(CONFIG.GHOST_KEYWORD || "ghost").toLowerCase();
qsa('ul.trade-list-container > li').forEach(li => {
const clone = li.cloneNode(true);
clone.querySelector('.user.name')?.remove();
if (!CONFIG.STRICT_GHOST_MODE || clone.innerText.toLowerCase().includes(keyword)) {
const mid = li.querySelector('a.btn-wrap')?.href.match(/ID=(\d+)/);
if (mid) {
STATE.ghostID = mid[1];
localStorage.setItem('torn_tactical_ghost_id', mid[1]);
injectBtn();
if (STATE.panic && STATE.balance >= CONFIG.PANIC_THRESHOLD) triggerPanic();
}
}
});
}
};
let mutationTimeout = null;
const observerCallback = (mutations) => {
let ignore = true;
for (const m of mutations) {
const isElement = m.target.nodeType === 1;
const targetId = isElement ? m.target.id : '';
if (isElement && (targetId === 'user-money' || m.target.closest?.('#user-money'))) {
const moneyEl = document.getElementById('user-money');
if (moneyEl) {
const val = moneyEl.getAttribute('data-money') || moneyEl.innerText.replace(/[^\d]/g, '');
if (val) updateBalance(val);
}
ignore = false;
}
if (isElement && (m.target.closest?.('ul[class*="status-icons"]'))) {
if (STATE.panic && STATE.balance >= CONFIG.PANIC_THRESHOLD) {
triggerPanic();
}
ignore = false;
}
if (!targetId?.includes('torn-') &&
(!isElement || !m.target.closest?.('#torn-tactical-deposit')) &&
(!isElement || !m.target.closest?.('#torn-panic-overlay')) &&
(!isElement || !m.target.closest?.('#torn-deposit-settings-modal')) && // Ignore settings modal
(!isElement || !m.target.closest?.('#torn-tactical-settings'))) {
ignore = false;
break;
}
}
if (ignore) return;
if (mutationTimeout) clearTimeout(mutationTimeout);
mutationTimeout = setTimeout(() => {
injectBtn();
scanTrades();
const moneyEl = document.getElementById('user-money');
if (moneyEl) {
const val = moneyEl.getAttribute('data-money') || moneyEl.innerText.replace(/[^\d]/g, '');
if (val) updateBalance(val);
}
if (STATE.panic && STATE.balance >= CONFIG.PANIC_THRESHOLD) triggerPanic();
}, 50);
};
let init = false;
const start = () => {
if (init) return;
init = true;
const moneyEl = document.getElementById('user-money');
if (moneyEl) {
const val = moneyEl.getAttribute('data-money') || moneyEl.innerText.replace(/[^\d]/g, '');
if (val) updateBalance(val);
}
new MutationObserver(observerCallback).observe(document, { childList: true, subtree: true });
if (moneyEl) injectBtn();
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', start);
} else start();
})();