FF Progs

Adds a Button to view logs in xivanalysis also some minor improvements.

目前為 2023-04-30 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name                FF Progs
// @name:en             FF Progs
// @namespace           k_fizzel
// @version             1.0.3
// @author              Chad Bradly
// @description         Adds a Button to view logs in xivanalysis also some minor improvements.
// @description:en      Adds a Button to view logs in xivanalysis also some minor improvements.
// @website             https://www.fflogs.com/character/id/12781922
// @icon                https://assets.rpglogs.com/img/ff/favicon.png?v=2
// @match               https://*.etro.gg/*
// @match               https://*.fflogs.com/*
// @grant               unsafeWindow
// @grant               GM_addStyle
// @grant               GM_getValue
// @grant               GM_setValue
// @grant               GM_deleteValue
// @license             MIT License
// ==/UserScript==

(function () {
  "use strict";
  //check if the domain is fflogs.com
  if (/fflogs\.com/.test(location.hostname)) {
    // Adblocker
    $("#top-banner, .side-rail-ads, #bottom-banner, #subscription-message-tile-container, #playwire-video-container, #right-ad-box, #right-vertical-banner").remove();
    $("#table-container").css("margin", "0 0 0 0");

    if (/\/reports\/.+/.test(location.pathname)) {
      // Add XIV Analysis Button
      $("#filter-analyze-tab").before(
        `<a href="https://xivanalysis.com/report-redirect/${location.href}" target="_blank" class="big-tab view-type-tab" id="xivanalysis-tab"><span class="zmdi zmdi-time-interval"></span> <span class="big-tab-text"><br>xivanalysis</span></a>`
      );
      $("#xivanalysis-tab").click(() => {
        $("#xivanalysis-tab").attr("href", `https://xivanalysis.com/report-redirect/${location.href}`);
      });

      let jobs;
      const rankOnes = {};
      const tableContainer = document.querySelector("#table-container");

      const getHash = () => {
        return location.hash
          .replace("#", "")
          .split("&")
          .reduce((res, item) => {
            const parts = item.split("=");
            res[parts[0]] = parts[1];
            return res;
          }, {});
      };

      const characterAllStar = (rank, outOf, rDPS, rankOneRDPS) => {
        return Math.min(Math.max(100 * (rDPS / rankOneRDPS), 100 - (rank / outOf) * 100) + 20 * (rDPS / rankOneRDPS), 120);
      };

      const addASPToTables = () => {
        if (getHash().view === "rankings") {
          if (!GM_getValue("apiKey")) return;
          const rows = [];
          if (!jobs) {
            fetch(`https://www.fflogs.com/v1/classes?api_key=${GM_getValue("apiKey")}`)
              .then((res) => res.json())
              .then((data) => {
                jobs = data[0].specs;
                rows.forEach((row) => {
                  updatePoints(row);
                });
              })
              .catch((err) => console.error(err));
          } else {
            setTimeout(() => {
              rows.forEach((row) => {
                updatePoints(row);
              });
            }, 0);
          }

          const updatePoints = async (row) => {
            const rank = Number($(row).find("td:nth-child(2)").text().replace("~", ""));
            const outOf = Number($(row).find("td:nth-child(3)").text().replace(",", ""));
            const dps = Number($(row).find("td:nth-child(6)").text().replace(",", ""));
            const jobName = $(row).find("td:nth-child(5) > a").attr("class") || "";
            const jobName2 = $(row).find("td:nth-child(5) > a:nth-last-child(1)").attr("class") || "";
            const playerMetric = getHash().playermetric || "rdps";

            if (jobName2 !== "players-table-realm") {
              $(row)
                .find("td:nth-child(7)")
                .html(`<center><img src="https://cdn.7tv.app/emote/62523dbbbab59cfd1b8b889d/1x.webp" title="no api endpoint for combind damage" style="height: 15px;"></center>`);
              return;
            }

            const updateCharecterAllStar = async () => {
              $(row).find("td:nth-child(7)").html(characterAllStar(rank, outOf, dps, rankOnes[jobName][playerMetric]).toFixed(2));
            };

            if (!rankOnes[jobName]) {
              rankOnes[jobName] = {};
            }

            if (!rankOnes[jobName][playerMetric]) {
              const url = `https://www.fflogs.com/v1/rankings/encounter/${reportsCache.filterFightBoss}?metric=${playerMetric}&spec=${
                jobs.find((job) => job.name.replace(" ", "") === jobName)?.id
              }&api_key=${GM_getValue("apiKey")}`;
              fetch(url)
                .then((res) => res.json())
                .then((data) => {
                  rankOnes[jobName][playerMetric] = Number(data.rankings[0].total.toFixed(1));
                  updateCharecterAllStar();
                })
                .catch((err) => console.error(err));
            } else {
              updateCharecterAllStar();
            }
          };

          $(".player-table").each((_i, table) => {
            $(table)
              .find("thead tr th:nth-child(6)")
              .after(
                `<th class="sorting ui-state-default" tabindex="0" aria-controls="DataTables_Table_0" rowspan="1" colspan="1" aria-label="Patch: activate to sort column ascending"><div class="DataTables_sort_wrapper">Points<span class="DataTables_sort_icon css_right ui-icon ui-icon-caret-2-n-s"></span></div></th>`
              );
            $(table)
              .find("tbody tr")
              .each((_i, row) => {
                $(row)
                  .find("td:nth-child(6)")
                  .after(`<td class="rank-per-second primary main-table-number"><center><span class="zmdi zmdi-spinner zmdi-hc-spin" style="color:white font-size:24px"></center></span></td>`);
                rows.push(row);
              });
          });
        }
      };

      if (tableContainer) {
        const observer = new MutationObserver(addASPToTables);
        observer.observe(tableContainer, { attributes: true, characterData: true, childList: true });
      }
    }

    if (/\/zone\/.+/.test(location.pathname)) {
      const verticalContent = document.querySelector(".vertical-content");
      const contentAndAds = document.querySelector(".content--full-width");
      const pageContainer = document.querySelector(".progress-race-page-container");

      const removeAdds = () => {
        const elements = document.querySelectorAll(".content-and-ads__ads");
        const resize = document.querySelector(".content-and-ads--with-ads");
        const resize2 = document.querySelector(".content-and-ads__content");
        if (resize) {
          resize.style = "grid-template-columns: 1fr !important;";
          resize2.style = "grid-template-columns: 1fr !important;";
        }
        for (const element of elements) {
          if (element) {
            element.remove();
          }
        }
      };

      if (verticalContent) {
        const observer = new MutationObserver(removeAdds);
        observer.observe(verticalContent, { attributes: true, characterData: true, childList: true });
      }

      if (contentAndAds) {
        const observer = new MutationObserver(removeAdds);
        observer.observe(contentAndAds, { attributes: true, characterData: true, childList: true });
      }

      if (pageContainer) {
        const observer = new MutationObserver(removeAdds);
        observer.observe(pageContainer, { attributes: true, characterData: true, childList: true });
      }
    }

    if (/\/character\/.+/.test(location.pathname)) {
      $(".table-icon").removeAttr("alt");
      const jobOrder = [
        "Paladin",
        "Warrior",
        "DarkKnight",
        "Gunbreaker",
        "WhiteMage",
        "Scholar",
        "Astrologian",
        "Sage",
        "Monk",
        "Dragoon",
        "Ninja",
        "Samurai",
        "Reaper",
        "Bard",
        "Machinist",
        "Dancer",
        "BlackMage",
        "Summoner",
        "RedMage",
      ];
      const jobList = $("#jobs-header-icons").children();
      const jobListSortedNumbers = [];
      const jobListSorted = [];

      jobOrder.forEach((job) =>
        jobList
          .children()
          .toArray()
          .forEach((jobElement, i) => {
            if (jobElement.alt === job) jobListSortedNumbers.push(i);
          })
      );
      jobListSortedNumbers.forEach((i) => jobListSorted.push(jobList[i]));
      jobList.remove();
      $("#jobs-header-icons").append(jobListSorted);

      // Chad Bradly's Profile Customization
      if (/\/character\/id\/12781922/.test(location.pathname)) {
        $("#character-portrait-image").attr("src", "https://media.tenor.com/epNMHGvRyHcAAAAd/gigachad-chad.gif");
        $("#portrait-and-basics, #character-header-customize-action-box, #update-box").addClass("slightly-transparent-box");
        $("#character-portrait-box").css("background-image", 'url("https://i.imgur.com/dbwqHIt.png")').addClass("with-banner");
      }
    }

    if (/\/profile/.test(location.pathname)) {
      let apiKey = GM_getValue("apiKey");

      $("#api").after(`<div id="extension" class="dialog-block"></div>`);
      $("#extension").append(`<div id="extension-title" class="dialog-title">FF Progs</div>`);
      $("#extension").append(`<div id="extension-content" style="margin:1em"></div>`);

      const addApiInput = () => {
        $("#extension-content").append(`<div id="extension-contents" style="margin:1em">Enter your FFLogs API Key</div>`);
        $("#extension-content").append(`<input type=text id="apiKeyInput" style="margin-left: 10px">`);
        $("#extension-content").append(`<input type=button id="apiButton" style="margin-left: 10px" value="Save API Key">`);
        $("#apiButton").click(() => {
          apiKey = $("#apiKeyInput").val();
          GM_setValue("apiKey", apiKey);
          $("#apiButton").remove();
          $("#apiKeyInput").remove();
          $("#extension-content").append(`<div id="resloved-text" style="margin:1em">API Key Saved</div>`);
          setTimeout(() => {
            $("#extension-contents").remove();
            $("#resloved-text").remove();
            removeApiInput();
          }, 2000);
        });
      };

      const removeApiInput = () => {
        $("#extension-content").append(`<input type=button id="apiDeleteButton" style="margin-left: 10px" value="Remove API Key">`);
        $("#apiDeleteButton").click(() => {
          GM_deleteValue("apiKey");
          $("#apiDeleteButton").remove();
          $("#extension-content").append(`<div id="resloved-text" style="margin:1em">API Key Removed</div>`);
          setTimeout(() => {
            $("#resloved-text").remove();
            addApiInput();
          }, 2000);
        });
      };
      if (!apiKey) {
        addApiInput();
      } else {
        removeApiInput();
      }
    }
  }

  if (/etro\.gg/.test(location.hostname)) {
    document.querySelector(".mantine-Header-root").style.marginBottom = "0px";
    const body = document.querySelector(".mantine-AppShell-main");
    const removeAdGearset = () => {
      const elements = document.querySelectorAll('[id^="etro-ad"]');

      for (const element of elements) {
        if (element) {
          element.remove();
        }
      }
    };
    if (body) {
      const observer = new MutationObserver(removeAdGearset);
      observer.observe(body, { attributes: true, characterData: true, childList: true });
    }
  }
})();