您需要先安装一个扩展,例如 篡改猴、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();
- })();