Xegor / Bungie.net Enhanced LFG

// ==UserScript==
// @namespace    https://openuserjs.org/users/Xegor
// @name         Bungie.net Enhanced LFG
// @description  Adds some functionality to Bungie.net, such as improved LFG.
// @author       Xegor
// @copyright    2019, Xegor (https://openuserjs.org/users/Xegor)
// @license      0BSD
// @version      0.0.7
// @match        https://www.bungie.net/*
// @grant        none
// @updateURL    https://openuserjs.org/meta/Xegor/Bungie.net_Enhanced_LFG.meta.js
// @downloadURL  https://openuserjs.org/src/scripts/Xegor/Bungie.net_Enhanced_LFG.user.js
// ==/UserScript==

// ==OpenUserJS==
// @author Xegor
// ==/OpenUserJS==

(function () {
  'use strict';

  // Improvements to LFG page
  function setupFireteamSearch() {
    if (window.location.pathname.indexOf("ClanV2/FireteamSearch") == -1) {
      return;
    }

    const css = document.createElement("style");
    css.type = "text/css";
    css.innerText = `
  .RefreshIcon {
  }
  .RefreshIcon.RefreshIconBusy {
    color: tomato;
    -webkit-animation: 0.5s linear 0s 1 RefreshIconSpin;
    -moz-animation: 0.5s linear 0s 1 RefreshIconSpin;
    animation: 0.5s linear 0s 1 RefreshIconSpin;
  }

  @keyframes RefreshIconSpin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } }
    `;
    document.body.appendChild(css);

    const refs = {
      refreshButton: null
    };

    function addDiv(html) {
      const div = document.createElement("div");
      div.style.marginTop = "auto";
      div.style.marginBottom = "auto";
      div.innerHTML = html;
      document.getElementsByClassName("options-container")[0].appendChild(div);
      return div;
    }

    function refreshList() {
      refs.refreshButton.classList.add("RefreshIconBusy");

      let event = document.createEvent('Event');
      event.initEvent('change', true, true);
      document.getElementsByName("activityType")[1].dispatchEvent(event);
      console.log("refresh");
    }

    let autorefreshHandle = null;
    let autorefreshEnabled = false;
    let autorefreshInterval = 0;
    let windowActive = true;

    function updateAutorefresh(forceImmediate) {
      if (autorefreshHandle !== null) {
        clearTimeout(autorefreshHandle);
        autorefreshHandle = null;
      }

      if (autorefreshEnabled && windowActive) {
        if (forceImmediate) {
          refreshList();
        }
        else {
          autorefreshHandle = setTimeout(refreshList, (autorefreshInterval * 1000) | 0);
        }
      }
    }

    window.onfocus = (function () {
      windowActive = true;
      updateAutorefresh(true);
    });

    window.onblur = (function () {
      windowActive = false;
      updateAutorefresh();
    });

    const prestigeRegexp = /prestige|(\bpres(t)?\b)/i;
    const activityRegexps = [
      { activity: "Raid", regexp: /\bsos\b|spire/i, icon: "//www.bungie.net/common/destiny2_content/icons/70422198861e09744fbc020bf1925be5.jpg" },
      { activity: "Raid", regexp: /\beow\b|eater/i, icon: "//www.bungie.net/common/destiny2_content/icons/69adea04559ff05a3422358109747187.jpg" },
      { activity: "Raid", regexp: /levi|\blev\b/i, icon: "//www.bungie.net/common/destiny2_content/icons/3edffbccc60b995c5e5deed0b7f07c1c.jpg" },
      { activity: "Raid", regexp: /wish|(\blw\b)|riven|shuro|kalli|morgeth|vault|queenswalk/i, icon: "//www.bungie.net/common/destiny2_content/icons/ecc3e805988dd9947f37c46428e4a12b.jpg" },
      { activity: "Raid", regexp: /scourge|(\bsotp\b)/i, icon: "//www.bungie.net/common/destiny2_content/icons/1879398bc8a50d47cdd14cc746c073e1.jpg" },
      { activity: "Raid", regexp: /crown|(\bcos\b)/i, icon: "//www.bungie.net/common/destiny2_content/icons/6039feb4c132650cab776f8cb872441d.jpg" },
    ];

    //
    // Filter by text
    //
    let div = addDiv(`
  <div class="form-element companion-text-input">
  <input type="text" id="text_filter" name="text_filter" placeholder="Filter by text..." onsubmit="return false;"/>
  </div>
   `);

   const textFilter = document.getElementById("text_filter");

    function updateFilters() {
      const fireteams = document.getElementsByClassName("item-fireteam");
      let regexps = [];

      let filterText = textFilter.value ? textFilter.value.trim() : "";
      if (filterText[0] === '/' && filterText[filterText.length - 2] === '/') {
        let re = filterText.substring(1, filterText.length - 2);
        if (re.length) {
          try {
            regexps.push(new RegExp(re, "i"));
          } catch (err) { }
        }
      }
      else {
        let words = filterText.split(/\s+/);
        for (let i = 0; i < words.length; ++i) {
          let word = words[i].trim().replace(/\*/g, "\\S*");
          //word = "\\b" + word + "\\b";
          try {
            regexps.push(new RegExp(word, "i"));
          } catch (err) { }
        }
      }

      for (let i = 0; i < fireteams.length; ++i) {
        const title = fireteams[i].getElementsByClassName("title")[0];
        let teamName = title.lastChild.textContent;
        let ok = true;
        for (let j = 0; j < regexps.length; ++j) {
          ok = ok && regexps[j].test(teamName);
        }

        fireteams[i].style.display = ok ? "" : "none";

        let actIcon = fireteams[i].getElementsByClassName("activity-icon")[0];
        let type = actIcon.getAttribute("data-activity");
        let icon = "";
        for (let j = 0; j < activityRegexps.length; ++j) {
          let act = activityRegexps[j];
          if ((!act.activity || type == act.activity) && (!act.regexp || act.regexp.test(teamName))) {
            icon = act.icon;
            break;
          }
        }
        if (icon) {
          actIcon.style.backgroundImage = 'url("' + icon + '")';
          actIcon.style.backgroundSize = 'cover';

          let borderColor = "white";
          if (prestigeRegexp.test(teamName)) {
            borderColor = "gold";
          }


          actIcon.style.outline = "2px solid " + borderColor;
        }

        /*if (icon && title.firstChild == title.lastChild) {
            let div = document.createElement("div");
            //div.innerText = "[" + type + "]";
            div.style.marginRight = "8px";
            div.style.display = "inline-block";
            div.style.height = "32px";
            div.style.width = "32px";
            div.style.verticalAlign = "middle";
            div.style.backgroundImage = 'url("' + icon + '")';
            div.style.backgroundSize = 'contain';
            title.insertBefore(div, title.firstChild);
        }*/
      }

      refs.refreshButton.classList.remove("RefreshIconBusy");
    }

    textFilter.value = localStorage.getItem("TextFilter", "");
    textFilter.oninput = function () {
      localStorage.setItem("TextFilter", textFilter.value);
      updateFilters();
    };


    const observer = new MutationObserver(function (mutationsList, observer) {
      updateFilters();
      updateAutorefresh();
    });
    observer.observe(document.getElementsByClassName("clanResults")[0], { childList: true, subtree: true });



    //
    // Spacer
    //
    addDiv("").style.flexGrow = 1;



    //
    // Refresh
    //


    const autorefreshDiv = addDiv(`
  <div class="form-element" style="display: inline-block;">
    <input type="checkbox" id="autorefresh_enabled" name="a1" />
  </div>
  <div class="form-element companion-text-input" style="display: inline-block;">
  <input type="text" id="autorefresh_interval" name="a2" placeholder="0" style="width: 2em;" onsubmit="return false;"/>
    <span style="color: white";>s</span>
  </div>
   `);
   const autorefreshChk = document.getElementById("autorefresh_enabled");
   const autorefreshInput = document.getElementById("autorefresh_interval");

    //autorefreshChk.checked = localStorage.getItem("AutorefreshEnabled", "") == "1";
    autorefreshEnabled = autorefreshChk.checked;
    autorefreshChk.onchange = function () {
      autorefreshEnabled = autorefreshChk.checked;
      //localStorage.setItem("AutorefreshEnabled", autorefreshEnabled ? "1" : "0");
      updateAutorefresh();
    };

    autorefreshInput.value = localStorage.getItem("AutorefreshInterval", "");
    autorefreshInterval = Math.max(0, parseFloat(autorefreshInput.value || "") || 0);
    autorefreshInput.oninput = function () {
      autorefreshInterval = Math.max(0, parseFloat(autorefreshInput.value || "") || 0);
      localStorage.setItem("AutorefreshInterval", autorefreshInterval);
      updateAutorefresh();
    };

    const refreshDiv = addDiv(`
  <div class="nav_top">
    <div class="RefreshIcon notification-badges" style="padding-top: 0;"><i class="material-icons">refresh</i></div>
  </div>
   `);
    refs.refreshButton = refreshDiv.getElementsByClassName("notification-badges")[0];
    refs.refreshButton.onclick = function () {
      refreshList();
    };



    updateFilters();
    updateAutorefresh();

  }

  // Add blizzard IDs on profile pages, for easy copying
  function setupProfilePage() {
    const sv = window.ServerVars;
    if (sv && sv.PageData && sv.PageData.OnPageUser) {
      const id = sv.PageData.OnPageUser.MembershipData.bungieNetUser.blizzardDisplayName;
      const divs = document.getElementsByClassName("unique-name");
      if (divs.length == 1) {
        divs[0].innerHTML += "<div>" + id + "</div>";
      }
    }
  }

  // Improvements to fireteam actions in LFG:
  // - Add button to fetch user ids on fireteam view
  function setupFireteamActions() {
    function selectNodeText(textNode) {
      if (document.body.createTextRange) { // ms
        const range = document.body.createTextRange();
        range.moveToElementText(textNode);
        range.select();
      } else if (window.getSelection) { // moz, opera, webkit
        const selection = window.getSelection();
        const range = document.createRange();
        range.selectNodeContents(textNode);
        selection.removeAllRanges();
        selection.addRange(range);
      }
    }

    function fetchName(div) {
      fetch(div.href)
        .then(function (response) {
          return response.text();
        })
        .then(function (data) {
          const re = /"blizzardDisplayName":\s*"([^"]+)"/;
          const match = re.exec(data);
          if (match) {
            const span = document.createElement("span");
            span.style.marginLeft = "1em";
            span.innerHTML = match[1];
            span.onclick = function () {
              selectNodeText(span);
            };
            div.parentNode.appendChild(span);
            
            const a = document.createElement("a");
            a.style.marginLeft = "1em";
            a.href = "https://destinytracker.com/d2/profile/pc/" + match[1].replace(/#/, "-");
            a.target = "_blank";
            a.style.display = "inline-block";
            a.style.verticalAlign = "middle";
            a.style.width = "32px";
            a.style.height = "20px";
            a.style.backgroundImage = "url('https://destinytracker.com/Images/DestinyTracker/FavIcon/favicon-32.png')";
            a.style.backgroundPosition = "center";
            div.parentNode.appendChild(a);
          }
        });
    }

    const divs = document.getElementsByClassName("fireteam-actions");
    if (divs.length) {
      const a = document.createElement("a");
      a.className = "btn_fireteam button small gold";
      a.innerHTML = "Get Blizzard Ids";
      a.onclick = function () {
        const names = document.getElementsByClassName("display-name");
        for (let i = 0; i < names.length; ++i) {
          fetchName(names[i]);
        }
        a.parentNode.removeChild(a);
        return false;
      };
      divs[0].appendChild(a);
    }

  }


  setupFireteamSearch();
  setupProfilePage();
  setupFireteamActions();

})();