您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Fetches faction data using the Torn API when on a faction profile page.
当前为
// ==UserScript== // @name Torn Faction Lookup API Caller // @license MIT // @namespace http://tampermonkey.net/ // @version 0.1 // @description Fetches faction data using the Torn API when on a faction profile page. // @author Tinkt [3169756] // @match https://www.torn.com/factions.php?step=profile&ID=* // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // ==/UserScript== (function () { "use strict"; /** * Configuratoin data for the script. You will be prompted to enter an API key if you have not yet. */ const API_KEY_STORAGE_KEY = null; const REFRESH_FREQUENCY_IN_SECONDS = 10; const ERRORS = { KEY_REQUIRED: "API key is required. The script will not function without it.", FETCH_ERROR: "Error fetching Torn data. Check console for details.", NETWORK_ERROR: "Network error while fetching faction data. Check console for details.", NO_FACTION_ID: "Not on a faction profile page, or could not extract faction ID.", }; /** * Torn specific data. */ const USER_STATUSES = { OKAY: "Okay", ABROAD: "Abroad", HOSPITAL: "Hospital", TRAVELING: "Traveling", }; const LOCATIONS = { TORN: "Torn", ARGENTINA: "Argentina", CANADA: "Canada", CHINA: "China", CAYMAN_ISLANDS: "Cayman Islands", JAPAN: "Japan", MEXICO: "Mexico", SWITZERLAND: "Switzerland", UAE: "United Arab Emirates", UK: "United Kingdom", SOUTH_AFRICA: "South Africa", }; let my_location = null; let apiKey = GM_getValue(API_KEY_STORAGE_KEY, null); if (!apiKey) { apiKey = prompt( "Please enter your Torn API key, any level of key will work:" ); if (apiKey) { GM_setValue(API_KEY_STORAGE_KEY, apiKey); } else { alert(ERRORS.KEY_REQUIRED); return; // Exit if no API key is provided. } } // Function to extract the faction ID from the URL function getFactionId() { const urlParams = new URLSearchParams(window.location.search); return urlParams.get("ID"); } function secondsToMilliseconds(seconds) { return seconds * 1000; } // Function to fetch data from the Torn API function fetchFactionData(factionId, apiKey) { const apiUrl = `https://api.torn.com/faction/${factionId}?selections=basic&key=${apiKey}`; GM_xmlhttpRequest({ method: "GET", url: apiUrl, onload: function (response) { if (response.status === 200) { const data = JSON.parse(response.responseText); updatePlayerStatuses(data.members); } else { console.error( "Torn API Error:", response.status, response.responseText ); alert(ERRORS.FETCH_ERROR); } }, onerror: function (error) { console.error("GM_xmlhttpRequest Error:", error); alert(ERRORS.NETWORK_ERROR); }, }); } function fetchUserData(apiKey) { const apiUrl = `https://api.torn.com/user/?selections=profile&key=${apiKey}`; GM_xmlhttpRequest({ method: "GET", url: apiUrl, onload: function (response) { if (response.status === 200) { const data = JSON.parse(response.responseText); my_location = determineLocation(data.status); } else { console.error( "Torn API Error:", response.status, response.responseText ); alert(ERRORS.FETCH_ERROR); } }, onerror: function (error) { console.error("GM_xmlhttpRequest Error:", error); alert(ERRORS.NETWORK_ERROR); }, }); } function determineLocation({ state, description }) { const locations = { "In Argentina": LOCATIONS.ARGENTINA, "In Canada": LOCATIONS.CANADA, "In the Cayman Islands": LOCATIONS.CAYMAN_ISLANDS, "In China": LOCATIONS.CHINA, "In Hawaii": LOCATIONS.HAWAII, "In Japan": LOCATIONS.JAPAN, "In Mexico": LOCATIONS.MEXICO, Okay: LOCATIONS.TORN, "In South Africa": LOCATIONS.SOUTH_AFRICA, "In Switzerland": LOCATIONS.SWITZERLAND, "In UAE": LOCATIONS.UAE, "In the United Kingdom": LOCATIONS.UK, }; const hospitalLocations = { "In an Argentinian": LOCATIONS.ARGENTINA, "In a Canadian": LOCATIONS.CANADA, "In a Cayman": LOCATIONS.CAYMAN_ISLANDS, "In a Chinese": LOCATIONS.CHINA, "In an Emirati": LOCATIONS.UAE, "In a Hawaiian": LOCATIONS.HAWAII, "In hospital": LOCATIONS.TORN, "In a Japanese": LOCATIONS.JAPAN, "In a Mexican": LOCATIONS.MEXICO, "In a South African": LOCATIONS.SOUTH_AFRICA, "In a Swiss": LOCATIONS.SWITZERLAND, "In a UK": LOCATIONS.UK, }; const travelLocations = { "to Argentina": LOCATIONS.ARGENTINA, "to Canada": LOCATIONS.CANADA, "to the Cayman": LOCATIONS.CAYMAN_ISLANDS, "to China": LOCATIONS.CHINA, "to UAE": LOCATIONS.UAE, "to Hawaii": LOCATIONS.HAWAII, "to Japan": LOCATIONS.JAPAN, "to Mexico": LOCATIONS.MEXICO, "to South Africa": LOCATIONS.SOUTH_AFRICA, "to Switzerland": LOCATIONS.SWITZERLAND, "to United Kingdom": LOCATIONS.UK, "to Torn": LOCATIONS.TORN, }; if (state === USER_STATUSES.HOSPITAL) { for (const [key, value] of Object.entries(hospitalLocations)) { if (description.includes(key)) { return value; } } } if (state === USER_STATUSES.TRAVELING) { for (const [key, value] of Object.entries(travelLocations)) { if (description.includes(key)) { return value; } } } return locations[description] || state; } /** * A user is where you are if they are not traveling and their location evaluates to your location. * @param {*} param0 * @returns */ function isUserWhereYouAre({ state, description }) { const location = determineLocation({ state, description }); if (state === USER_STATUSES.TRAVELING) { return false; } return determineLocation({ state, description }) === my_location; } /** * A user is approaching if they are traveling to your current location. This function does * not take into account when you are traveling. This could be improved to handle that scenario. * @param {*} param0 * @returns */ function isUserApproaching({ state, description }) { if (state !== USER_STATUSES.TRAVELING) return false; const their_destination = determineLocation({ state, description }); return their_destination === my_location; } function isUserAttackable({ state, description }) { if (state === USER_STATUSES.OKAY && my_location === LOCATIONS.TORN) { return true; } if (state === USER_STATUSES.ABROAD && my_location !== LOCATIONS.TORN) { if (description.includes(my_location)) { return true; } } return false; } /** * Styling related functions */ function applyPlayerRowMutations(playerRow) { playerRow.style.height = "80px"; } function applyIconTrayMutations(memberIconsDiv) { const iconTray = memberIconsDiv.querySelector("#iconTray"); // Update its width to 50% if (iconTray) { iconTray.style.width = "50%"; } } function applyDetailContainerStyles(detailsContainer) { detailsContainer.className = "player-details"; detailsContainer.style.marginTop = "5px"; detailsContainer.style.width = "50%"; } function applyStatusTextStyles(statusTextContainer) { // Create a span for the "Status:" label const statusLabel = document.createElement("span"); statusLabel.textContent = "Status: "; statusTextContainer.appendChild(statusLabel); } function applyStatusDescriptionStyles( statusTextContainer, { description, color } ) { // Create a span for the status description and apply the color const statusDescription = document.createElement("span"); statusDescription.textContent = description; // Map colors to their respective hex codes const colorMap = { red: "#FF8787", green: "#82C91E", blue: "#3BC9DB", }; // Apply the mapped color or default to black if the color is not in the map statusDescription.style.color = colorMap[color] || "black"; statusTextContainer.appendChild(statusDescription); } function applyAttackButtonStyles(attackButton) { attackButton.textContent = "Attack"; attackButton.style.marginTop = "2px"; attackButton.style.padding = "2px 10px"; // Smaller padding for a compact button attackButton.style.border = "none"; // Remove default border attackButton.style.borderRadius = "3px"; // Add slightly rounded corners attackButton.style.color = "white"; // Set text color to white attackButton.style.cursor = "pointer"; // Change cursor to pointer on hover attackButton.style.fontSize = "12px"; // Set font size to 12px attackButton.style.height = "15px"; // Set a fixed height for the button } function applyAttackButtonMutations(attackButton, status) { if (isUserApproaching(status)) { attackButton.style.backgroundColor = "orange"; attackButton.style.color = "black"; attackButton.style.cursor = "wait"; } if (isUserAttackable(status)) { attackButton.style.backgroundColor = "red"; attackButton.style.color = "black"; attackButton.style.cursor = "crosshair"; } if (isUserWhereYouAre(status) && !isUserAttackable(status)) { attackButton.style.backgroundColor = "blue"; attackButton.style.color = "white"; attackButton.style.cursor = "wait"; } if ( !isUserAttackable(status) && !isUserApproaching(status) && !isUserWhereYouAre(status) ) { attackButton.style.backgroundColor = "gray"; attackButton.style.cursor = "not-allowed"; } } /** * Main function responsible for building out and changing the page. * @param {*} payload - The data from the Torn API for the faction. */ function updatePlayerStatuses(payload) { Object.keys(payload).forEach((playerId) => { const playerRowXpath = `//div[contains(@class, "members-list")]//li[.//a[contains(@href, "/profiles.php?XID=${playerId}")]]`; const playerRow = document.evaluate( playerRowXpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue; if (playerRow) { applyPlayerRowMutations(playerRow); // Find the div with the class "member-icons" within the playerRow const memberIconsDiv = playerRow.querySelector(".member-icons"); applyIconTrayMutations(memberIconsDiv); if (memberIconsDiv) { // Check if the details already exist to avoid duplication if (!memberIconsDiv.querySelector(".player-details")) { // Create a new container for player details const detailsContainer = document.createElement("div"); applyDetailContainerStyles(detailsContainer); // Add the status text const statusTextContainer = document.createElement("p"); applyStatusTextStyles(statusTextContainer); applyStatusDescriptionStyles( statusTextContainer, payload[playerId].status ); // Append the status text container to the details container detailsContainer.appendChild(statusTextContainer); // Add the attack button const attackButton = document.createElement("button"); applyAttackButtonStyles(attackButton); applyAttackButtonMutations(attackButton, payload[playerId].status); attackButton.onclick = () => { // Redirect to the attack URL window.location.href = `https://www.torn.com/loader.php?sid=attack&user2ID=${playerId}`; }; // Append the attack button to the details container detailsContainer.appendChild(attackButton); // Append the details container to the member-icons div memberIconsDiv.appendChild(detailsContainer); } } } }); } const factionId = getFactionId(); const refreshFrequency = secondsToMilliseconds(REFRESH_FREQUENCY_IN_SECONDS); if (factionId) { const fetchData = () => { fetchUserData(apiKey); fetchFactionData(factionId, apiKey); }; // Call the function immediately. fetchData(); // Call the function every n seconds. setInterval(fetchData, refreshFrequency); } else { console.log(ERRORS.NO_FACTION_ID); } })();