FFProgs

Minor improvements to FFlogs and progression tools.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         FFProgs
// @namespace    http://tampermonkey.net/
// @version      0.1.0
// @description  Minor improvements to FFlogs and progression tools.
// @author       You
// @match        https://www.fflogs.com/*
// @icon         https://assets.rpglogs.com/img/ff/favicon.png?v=2
// @grant        none
// ==/UserScript==

(function () {
  if (/(^|\.)fflogs\.com$/.test(document.location.hostname) === false) { return; };

  // Helper functions
  function elements(arr) {
    return arr.map(e => document.getElementById(e));
  }

  function addGlobalEventListener(type, selector, callaBack) {
    document.addEventListener(type, e => {
      if (e.target.matches(selector)) callaBack(e);
    })
  }

  function retrieveWindowVariables(variables) {
    const ret = {};

    let scriptContent = "";
    for (let i = 0; i < variables.length; i++) {
      const currVariable = variables[i];
      scriptContent += `if (typeof ${currVariable} !== \"undefined\") $(\"body\").attr(\"tmp_${currVariable}\", JSON.stringify(${currVariable}));\n`
    }

    const script = document.createElement("script");
    script.id = "tmpScript";
    script.appendChild(document.createTextNode(scriptContent));
    (document.body || document.head || document.documentElement).appendChild(script);

    for (let i = 0; i < variables.length; i++) {
      const currVariable = variables[i];
      ret[currVariable] = $.parseJSON($("body").attr(`tmp_${currVariable}`));
      $("body").removeAttr(`tmp_${currVariable}`);
    }

    $("#tmpScript").remove();

    return ret;
  }

  // Easter Egg/Credits
  const characterName = document.querySelector("#character-name > .character-name-link");
  if (characterName) {
    if (characterName.innerHTML === "Chad Bradly") {
      // GIGACHAD
      const characterPortrait = document.getElementById("character-portrait-image");
      characterPortrait.src = "https://c.tenor.com/epNMHGvRyHcAAAAd/gigachad-chad.gif";

      // Adds background to your own page
      const addBackgroundList = ["portrait-and-basics", "character-header-customize-action-box", "update-box"];
      elements(addBackgroundList).forEach((e) => {
        if (e) {
          e.className = "slightly-transparent-box";
        }
      })
      const banner = document.getElementById("character-portrait-box");
      if (banner) {
        banner.style = "background-image: url(\"https://cdn.discordapp.com/attachments/613521566185029642/925189465868218368/ffxiv_dx11_sH6dfhzjue.jpg\");";
        banner.className = "with-banner";
      }
    }
  }

  // Adblock
  const deleteList = ["top-banner", "bottom-banner", "playwire-video-container", "patron-box", "gear-box-ad"];
  elements(deleteList).forEach(e => {
    if (e) {
      e.outerHTML = "";
    }
  });

  // Removes alt-text from item images.
  const imgs = document.getElementsByClassName("gear-img-cell");
  for (let i = 0; i < imgs.length; i++) {
    const img = imgs[i].firstChild;
    if (img) {
      img.alt = "";
    }
  }

  // XIVAnalysis button
  const tabs = document.getElementById("top-level-view-tabs");
  if (tabs) {
    function getReportUrl() {
      const reportURL = document.location.href.split("/reports/")[1];
      const [report, reportInfo] = reportURL.split("#");
      let fight, job;
      if (reportInfo) {
        reportInfo.split("&").forEach((e) => {
          const [key, value] = e.split("=")
          if (key === "fight") {
            fight = value;
          }
          if (fight && key === "source") {
            job = value;
          }
        })
      }
      let url = "https://xivanalysis.com/fflogs";
      [report, fight, job].forEach((urlElement) => {
        if (urlElement) {
          url += `/${urlElement}`;
        }
      });
      return url;
    }

    function refreshAnalysis() {
      const xivanalysisButton = document.getElementById("xivanalysis-tab");
      const url = getReportUrl();
      xivanalysisButton.href = url;
    }

    const url = getReportUrl();
    const xivanalysisButton = document.createElement("a");
    xivanalysisButton.href = url;
    xivanalysisButton.target = "_blank";
    xivanalysisButton.classList.add("big-tab", "view-type-tab");
    xivanalysisButton.id = "xivanalysis-tab";

    const icon = document.createElement("span");
    icon.classList.add("zmdi", "zmdi-time-interval");
    xivanalysisButton.appendChild(icon);

    const text = document.createElement("span");
    text.classList.add("big-tab-text");
    text.innerHTML = "<br>xivanalysis";
    xivanalysisButton.appendChild(text);

    tabs.firstChild.before(xivanalysisButton);

    tabs.addEventListener("click", (e) => { refreshAnalysis(); });
  }

  //Video Player Stuff
  const videoButton = document.querySelector(".replay-video");
  if (videoButton) {
    const streams = {};
    videoButton.addEventListener("click", (e) => {
      const selectVideoButton = document.getElementById("select-video");
      if (selectVideoButton) {
        // Functions for the multistream buttons.
        const videoFrame = document.getElementById("video-frame-inner");

        function showMultistreamFrame() {
          const platform = "youTube"
          const iframe = document.createElement("iframe");
          iframe.id = "player";
          iframe.style = "border: none; width:100%; height: 100%;";
          iframe.allowFullscreen = "1";
          iframe.allow = "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture;";
          iframe.title = "YouTube video player";

          videoFrame.innerHTML = iframe.outerHTML;
        }

        function showMultistreamOptions() {
          const div = document.createElement("div");
          div.style = "text-align: center;";

          const form = document.createElement("form");
          form.style = "margin: 0;";
          form.acceptCharset = "utf-8";
          form.method = "GET";
          form.action = "javascript:void(0);";

          const table = document.createElement("table");
          table.style = "border-collapse: separate; border-spacing: 8px; margin: auto; text-align: left;";

          //Table Infromation
          const infoRow = document.createElement("tr");
          const nameInfo = document.createElement("td");
          const URLInfo = document.createElement("td");
          const offsetInfo = document.createElement("td");
          nameInfo.innerHTML = "Name:";
          URLInfo.innerHTML = "Stream URL:";
          offsetInfo.innerHTML = "Offset:";
          offsetInfo.style = "max-width: 60px;";

          infoRow.append(nameInfo, URLInfo, offsetInfo);
          table.appendChild(infoRow);

          const raidFrames = document.querySelectorAll(".raid-frame-contents");
          const raiders = [];
          raidFrames.forEach((frame) => {
            raiders.push(frame.innerHTML);
          })

          raiders.forEach((person) => {
            const id = person.replace(/ /g, "_");

            const tableRow = document.createElement("tr");
            const nameData = document.createElement("td");
            nameData.innerHTML = person;
            tableRow.appendChild(nameData);
            const streamData = document.createElement("td");
            const streamUrl = document.createElement("input");
            streamUrl.style = "min-width: 260px;"
            streamUrl.type = "text";
            streamUrl.id = `${id}-stream_url`;
            streamUrl.name = `${id}-stream_url`;
            streamUrl.placeholder = "youtube.com/watch?v= or twitch.tv/videos/";
            if (streams[id] && streams[id].url) {
              streamUrl.setAttribute("value", streams[id].url);
            }
            streamUrl.classList.add("url-table-row");
            streamData.appendChild(streamUrl);
            tableRow.appendChild(streamData);

            const offsetData = document.createElement("td");
            const offsetTime = document.createElement("input");
            offsetTime.size = "3";
            offsetTime.id = `${id}-stream_offset`;
            offsetTime.name = `${id}-stream_offset`;
            offsetTime.placeholder = "# in sec";
            if (streams[id] && streams[id].offset) {
              offsetTime.setAttribute("value", streams[id].offset)
            }
            offsetTime.classList.add("url-table-row");

            offsetData.appendChild(offsetTime);
            tableRow.appendChild(offsetData);

            table.appendChild(tableRow);
          })

          form.appendChild(table);
          div.appendChild(form);
          videoFrame.innerHTML = div.outerHTML;
        }

        addGlobalEventListener("input", ".url-table-row", (e) => {
          const [user, action] = e.target.id.split("-");
          const value = e.target.value;
          if (action === "stream_url") {
            if (!streams[user]) streams[user] = {};
            streams[user].url = value;
          }
          if (action === "stream_offset") {
            if (!streams[user]) streams[user] = {};
            streams[user].offset = value;
          }
        })

        // Creates Menu Below Video Player
        const multiStreamOptions = document.getElementById("multistream-options");
        if (!multiStreamOptions) {

          const videoFrameControls = document.getElementById("video-frame-controls");
          const multiStreamO = document.createElement("span");
          multiStreamO.style = "float: right; margin-right: 10px;";
          multiStreamO.id = "multistream-options";
          multiStreamO.classList.add("graph-legend-button");
          multiStreamO.onclick = showMultistreamOptions;
          multiStreamO.innerText = "Multistream options"
          videoFrameControls.appendChild(multiStreamO);

          const multiStreamV = document.createElement("span");
          multiStreamV.style = "margin-right: -1px; float: right;";
          multiStreamV.id = "multistream-view";
          multiStreamV.classList.add("graph-legend-button");
          multiStreamV.onclick = showMultistreamFrame;
          multiStreamV.innerText = "Multistream View";
          videoFrameControls.appendChild(multiStreamV);
        }

        // Save Video URL (OLD)
        selectVideoButton.addEventListener("click", (e) => {
          const windowVariables = retrieveWindowVariables(["videoID", "videoOffset"]);
          if (windowVariables.videoID !== "none") {
            const videoURLInput = document.getElementById("video_url");
            videoURLInput.value = `https://www.youtube.com/watch?v=${windowVariables.videoID}`;
          }
          if (windowVariables.videoOffset) {
            const videoOffsetInput = document.getElementById("video_offset");
            videoOffsetInput.value = windowVariables.videoOffset;
          }
        })
      }
    })
  }



  /*
  Backlog:
    Make youtube/twitch livestream work
    Streams work across multiple fights in one log.
    <- Upload to greasy fork here 0.1.0 ->
    Multiple POV"s depending on selected player.
    Add way to share log viewer and save current streams to a log.

  Doing:
    Make youtube videos work.
    Make Twitch VOD"s work.
    Import/export settings
    Use my Own I frames


    Done:
    Save video inputted into video player,
    Make settings be able to add streams to players
    Save Stream/Offset to session storage on edit for the zones so no need for submit

  */
})();