Greasy Fork镜像 is available in English.

Torn War Stuff Enhanced

Show travel status and hospital time and sort by hospital time on war page. Fork of https://greasyforks.org/en/scripts/448681-torn-war-stuff

Versión del día 14/03/2025. Echa un vistazo a la versión más reciente.

// ==UserScript==
// @name         Torn War Stuff Enhanced
// @namespace    namespace
// @version      1.0.1
// @description  Show travel status and hospital time and sort by hospital time on war page. Fork of https://greasyforks.org/en/scripts/448681-torn-war-stuff
// @author       xentac
// @license      MIT
// @match        *.torn.com/factions.php*
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        GM_xmlhttpRequest
// @connect      api.torn.com
// ==/UserScript==

(async function () {
  ("use strict");

  let apiKey =
    localStorage.getItem("xentac-torn_war_stuff_enhanced-apikey") ??
    "###PDA-APIKEY###";
  const sort_enemies = true;

  try {
    GM_registerMenuCommand("Set Api Key", function () {
      checkApiKey(false);
    });
  } catch (error) {
    // This is fine, but we need to handle torn pda too
  }

  function checkApiKey(checkExisting = true) {
    if (
      !checkExisting ||
      apiKey === null ||
      apiKey.indexOf("PDA-APIKEY") > -1 ||
      apiKey.length != 16
    ) {
      let userInput = prompt(
        "Please enter a PUBLIC Api Key, it will be used to get basic faction information:",
        apiKey ?? "",
      );
      if (userInput !== null && userInput.length == 16) {
        apiKey = userInput;
        localStorage.setItem(
          "xentac-torn_war_stuff_enhanced-apikey",
          userInput,
        );
      } else {
        console.error(
          "[TornWarStuffEnhanced] User cancelled the Api Key input.",
        );
      }
    }
  }

  GM_addStyle(`
.warstuff_highlight {
  background-color: #afa5 !important;
}
`);

  GM_addStyle(`
.warstuff_traveling .status {
  color: #F287FF !important;
}
`);

  function get_faction_ids() {
    const nodes = document.querySelectorAll("UL.members-list");
    if (nodes.length != 2) {
      return [];
    }
    const enemy_faction_id = nodes[0]
      .querySelector(`A[href^='/factions.php']`)
      .href.split("ID=")[1];
    const your_faction_id = nodes[1]
      .querySelector(`A[href^='/factions.php']`)
      .href.split("ID=")[1];
    return [enemy_faction_id, your_faction_id];
  }

  function get_member_lists() {
    return document.querySelectorAll("ul.members-list");
  }

  setInterval(() => {
    update_statuses();

    // Backup in case the observer doesn't work
    if (watcher == null) {
      extract_all_member_lis();
      create_watcher();
    }
  }, 15000);

  const observer = new MutationObserver((mutations) => {
    for (const mutation of mutations) {
      for (const node of mutation.addedNodes) {
        if (node.classList && node.classList.contains("faction-war")) {
          console.log("Caught a mutation");

          update_statuses();
          clear_watchers();
          extract_all_member_lis();
          create_watcher();
        }
      }
    }
  });

  function pad_with_zeros(n) {
    if (n < 10) {
      return "0" + n;
    }
    return n;
  }

  const wrapper = document.body; //.querySelector('#mainContainer')
  observer.observe(wrapper, { subtree: true, childList: true });

  const member_status = new Map();
  const member_lis = new Map();
  let watcher = null;

  let last_request = null;
  const MIN_TIME_SINCE_LAST_REQUEST = 5000;

  async function update_statuses() {
    if (
      last_request &&
      new Date() - last_request < MIN_TIME_SINCE_LAST_REQUEST
    ) {
      return;
    }
    last_request = new Date();
    const faction_ids = get_faction_ids();
    for (let i = 0; i < faction_ids.length; i++) {
      update_status(faction_ids[i]);
    }
  }

  async function update_status(faction_id) {
    let error = false;
    const status = await fetch(
      `https://api.torn.com/faction/${faction_id}?selections=basic&key=${apiKey}`,
    )
      .then((r) => r.json())
      .catch((m) => {
        console.error("[TornWarStuffEnhanced] ", m);
        error = true;
      });
    if (error) {
      return;
    }
    if (!status.members) {
      return;
    }
    for (const [k, v] of Object.entries(status.members)) {
      v.status.description = v.status.description
        .replace("South Africa", "SA")
        .replace("Cayman Islands", "CI")
        .replace("United Kingdom", "UK")
        .replace("Argentina", "Arg")
        .replace("Switzerland", "Switz");
      member_status.set(k, v);
    }
  }

  function extract_all_member_lis() {
    get_member_lists().forEach((ul) => {
      extract_member_lis(ul);
    });
  }

  function extract_member_lis(ul) {
    const lis = ul.querySelectorAll("LI");
    lis.forEach((li) => {
      const id = li
        .querySelector(`A[href^='/profiles.php']`)
        .href.split("ID=")[1];
      member_lis.set(id, li);
    });
  }

  function create_watcher() {
    watcher = setInterval(() => {
      let needsupdate = false;
      member_lis.forEach((li, id) => {
        const state = member_status.get(id);
        if (!state) {
          return;
        }
        const status = state.status;

        const status_DIV = li.querySelector("DIV.status");
        li.setAttribute("data-until", status.until);
        switch (status.state) {
          case "Abroad":
          case "Traveling":
            if (
              !(
                status_DIV.classList.contains("traveling") ||
                status_DIV.classList.contains("abroad")
              )
            ) {
              needsupdate = true;
              break;
            }
            if (status.description.includes("Traveling to ")) {
              li.setAttribute("data-sortA", "4");
              status_DIV.innerText =
                "► " + status.description.split("Traveling to ")[1];
            } else if (status.description.includes("In ")) {
              li.setAttribute("data-sortA", "3");
              status_DIV.innerText = status.description.split("In ")[1];
            } else if (status.description.includes("Returning")) {
              li.setAttribute("data-sortA", "2");
              status_DIV.innerText =
                "◄ " + status.description.split("Returning to Torn from ")[1];
            } else if (status.description.includes("Traveling")) {
              li.setAttribute("data-sortA", "5");
              status_DIV.innerText = "Traveling";
            }
            break;
          case "Hospital":
          case "Jail":
            if (
              !(
                status_DIV.classList.contains("hospital") ||
                status_DIV.classList.contains("jail")
              )
            ) {
              li.classList.remove("warstuff_highlight");
              li.classList.remove("warstuff_traveling");
              needsupdate = true;
              break;
            }
            li.setAttribute("data-sortA", "1");
            if (status.description.includes("In a")) {
              li.classList.add("warstuff_traveling");
            } else {
              li.classList.remove("warstuff_traveling");
            }

            const hosp_time_remaining = Math.round(
              status.until - new Date().getTime() / 1000,
            );
            if (hosp_time_remaining <= 0) {
              li.classList.remove("warstuff_highlight");
              return;
            }
            const s = Math.floor(hosp_time_remaining % 60);
            const m = Math.floor((hosp_time_remaining / 60) % 60);
            const h = Math.floor(hosp_time_remaining / 60 / 60);
            const time_string = `${pad_with_zeros(h)}:${pad_with_zeros(m)}:${pad_with_zeros(s)}`;

            // See if the DOM changed between the beginning and now
            if (
              !(
                status_DIV.classList.contains("hospital") ||
                status_DIV.classList.contains("jail")
              )
            ) {
              li.classList.remove("warstuff_highlight");
              li.classList.remove("warstuff_traveling");
              needsupdate = true;
              break;
            }
            if (status_DIV.innerText != time_string) {
              status_DIV.innerText = time_string;
            }

            if (hosp_time_remaining < 300) {
              li.classList.add("warstuff_highlight");
            } else {
              li.classList.remove("warstuff_highlight");
            }
            break;

          default:
            if (!status_DIV.classList.contains("okay")) {
              needsupdate = true;
            }
            li.setAttribute("data-sortA", "0");
            li.classList.remove("warstuff_highlight");
            li.classList.remove("warstuff_traveling");
            break;
        }
      });
      if (needsupdate) {
        update_statuses();
      }
      if (sort_enemies) {
        const nodes = get_member_lists();
        for (let i = 0; i < nodes.length; i++) {
          let lis = nodes[i].querySelectorAll("LI");
          let sorted_lis = Array.from(lis).sort((a, b) => {
            return (
              a.getAttribute("data-sortA") - b.getAttribute("data-sortA") ||
              a.getAttribute("data-until") - b.getAttribute("data-until")
            );
          });
          let sorted = true;
          for (let j = 0; j < sorted_lis.length; j++) {
            if (nodes[i].children[j] !== sorted_lis[j]) {
              sorted = false;
              break;
            }
          }
          if (!sorted) {
            sorted_lis.forEach((li) => {
              nodes[i].appendChild(li);
            });
          }
        }
      }
    }, 250);
  }

  function clear_watchers() {
    clearInterval(watcher);
    watcher = null;
  }
})();
长期地址
遇到问题?请前往 GitHub 提 Issues。