Tampermonkey/Greasemonkey Scripts

Install Tampermonkey, then create a new script and paste the code.

FFlogs XIVAnalysis Button

@match: https://www.fflogs.com/reports/*

Adds a button linking to XIVAnalysis based on the current fight parameter in FFLogs reports. Generated by AI so don't judge it too hard.

// ==UserScript==
// @name         FFLogs → XIVAnalysis Fight Button
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Adds a button linking to XIVAnalysis based on the current fight parameter in FFLogs reports.
// @match        https://www.fflogs.com/reports/*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(() => {
  'use strict'

  const BUTTON_ID = 'xivanalysis-button'

  function getFightParam() {
    const url = new URL(window.location.href)
    return url.searchParams.get('fight')
  }

  function getReportID() {
    // /reports/{reportID}...
    const m = window.location.pathname.match(/\/reports\/([^/]+)/)
    return m?.[1] ?? null
  }

  function ensureButton(container) {
    let button = document.getElementById(BUTTON_ID)
    if (!button) {
      button = document.createElement('a')
      button.id = BUTTON_ID
      button.className = 'big-tab view-type-tab'
      button.innerHTML = \`
        <span class="zmdi zmdi-search-in-file"></span>
        <span class="big-tab-text"><br>XIVAnalysis</span>
      \`
      container.appendChild(button)
    } else if (button.parentElement !== container) {
      // If FFLogs re-rendered tabs, move our button back into the right container
      container.appendChild(button)
    }
    return button
  }

  function updateButton() {
    const fight = getFightParam()
    const reportID = getReportID()

    const existing = document.getElementById(BUTTON_ID)

    // If we can't build a valid link, remove the button if it exists
    if (!fight || !reportID) {
      if (existing) existing.remove()
      return
    }

    const container = document.getElementById('top-level-view-tabs')
    if (!container) return // wait for SPA to render

    const button = ensureButton(container)
    button.href = \`https://xivanalysis.com/fflogs/\${reportID}/\${fight}\`
    button.target = '_blank'
    button.rel = 'noopener noreferrer'
  }

  // Fire update when history changes (SPA navigation)
  ;(history => {
    const pushState = history.pushState
    const replaceState = history.replaceState

    history.pushState = function () {
      const ret = pushState.apply(this, arguments)
      window.dispatchEvent(new Event('locationchange'))
      return ret
    }

    history.replaceState = function () {
      const ret = replaceState.apply(this, arguments)
      window.dispatchEvent(new Event('locationchange'))
      return ret
    }
  })(window.history)

  window.addEventListener('popstate', () => window.dispatchEvent(new Event('locationchange')))
  window.addEventListener('locationchange', updateButton)

  // Observe DOM changes because FFLogs frequently re-renders the tab bar
  const obs = new MutationObserver(() => updateButton())
  obs.observe(document.documentElement, { childList: true, subtree: true })

  // Initial run
  updateButton()
})()