您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically keeps track of your vault transactions when vault sharing
// ==UserScript== // @name Vault Sharing - Mobile Enhanced // @namespace vault.sharing.mobile // @version 2.1 // @description Automatically keeps track of your vault transactions when vault sharing // @author ANITABURN // @match https://www.torn.com/properties.php* // @match https://www.torn.com/properties.php // @grant none // @run-at document-end // @license MIT // ==/UserScript== (function() { 'use strict'; const isMobile = /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile/i.test(navigator.userAgent) || window.innerWidth <= 768 || ('ontouchstart' in window) || navigator.maxTouchPoints > 0; let localStorageKey = "vault_sharing_mobile:settings"; let playerName = ""; window.addEventListener("hashchange", (event) => { if (event.newURL.indexOf("vault") > -1) { setTimeout(startVaultSharing, 1000); } }, false); if (window.location.href.indexOf("vault") > -1) { setTimeout(startVaultSharing, 1000); } function startVaultSharing() { console.log('Starting vault sharing on mobile:', isMobile); try { let wsData = document.querySelector("#websocketConnectionData"); if (wsData) { playerName = JSON.parse(wsData.innerText).playername; } else { let userLinks = document.querySelectorAll('a[href*="/profiles.php?XID="]'); if (userLinks.length > 0) { playerName = userLinks[0].textContent.trim(); } } } catch (e) { console.log('Could not get player name:', e); playerName = "You"; } console.log('Player name:', playerName); let { startTime, ownStartingBalance, spouseStartingBalance, playerDisplayName, spouseDisplayName } = JSON.parse(localStorage.getItem(localStorageKey)) || { startTime: new Date().toISOString().slice(0, 16), ownStartingBalance: 0, spouseStartingBalance: 0, playerDisplayName: playerName || "You", spouseDisplayName: "Spouse" }; let isDarkMode = true; function parseTransaction(transactionItem) { try { let dateEl = transactionItem.querySelector(".transaction-date") || transactionItem.querySelector('[class*="date"]'); let timeEl = transactionItem.querySelector(".transaction-time") || transactionItem.querySelector('[class*="time"]'); let userEl = transactionItem.querySelector(".user.name") || transactionItem.querySelector('a[href*="XID="]'); let typeEl = transactionItem.querySelector(".type") || transactionItem.querySelector('[class*="type"]'); let amountEl = transactionItem.querySelector(".amount") || transactionItem.querySelector('[class*="amount"]'); let balanceEl = transactionItem.querySelector(".balance") || transactionItem.querySelector('[class*="balance"]'); if (!dateEl || !timeEl || !userEl || !typeEl || !amountEl) { return null; } let date = dateEl.innerText.trim().split("/"); let time = timeEl.innerText.trim(); let datetime = new Date(Date.parse(`20${date[2]}-${date[1]}-${date[0]}T${time}Z`)); let userName = userEl.title ? userEl.title.split(" ")[0]: userEl.textContent.trim(); let type = typeEl.innerText.replace(/[^A-z]/g, ""); let amount = parseInt(amountEl.innerText.replace(/[^0-9]/g, "")); let balance = balanceEl ? parseInt(balanceEl.innerText.replace(/[^0-9]/g, "")): 0; return { datetime: datetime, name: userName, type: type, amount: amount, originalBalance: balance }; } catch (e) { console.log('Error parsing transaction:', e); return null; } } function readTransactionData() { let transactionData = {}; let selectors = [ ".vault-trans-wrap ul li[transaction_id]", ".vault-trans-wrap li", '[class*="vault"] li', '[class*="transaction"]' ]; let transactionListItems = []; for (let selector of selectors) { transactionListItems = document.querySelectorAll(selector); if (transactionListItems.length > 0) break; } console.log(`Found ${transactionListItems.length} transaction items`); for (let item of transactionListItems) { let transactionId = item.getAttribute("transaction_id") || item.getAttribute("data-id") || Date.now() + Math.random(); let parsedTransaction = parseTransaction(item); if (parsedTransaction) { transactionData[transactionId] = parsedTransaction; } } return transactionData; } function formatBalance(balance) { return (balance < 0 ? "-": "") + "$" + Math.abs(balance).toLocaleString(); } function showBalances(ownBalance, spouseBalance) { let ownEl = document.getElementById("vault-sharing-own-balance"); let spouseEl = document.getElementById("vault-sharing-spouse-balance"); let totalEl = document.getElementById("vault-sharing-total-balance"); if (ownEl) ownEl.innerText = formatBalance(ownBalance); if (spouseEl) spouseEl.innerText = formatBalance(spouseBalance); if (totalEl) totalEl.innerText = formatBalance(ownBalance + spouseBalance); } function calculateBalances() { let transactionData = readTransactionData(); let startTimeAsDate = new Date(Date.parse(startTime + "Z")); let ownBalance = ownStartingBalance; let spouseBalance = spouseStartingBalance; console.log('Calculating balances from:', startTimeAsDate); console.log('Starting balances - Own:', ownBalance, 'Spouse:', spouseBalance); let relevantTransactions = Object.entries(transactionData) .filter(e => e[1].datetime > startTimeAsDate) .sort((a, b) => a[1].datetime - b[1].datetime); console.log(`Processing ${relevantTransactions.length} relevant transactions`); for (let [id, transaction] of relevantTransactions) { let amount = parseInt(transaction.type === "Deposit" ? transaction.amount: -transaction.amount); if (transaction.name === playerName || transaction.name.toLowerCase().includes(playerName.toLowerCase())) { ownBalance += amount; console.log(`${transaction.type} by ${transaction.name}: ${amount}, new balance: ${ownBalance}`); } else { spouseBalance += amount; console.log(`${transaction.type} by ${transaction.name}: ${amount}, spouse balance: ${spouseBalance}`); } } return { ownBalance, spouseBalance }; } function calculateAndShowBalances() { let result = calculateBalances(); showBalances(result.ownBalance, result.spouseBalance); } function saveSettings() { let ownBalanceInput = document.getElementById("vault-sharing-own-start-balance"); let spouseBalanceInput = document.getElementById("vault-sharing-spouse-start-balance"); let startTimeInput = document.getElementById("vault-sharing-start-time"); let playerNameInput = document.getElementById("vault-sharing-player-name"); let spouseNameInput = document.getElementById("vault-sharing-spouse-name"); if (!ownBalanceInput || !spouseBalanceInput || !startTimeInput) return; let ownBalanceSetting = Number(ownBalanceInput.value.replace(/[^0-9]/g, "")); let spouseBalanceSetting = Number(spouseBalanceInput.value.replace(/[^0-9]/g, "")); let startTimeSetting = startTimeInput.value; let playerNameSetting = playerNameInput ? playerNameInput.value.trim(): playerDisplayName; let spouseNameSetting = spouseNameInput ? spouseNameInput.value.trim(): spouseDisplayName; localStorage.setItem(localStorageKey, JSON.stringify({ startTime: startTimeSetting, ownStartingBalance: ownBalanceSetting, spouseStartingBalance: spouseBalanceSetting, playerDisplayName: playerNameSetting, spouseDisplayName: spouseNameSetting })); startTime = startTimeSetting; ownStartingBalance = ownBalanceSetting; spouseStartingBalance = spouseBalanceSetting; playerDisplayName = playerNameSetting; spouseDisplayName = spouseNameSetting; toggleEditMode(false); showStatus('Settings saved!', false); } function handleSave() { saveSettings(); calculateAndShowBalances(); } function handleSetCurrent() { let result = calculateBalances(); let ownBalanceInput = document.getElementById("vault-sharing-own-start-balance"); let spouseBalanceInput = document.getElementById("vault-sharing-spouse-start-balance"); let startTimeInput = document.getElementById("vault-sharing-start-time"); if (ownBalanceInput) ownBalanceInput.value = result.ownBalance.toLocaleString(); if (spouseBalanceInput) spouseBalanceInput.value = result.spouseBalance.toLocaleString(); if (startTimeInput) startTimeInput.value = new Date().toISOString().slice(0, 16); showStatus('Updated to current values!', false); } function toggleEditMode(forceState = null) { let editSection = document.getElementById("vault-sharing-edit-section"); let editButton = document.getElementById("vault-sharing-edit-btn"); let playerNameEl = document.getElementById("vault-sharing-player-display-name"); let spouseNameEl = document.getElementById("vault-sharing-spouse-display-name"); let currentlyEditing = editSection && editSection.style.display !== 'none'; let shouldEdit = forceState !== null ? forceState: !currentlyEditing; if (editSection) { editSection.style.display = shouldEdit ? 'block': 'none'; } if (editButton) { editButton.textContent = shouldEdit ? 'Cancel': 'Edit'; editButton.style.background = shouldEdit ? '#68acff': '#68ebff'; } if (playerNameEl) playerNameEl.textContent = playerDisplayName; if (spouseNameEl) spouseNameEl.textContent = spouseDisplayName; } function showStatus(message, isError = false) { let statusEl = document.getElementById("vault-sharing-status"); if (statusEl) { statusEl.textContent = message; statusEl.style.display = 'block'; statusEl.style.color = isError ? '#68acff': '#e168ff'; statusEl.style.background = isError ? 'rgba(255,155,200,0.1)': 'rgba(155,183,255,0.1)'; statusEl.style.padding = '8px'; statusEl.style.borderRadius = '4px'; statusEl.style.marginTop = '10px'; setTimeout(() => { statusEl.style.display = 'none'; }, isMobile ? 5000: 3000); } } function formatMoneyInput(event) { let value = event.target.value.replace(/[^\d]/g, ''); if (value) { event.target.value = parseInt(value).toLocaleString(); } } function addUI() { let existing = document.getElementById("vault-sharing-container"); if (existing) existing.remove(); let mobileStyles = isMobile ? ` padding: 10px; margin: 5px; font-size: 14px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); `: ` padding: 8px; margin: 3px 0; `; let html = ` <div id="vault-sharing-container" style=" background: #3a3a3a; border: 1px solid #4a4a4a; border-radius: 6px; ${mobileStyles} position: relative; color: #ddd; "> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;"> <h4 style="margin: 0; color: #ddd; font-size: ${isMobile ? '16px': '14px'};">Vault Sharing</h4> <div style="display: flex; gap: 4px;"> <button id="vault-sharing-edit-btn" style=" background: #68ebff; color: white; border: none; padding: ${isMobile ? '6px 10px': '4px 8px'}; border-radius: 3px; cursor: pointer; font-size: ${isMobile ? '12px': '11px'}; min-height: ${isMobile ? '32px': '24px'}; ">Edit</button> </div> </div> <div style="display: flex; gap: 8px; margin-bottom: 8px;"> <div style="flex: 1; background: #4a4a4a; padding: ${isMobile ? '8px': '6px'}; border-radius: 4px; text-align: center; border-left: 3px solid #e168ff;"> <div style="font-size: ${isMobile ? '11px': '10px'}; color: #aaa; margin-bottom: 2px;"> <span id="vault-sharing-player-display-name">${playerDisplayName}</span> </div> <div id="vault-sharing-own-balance" style="font-size: ${isMobile ? '14px': '12px'}; color: #e168ff; font-weight: bold;">Calculating...</div> </div> <div style="flex: 1; background: #4a4a4a; padding: ${isMobile ? '8px': '6px'}; border-radius: 4px; text-align: center; border-left: 3px solid #68acff;"> <div style="font-size: ${isMobile ? '11px': '10px'}; color: #aaa; margin-bottom: 2px;"> <span id="vault-sharing-spouse-display-name">${spouseDisplayName}</span> </div> <div id="vault-sharing-spouse-balance" style="font-size: ${isMobile ? '14px': '12px'}; color: #68acff; font-weight: bold;">Calculating...</div> </div> </div> <div style="text-align: center; padding: ${isMobile ? '6px': '4px'}; background: #4a4a4a; border-radius: 4px; border-left: 3px solid #68ebff;"> <span style="font-size: ${isMobile ? '11px': '10px'}; color: #aaa;">Total: </span> <span id="vault-sharing-total-balance" style="font-size: ${isMobile ? '14px': '12px'}; color: #68ebff; font-weight: bold;">$0</span> </div> <div id="vault-sharing-edit-section" style="display: none; margin-top: 10px; padding: 10px; background: #4a4a4a; border-radius: 4px; border: 1px solid #555;"> <div style="display: ${isMobile ? 'block': 'flex'}; gap: 8px; margin-bottom: 10px;"> <div style="flex: 1; margin-bottom: ${isMobile ? '8px': '0'};"> <label style="display: block; margin-bottom: 2px; font-size: ${isMobile ? '12px': '11px'}; color: #ddd;">Your Name:</label> <input id="vault-sharing-player-name" type="text" value="${playerDisplayName}" style=" width: 100%; padding: ${isMobile ? '6px': '4px'}; border: 1px solid #666; border-radius: 3px; font-size: ${isMobile ? '14px': '12px'}; box-sizing: border-box; background: #333; color: #ddd; "> </div> <div style="flex: 1;"> <label style="display: block; margin-bottom: 2px; font-size: ${isMobile ? '12px': '11px'}; color: #ddd;">Spouse Name:</label> <input id="vault-sharing-spouse-name" type="text" value="${spouseDisplayName}" style=" width: 100%; padding: ${isMobile ? '6px': '4px'}; border: 1px solid #666; border-radius: 3px; font-size: ${isMobile ? '14px': '12px'}; box-sizing: border-box; background: #333; color: #ddd; "> </div> </div> <div style="display: ${isMobile ? 'block': 'flex'}; gap: 8px; margin-bottom: 10px;"> <div style="flex: 1; margin-bottom: ${isMobile ? '8px': '0'};"> <label style="display: block; margin-bottom: 2px; font-size: ${isMobile ? '12px': '11px'}; color: #ddd;">Your Starting Balance:</label> <input id="vault-sharing-own-start-balance" type="text" value="${ownStartingBalance.toLocaleString()}" style=" width: 100%; padding: ${isMobile ? '6px': '4px'}; border: 1px solid #666; border-radius: 3px; font-size: ${isMobile ? '14px': '12px'}; box-sizing: border-box; background: #333; color: #ddd; "> </div> <div style="flex: 1;"> <label style="display: block; margin-bottom: 2px; font-size: ${isMobile ? '12px': '11px'}; color: #ddd;">Spouse Starting Balance:</label> <input id="vault-sharing-spouse-start-balance" type="text" value="${spouseStartingBalance.toLocaleString()}" style=" width: 100%; padding: ${isMobile ? '6px': '4px'}; border: 1px solid #666; border-radius: 3px; font-size: ${isMobile ? '14px': '12px'}; box-sizing: border-box; background: #333; color: #ddd; "> </div> </div> <div style="margin-bottom: 10px;"> <label style="display: block; margin-bottom: 2px; font-size: ${isMobile ? '12px': '11px'}; color: #ddd;">Start Date & Time:</label> <input id="vault-sharing-start-time" type="datetime-local" value="${startTime}" style=" width: 100%; padding: ${isMobile ? '6px': '4px'}; border: 1px solid #666; border-radius: 3px; font-size: ${isMobile ? '14px': '12px'}; box-sizing: border-box; background: #333; color: #ddd; "> </div> <div style="display: ${isMobile ? 'block': 'flex'}; gap: 6px;"> <button id="vault-sharing-save" style=" background: #e168ff; color: white; border: none; padding: ${isMobile ? '8px 12px': '6px 10px'}; border-radius: 3px; cursor: pointer; font-size: ${isMobile ? '12px': '11px'}; flex: 1; margin-bottom: ${isMobile ? '6px': '0'}; min-height: ${isMobile ? '36px': '28px'}; ">Save</button> <button id="vault-sharing-update" style=" background: #68acff; color: white; border: none; padding: ${isMobile ? '8px 12px': '6px 10px'}; border-radius: 3px; cursor: pointer; font-size: ${isMobile ? '12px': '11px'}; flex: 1; min-height: ${isMobile ? '36px': '28px'}; ">Set to Current</button> </div> <div id="vault-sharing-status" style="display: none; margin-top: 8px; font-size: ${isMobile ? '12px': '11px'};"></div> </div> </div> `; let insertTarget = document.querySelector(".vault-trans-wrap") || document.querySelector('[class*="vault-trans"]') || document.querySelector('[class*="property-option"]') || document.querySelector('#properties-page-wrap'); if (insertTarget) { insertTarget.insertAdjacentHTML("beforebegin", html); document.getElementById("vault-sharing-save")?.addEventListener("click", handleSave); document.getElementById("vault-sharing-update")?.addEventListener("click", handleSetCurrent); document.getElementById("vault-sharing-edit-btn")?.addEventListener("click", () => toggleEditMode()); ['vault-sharing-own-start-balance', 'vault-sharing-spouse-start-balance'].forEach(id => { let input = document.getElementById(id); if (input) { input.addEventListener('input', formatMoneyInput); if (isMobile) { input.addEventListener('focus', () => { document.querySelector('meta[name="viewport"]')?.setAttribute('content', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'); }); input.addEventListener('blur', () => { document.querySelector('meta[name="viewport"]')?.setAttribute('content', 'width=device-width, initial-scale=1.0'); }); } } }); toggleEditMode(false); console.log('UI added successfully'); return true; } else { console.log('Could not find insertion target'); return false; } } function setupObservers() { let mutationConfig = { attributes: false, childList: true, subtree: true }; let transactionCallback = (mutationList, observer) => { calculateAndShowBalances(); }; let initialCallback = (mutationList, observer) => { for (let mutation of mutationList) { if (mutation.type === "childList") { for (let node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE) { if (node.classList?.contains("property-option") || node.querySelector?.('.vault-trans-wrap') || node.classList?.contains("vault-trans-wrap")) { if (addUI()) { calculateAndShowBalances(); let transactionList = document.querySelector(".vault-trans-wrap ul") || document.querySelector('[class*="vault-trans"] ul'); if (transactionList) { let transactionObserver = new MutationObserver(transactionCallback); transactionObserver.observe(transactionList, mutationConfig); } } } } } } } }; let pageWrap = document.getElementById("properties-page-wrap"); if (pageWrap) { let pageObserver = new MutationObserver(initialCallback); pageObserver.observe(pageWrap, mutationConfig); } let vaultTransWrap = document.querySelector(".vault-trans-wrap"); if (vaultTransWrap) { if (addUI()) { calculateAndShowBalances(); let transactionList = vaultTransWrap.querySelector("ul"); if (transactionList) { let transactionObserver = new MutationObserver(transactionCallback); transactionObserver.observe(transactionList, mutationConfig); } } } } setupObservers(); } if (isMobile) { let style = document.createElement('style'); style.textContent = ` #vault-sharing-container input { -webkit-appearance: none; -moz-appearance: none; appearance: none; } #vault-sharing-container button { -webkit-appearance: none; -moz-appearance: none; appearance: none; -webkit-tap-highlight-color: rgba(0,0,0,0); } #vault-sharing-container button:active { transform: scale(0.98); } #vault-sharing-container input:focus { border-color: #e94560 !important; } `; document.head.appendChild(style); } })();