您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Displays faction members that are not currently participating in a faction crime. Highlights members inactive for days.
// ==UserScript== // @name OC 2.0 Helper // @namespace https://www.torn.com/ // @version 0.10.0 // @description Displays faction members that are not currently participating in a faction crime. Highlights members inactive for days. // @author Slaterz [2479416] // @match https://www.torn.com/factions.php* // @grant GM_xmlhttpRequest // @license MIT // ==/UserScript== console.log("OC2 Helper script loading"); (function () { "use strict"; const TORN_API_BASE_V2 = "https://api.torn.com/v2/"; const TORN_API_BASE_V1 = "https://api.torn.com/"; let { MEMBERS_ENDPOINT, CRIME_RANKS_ENDPOINT, PLANNED_CRIMES_ENDPOINT, RECRUITING_CRIMES_ENDPOINT, } = buildEndpoints(); function constructEndpoint(base, params) { const apiKey = localStorage.getItem("OC2H_API"); return `${base}?key=${apiKey}&${params}`; } function buildEndpoints() { return { MEMBERS_ENDPOINT: constructEndpoint( `${TORN_API_BASE_V2}faction/members`, "striptags=false" ), CRIME_RANKS_ENDPOINT: constructEndpoint( `${TORN_API_BASE_V1}faction/`, "selections=crimeexp" ), PLANNED_CRIMES_ENDPOINT: constructEndpoint( `${TORN_API_BASE_V2}faction/crimes`, "cat=planning&offset=0" ), RECRUITING_CRIMES_ENDPOINT: constructEndpoint( `${TORN_API_BASE_V2}faction/crimes`, "cat=recruiting&offset=0" ), }; } async function fetchData(endpoint) { try { const response = await fetch(endpoint); if (!response.ok) { throw new Error(`API call failed with status ${response.status}`); } // Parse the response JSON const data = await response.json(); // Check for API error in the response if (data.error) { throw new Error(data.error.error); // Extract and throw the API error message } return data; // Return the valid data } catch (error) { console.error("Error fetching data from API:", error.message); // Log the error message throw error; // Re-throw the error to propagate it to the caller } } function populateFactionMembersTable(members, recruitingCrimeMembers) { const table = document.getElementById("oc2h-faction-members-table"); const tbody = table.querySelector("tbody"); tbody.innerHTML = ""; // Clear previous rows members.forEach((member) => { const row = document.createElement("tr"); const lastActiveText = member.last_action?.relative || "Unknown"; // Check if the member is inactive if (lastActiveText.includes("day")) { row.classList.add("inactive-row"); // Apply class to the entire row } // Check if the member is in a recruiting crime if (recruitingCrimeMembers.has(member.id.toString())) { row.classList.add("recruiting-row"); } row.innerHTML = ` <td class="member-name">${member.name} [${member.id}]</td> <td>${member.rank || "Unranked"}</td> <td>${lastActiveText}</td> `; tbody.appendChild(row); }); table.classList.remove("hidden"); } async function fetchAllFactionData() { const dataContainer = document.getElementById("oc2h-data-container"); const errorMessage = document.getElementById("oc2h-error-message"); try { const [membersData, crimeRanks, plannedCrimes, recruitingCrimes] = await Promise.all([ fetchData(MEMBERS_ENDPOINT), fetchData(CRIME_RANKS_ENDPOINT), fetchData(PLANNED_CRIMES_ENDPOINT), fetchData(RECRUITING_CRIMES_ENDPOINT), ]); if (!membersData || !crimeRanks || !plannedCrimes || !recruitingCrimes) { throw new Error("Failed to fetch faction data."); } const membersObj = membersData.members || {}; const crimeRanksArray = crimeRanks.crimeexp || {}; // Create a Set of members already in planned crimes const plannedCrimeMembers = new Set(); plannedCrimes.crimes.forEach((crime) => { crime.slots.forEach((slot) => { if (slot.user && slot.user.id) { plannedCrimeMembers.add(slot.user.id.toString()); } }); }); //Create a Set of members in recruiting crimes const recruitingCrimeMembers = new Set(); recruitingCrimes.crimes.forEach((crime) => { crime.slots.forEach((slot) => { if (slot.user && slot.user.id) { recruitingCrimeMembers.add(slot.user.id.toString()); } }); }); // Map crime ranks to IDs const crimeRankMap = {}; crimeRanksArray.forEach((id, index) => { crimeRankMap[id.toString()] = index + 1; }); // Filter and sort members, and add the rank property const sortedMembers = Object.values(membersObj) .filter((member) => !plannedCrimeMembers.has(member.id.toString())) // Exclude planned members .map((member) => ({ ...member, rank: crimeRankMap[member.id.toString()] || "Unranked", // Map the rank })) .sort((a, b) => { const rankA = crimeRankMap[a.id.toString()] || Infinity; const rankB = crimeRankMap[b.id.toString()] || Infinity; return rankA - rankB; }); if (sortedMembers.length === 0) { throw new Error("No valid faction members found."); } // Populate table and show the data container populateFactionMembersTable(sortedMembers, recruitingCrimeMembers); dataContainer.classList.remove("hidden"); errorMessage.classList.add("hidden"); } catch (error) { // Display detailed error message console.error("Error fetching faction data:", error.message); errorMessage.textContent = error.message || "An unexpected error occurred."; errorMessage.classList.remove("hidden"); dataContainer.classList.add("hidden"); } } let uiInitialized = false; // Flag to track if the UI has been created // Function to create and insert the OC2 Helper UI. function createOC2HelperUI() { const factionCrimesElement = document.getElementById("faction-crimes"); if (!factionCrimesElement) { return; // Do nothing if the target element doesn't exist } if (document.getElementById("oc2h-helper-container")) { return; // Skip if the UI already exists } const factionCrimesWrap = factionCrimesElement.querySelector( ".faction-crimes-wrap" ); if (!factionCrimesWrap) { return; // Do nothing if the reference element doesn't exist } // Create the OC2 Helper container const oc2HelperContainer = document.createElement("div"); oc2HelperContainer.id = "oc2h-helper-container"; oc2HelperContainer.className = "oc2h-container"; oc2HelperContainer.innerHTML = ` <div class="oc2h-header"> <div class="oc2h-title">OC 2.0 Helper</div> <div id="oc2h-api-key-wrapper" class="oc2h-api-key-wrapper"> <div id="oc2h-api-key-form" class="oc2h-api-key-form hidden"> <input type="text" id="oc2h-api-key-input" class="oc2h-api-key-input" placeholder="Enter Limited API key" /> <button id="oc2h-api-key-submit" class="torn-btn oc2h-api-key-submit">Submit</button> </div> <button id="oc2h-set-api-key-btn" class="torn-btn oc2h-set-api-key-btn">Set API Key</button> </div> </div> <div id="oc2h-data-container" class="oc2h-data-container hidden"> <div class="oc2h-table-title">Members not in Planning OC</div> <table id="oc2h-faction-members-table" class="oc2h-table hidden"> <thead> <tr> <th>Member</th> <th>Rank</th> <th>Last Active</th> </tr> </thead> <tbody> <!-- Faction members will be populated here --> </tbody> </table> </div> <div id="oc2h-error-message" class="oc2h-error-message hidden"> <!-- Error messages will be displayed here --> </div> `; factionCrimesElement.insertBefore(oc2HelperContainer, factionCrimesWrap); // Add event listeners for the API key form and fetch button initializeApiKeyForm(); // Automatically fetch data fetchAllFactionData(); } function initializeApiKeyForm() { const setApiKeyBtn = document.getElementById("oc2h-set-api-key-btn"); setApiKeyBtn.addEventListener("click", () => { const form = document.getElementById("oc2h-api-key-form"); form.classList.remove("hidden"); // Show the input box and submit button }); const submitBtn = document.getElementById("oc2h-api-key-submit"); submitBtn.addEventListener("click", async () => { const input = document.getElementById("oc2h-api-key-input"); const newKey = input.value.trim(); console.log("New API Key entered:", newKey); // Log for debugging if (newKey) { localStorage.setItem("OC2H_API", newKey); // Directly store the key in localStorage alert("API Key has been set!"); input.value = ""; // Clear the input field const form = document.getElementById("oc2h-api-key-form"); form.classList.add("hidden"); // Hide the input box and submit button console.log("API Key successfully updated in localStorage."); // Rebuild the endpoints with the new API key ({ MEMBERS_ENDPOINT, CRIME_RANKS_ENDPOINT, PLANNED_CRIMES_ENDPOINT } = buildEndpoints()); console.log("Endpoints rebuilt with new API key."); // Trigger fetching data await fetchAllFactionData(); } else { alert("Please enter a valid API key."); } }); } // Function to observe DOM changes and reinitialize UI function observeCrimesTab() { const observer = new MutationObserver(() => { const factionCrimesElement = document.getElementById("faction-crimes"); if (factionCrimesElement) { createOC2HelperUI(); } else { uiInitialized = false; // Reset the flag when the element is removed } }); observer.observe(document.body, { childList: true, subtree: true, }); } // Add global styles function addGlobalStyle(css) { const head = document.getElementsByTagName("head")[0]; if (!head) return; const style = document.createElement("style"); style.type = "text/css"; style.innerHTML = css; head.appendChild(style); } addGlobalStyle(` .oc2h-container { background: var(--default-bg-panel-color); padding: 15px; margin: 15px 0; border: 1px solid var(--border-color); border-radius: 5px; box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1); } .oc2h-header { display: flex; justify-content: space-between; align-items: center; /* Ensures vertical alignment */ margin-bottom: 10px; } .oc2h-title { font-size: 2.5em; font-weight: bold; margin: 0; /* Remove margin to align with the header */ color: var(--text-color); } .oc2h-table-title { font-size: 2.0em; font-weight: bold; margin-top: 10px; margin-bottom: 10px; color: var(--text-color); } .oc2h-table { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 1.1em; background-color: var(--default-bg-panel-color); } .oc2h-table thead th { background: var(--btn-background); color: var(--btn-color); text-align: left; padding: 12px; border-bottom: 2px solid var(--border-color); text-transform: uppercase; font-size: 1.2em; font-weight: bold; } .oc2h-table tbody td { padding: 10px; border-bottom: 1px solid var(--border-color); color: var(--text-color); } .oc2h-table tbody tr td { padding: 2px 10px; } .oc2h-table tbody tr:hover { background-color: var(--btn-hover-background); } .oc2h-table tbody tr:nth-child(odd) { background-color: rgba(0, 0, 0, 0.05); /* Subtle striped rows for readability */ } .oc2h-table tbody tr.inactive-member { background-color: rgba(255, 0, 0, 0.1); /* Highlight inactive members */ } .oc2h-table td.member-name { font-weight: bold; color: var(--text-color); } .oc2h-api-key-wrapper { display: flex; align-items: center; gap: 10px; /* Add spacing between the elements */ } .oc2h-api-key-form.hidden { display: none; /* Hides the API key form */ } .oc2h-api-key-form { display: flex; align-items: center; gap: 5px; } .oc2h-api-key-input { padding: 5px; border: 1px solid var(--border-color); border-radius: 4px; font-size: 0.9em; } .oc2h-api-key-submit { padding: 5px 10px; background: var(--btn-background); color: var(--btn-color); border: none; border-radius: 4px; cursor: pointer; } .oc2h-api-key-submit:hover { background: var(--btn-hover-background); } .oc2h-set-api-key-btn { background: var(--btn-background); color: var(--btn-color); border: 1px solid var(--btn-border-color); padding: 5px 10px; font-size: 0.9em; cursor: pointer; border-radius: 4px; } .oc2h-set-api-key-btn:hover { background: var(--btn-hover-background); } .oc2h-data-container.hidden { display: none; } .oc2h-error-message { margin-top: 15px; padding: 10px; background: rgba(255, 0, 0, 0.1); border: 1px solid rgba(255, 0, 0, 0.3); color: var(--oc-level-color-text-hard); font-weight: bold; border-radius: 5px; } .oc2h-error-message.hidden { display: none; } .oc2h-table tbody tr.inactive-row { color: var(--oc-level-color-text-hard); font-weight: bold; } .oc2h-table tbody tr.recruiting-row { color: var(--oc-slot-menu-text); font-weight: bold; } `); // Initialize the script function waitForDOMReady() { const interval = setInterval(() => { if (document.body) { clearInterval(interval); observeCrimesTab(); createOC2HelperUI(); } }, 100); } waitForDOMReady(); })();