FFProgs

Minor improvements to FFlogs and progression tools.

当前为 2022-04-04 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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

  */
})();