您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically keeps track of your upkeep transactions for each user with date selector
// ==UserScript== // @name Automatic Upkeep Tracker // @namespace upkeep.sharing.mobile // @version 2.8 // @description Automatically keeps track of your upkeep transactions for each user with date selector // @author ANITABURN // @match https://www.torn.com/properties.php* // @license MIT // @grant none // ==/UserScript== (function () { "use strict"; const localStorageKey = "upkeep_tracker:settings"; let attempts = 0; const maxAttempts = 20; function findInsertionPoint() { attempts++; const payBillsContainer = document.querySelector( "div.pay-bills.cont-gray.bottom-round" ); const upkeepPaymentsWrap = document.querySelector( "div.upkeep-payments-wrap" ); if (payBillsContainer && upkeepPaymentsWrap) { insertCalculator(upkeepPaymentsWrap); return true; } return false; } function insertCalculator(targetElement) { if (document.getElementById("upkeep-totals-calculator")) return; let { user1DisplayName, user2DisplayName } = JSON.parse(localStorage.getItem(localStorageKey)) || { user1DisplayName: "User 1", user2DisplayName: "User 2", }; const container = document.createElement("div"); container.id = "upkeep-totals-calculator"; container.style.cssText = ` margin: 15px 0; padding: 20px; background: #3a3a3a; border: 1px solid #555; border-radius: 8px; color: white; `; const headerDiv = document.createElement("div"); headerDiv.style.cssText = ` display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; `; const title = document.createElement("h3"); title.textContent = "Upkeep Totals"; title.style.cssText = ` margin: 0; color: white; font-size: 18px; `; headerDiv.appendChild(title); const editBtn = document.createElement("button"); editBtn.textContent = "Edit"; editBtn.style.cssText = ` background: #4ecdc4; color: white; border: none; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 12px; `; headerDiv.appendChild(editBtn); container.appendChild(headerDiv); const dateDiv = document.createElement("div"); dateDiv.style.cssText = ` margin-bottom: 15px; `; const dateLabel = document.createElement("label"); dateLabel.textContent = "From: "; dateLabel.style.cssText = ` color: #ccc; margin-right: 10px; font-weight: bold; `; dateDiv.appendChild(dateLabel); const dateInput = document.createElement("input"); dateInput.type = "date"; dateInput.style.cssText = ` padding: 5px; border: 1px solid #666; border-radius: 3px; background: #2a2a2a; color: white; `; dateDiv.appendChild(dateInput); container.appendChild(dateDiv); const editSection = document.createElement("div"); editSection.id = "upkeep-edit-section"; editSection.style.cssText = ` display: none; /* This will be controlled by the Edit button */ margin-top: 20px; padding: 20px; background: #404040; border-radius: 6px; border: 1px solid #555; `; const nameInputsDiv = document.createElement("div"); nameInputsDiv.style.cssText = `display: flex; gap: 15px; margin-top: 15px;`; nameInputsDiv.innerHTML = ` <div style="flex: 1;"> <label style="display: block; margin-bottom: 5px; font-size: 12px; color: #ccc;">User 1 Display Name:</label> <input id="upkeep-user1-name-input" type="text" value="${user1DisplayName}" style="width: 100%; padding: 5px; border: 1px solid #666; border-radius: 3px; background: #2a2a2a; color: white; box-sizing: border-box;"> </div> <div style="flex: 1;"> <label style="display: block; margin-bottom: 5px; font-size: 12px; color: #ccc;">User 2 Display Name:</label> <input id="upkeep-user2-name-input" type="text" value="${user2DisplayName}" style="width: 100%; padding: 5px; border: 1px solid #666; border-radius: 3px; background: #2a2a2a; color: white; box-sizing: border-box;"> </div> `; // Move the date selector inside the new edit section and give its input a proper ID dateDiv.id = ""; dateInput.id = "upkeep-date-input"; editSection.appendChild(dateDiv); editSection.appendChild(nameInputsDiv); container.appendChild(editSection); const usersDiv = document.createElement("div"); usersDiv.style.cssText = ` display: flex; gap: 0; margin-bottom: 15px; `; const user1Div = document.createElement("div"); user1Div.style.cssText = ` flex: 1; padding: 15px; background: #4a4a4a; border-left: 4px solid #e168ff; text-align: center; border-top-left-radius: 6px; border-bottom-left-radius: 6px; `; user1Div.innerHTML = ` <div style="font-size: 12px; color: #ccc; margin-bottom: 5px;" id="user1-name">${user1DisplayName}</div> <div style="font-size: 16px; font-weight: bold; color: #e168ff;" id="user1-total">$0</div> `; const user2Div = document.createElement("div"); user2Div.style.cssText = ` flex: 1; padding: 15px; background: #4a4a4a; border-right: 4px solid #68acff; text-align: center; border-top-right-radius: 6px; border-bottom-right-radius: 6px; `; user2Div.innerHTML = ` <div style="font-size: 12px; color: #ccc; margin-bottom: 5px;" id="user2-name">${user2DisplayName}</div> <div style="font-size: 16px; font-weight: bold; color: #68acff;" id="user2-total">$0</div> `; usersDiv.appendChild(user1Div); usersDiv.appendChild(user2Div); container.appendChild(usersDiv); const totalDiv = document.createElement("div"); totalDiv.style.cssText = ` padding: 12px; background: #4a4a4a; border-radius: 6px; text-align: center; border-bottom: 4px solid #68ebff; `; totalDiv.innerHTML = ` <div style="font-size: 12px; color: #ccc; margin-bottom: 3px;">Total:</div> <div style="font-size: 18px; font-weight: bold; color: #68ebff;" id="grand-total">$0</div> `; container.appendChild(totalDiv); targetElement.parentNode.insertBefore(container, targetElement); function saveSettings() { const user1Input = document.getElementById("upkeep-user1-name-input"); const user2Input = document.getElementById("upkeep-user2-name-input"); user1DisplayName = user1Input.value.trim() || "User 1"; user2DisplayName = user2Input.value.trim() || "User 2"; localStorage.setItem( localStorageKey, JSON.stringify({ user1DisplayName, user2DisplayName }) ); document.getElementById("user1-name").textContent = user1DisplayName; document.getElementById("user2-name").textContent = user2DisplayName; } function toggleEditMode() { const editSection = document.getElementById("upkeep-edit-section"); const isEditing = editSection.style.display !== "none"; if (isEditing) { saveSettings(); updateTotals(); editSection.style.display = "none"; editBtn.textContent = "Edit"; editBtn.style.background = "#4ecdc4"; } else { editSection.style.display = "block"; editBtn.textContent = "Done"; editBtn.style.background = "#e74c3c"; } } editBtn.addEventListener("click", toggleEditMode); function parseDateTime(dtString) { const [part1, part2] = dtString.split(" "); if (!part1 || !part2) return null; // Try to determine which part is time (has colons) and which is date (has slashes) let timePart, datePart; if (part1.includes(":")) { timePart = part1; datePart = part2; } else if (part2.includes(":")) { timePart = part2; datePart = part1; } else { return null; // Neither part looks like time } const [hours, minutes, seconds] = timePart.split(":").map(Number); const [day, month, year] = datePart.split("/").map(Number); const fullYear = year < 100 ? 2000 + year : year; return new Date(fullYear, month - 1, day, hours, minutes, seconds); } function parseAmount(amountStr) { return Number(amountStr.replace(/[^0-9.-]+/g, "")); } function getCleanUsername(userAnchor) { let raw = ""; userAnchor.childNodes.forEach((node) => { if (node.nodeType === Node.TEXT_NODE) { raw += node.textContent.trim(); } }); return raw.trim(); } function updateTotals() { const startDateValue = dateInput.value; let startDate = null; if (startDateValue) { const [year, month, day] = startDateValue.split("-").map(Number); startDate = new Date(year, month - 1, day, 0, 0, 0); } const totals = {}; let totalAmount = 0; let users = []; const paymentRows = document.querySelectorAll( "ul.upkeep-payments li:not(.title) ul.payments" ); paymentRows.forEach((row) => { const dateSpan = row.querySelector("span.transaction-date"); const timeSpan = row.querySelector("span.transaction-time"); const userAnchor = row.querySelector("a.user.name"); const amountLi = row.querySelector("li.amount"); if (!dateSpan || !timeSpan || !userAnchor || !amountLi) return; // Try both possible orders to handle different HTML structures const dateTimeString1 = dateSpan.textContent.trim() + " " + timeSpan.textContent.trim(); const dateTimeString2 = timeSpan.textContent.trim() + " " + dateSpan.textContent.trim(); // Use whichever one parses successfully const dateTimeString = parseDateTime(dateTimeString1) ? dateTimeString1 : dateTimeString2; const date = parseDateTime(dateTimeString); if (!startDate || (date && date >= startDate)) { const amount = parseAmount(amountLi.textContent); const username = getCleanUsername(userAnchor); if (!totals[username]) { totals[username] = 0; users.push(username); } totals[username] += amount; totalAmount += amount; } }); const user1Name = document.getElementById("user1-name"); const user1Total = document.getElementById("user1-total"); const user2Name = document.getElementById("user2-name"); const user2Total = document.getElementById("user2-total"); const grandTotal = document.getElementById("grand-total"); if (users.length >= 1) { user1Total.textContent = "$" + (totals[users[0]] || 0).toLocaleString(); } else { user1Total.textContent = "$0"; } if (users.length >= 2) { user2Total.textContent = "$" + (totals[users[1]] || 0).toLocaleString(); } else { user2Total.textContent = "$0"; } grandTotal.textContent = "$" + totalAmount.toLocaleString(); } dateInput.addEventListener("change", updateTotals); const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); dateInput.value = thirtyDaysAgo.toISOString().split("T")[0]; setTimeout(updateTotals, 1000); } function init() { if (findInsertionPoint()) return; if (attempts < maxAttempts) setTimeout(init, 500); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();