Hover Alternative Front-Ends

Pops up a floating div when you hover over a link, containing alternative front-ends!

// ==UserScript==
// @name           Hover Alternative Front-Ends
// @namespace      HAFE
// @description    Pops up a floating div when you hover over a link, containing alternative front-ends!
// @homepageURL    https://greasyfork.org/en/scripts/437920-hover-alternative-front-ends
// @match          *://*/*
// @grant          none
// @run-at         document-idle
// @version        1.2.3
// ==/UserScript==

// This script is a fork of 'Hover Preview':
// https://greasyfork.org/en/scripts/8042-hover-preview

const focusReactionTime = 300;
const unfocusReactionTime = 1500;

const domains = {
  youtube: ["youtube.com","youtu.be","youtube-nocookie.com"],
  twitter: ["twitter.com","twimg.com"],
  reddit: ["reddit.com","i.redd.it"],
  instagram: ["instagram.com"],
  imgur: ["imgur.com"],
  medium: ["medium.com"],
  wikipedia: ["en.wikipedia.org"]
};

const youtubeFrontends = [{
  name: "Invidious(Snopyta)",
  address: "invidious.snopyta.org"
},{
  name: "Invidious(Yewtube)",
  address: "yewtu.be"
},{
  name: "Invidious(Puffyan)",
  address: "vid.puffyan.us"
},{
  name: "Invidious(Seth)",
  address: "invidious.sethforprivacy.com"
},{
  name: "CloudTube",
  address: "tube.cadence.moe"
},{
  name: "Piped",
  address: "piped.kavin.rocks",
}];

const twitterFrontends = [{
  name: "Nitter",
  address: "nitter.net"
},{
  name: "Nitter(Snopyta)",
  address: "nitter.snopyta.org"
},{
  name: "Nitter(Puss)",
  address: "nitter.pussthecat.org"
},{
  name: "Nitter(42l)",
  address: "nitter.42l.fr"
},{
  name: "Nitter(Seth)",
  address: "nitter.sethforprivacy.com"
},{
  name: "Nitter(Action Sack)",
  address: "nitter.actionsack.com"
}];

const redditFrontends = [{
  name: "Teddit",
  address: "teddit.net"
},{
  name: "Teddit(Puss)",
  address: "teddit.pussthecat.org"
},{
  name: "Teddit(Seth)",
  address: "teddit.sethforprivacy.com"
},{
  name: "Libreddit",
  address: "libredd.it"
},{
  name: "Libreddit(Spike)",
  address: "libreddit.spike.codes"
},{
  name: "Libreddit(Puss)",
  address: "libreddit.pussthecat.org"
}];

const instagramFrontends = [{
  name: "Bibliogram",
  address: "bibliogram.art"
},{
  name: "Bibliogram(Snopyta)",
  address: "bibliogram.snopyta.org"
},{
  name: "Bibliogram(Puss)",
  address: "bibliogram.pussthecat.org"
},{
  name: "Bibliogram(Action Sack)",
  address: "bib.actionsack.com"
},{
  name: "Bibliogram(Hamster)",
  address: "bibliogram.hamster.dance"
}];

const imgurFrontends = [{
  name: "Imgin",
  address: "imgin.voidnet.tech"
},{
  name: "Ringu(Action Sack)",
  address: "i.actionsack.com"
},{
  name: "Kageurufu",
  address: "imgur.kageurufu.net"
}];

const mediumFrontends = [{
  name: "Scribe",
  address: "scribe.rip"
},{
  name: "Scribe(NixNet)",
  address: "scribe.nixnet.services"
}];

const wikipediaFrontends = [{
  name: "Wikiless",
  address: "wikiless.org"
},{
  name: "Wikiless(Seth)",
  address: "wikiless.sethforprivacy.com"
},{
  name: "Infogalactic",
  address: "infogalactic.com"
}];

var focus = undefined;
var lastFocus = undefined;
var timer = null;

var hafePopup;
var hafeFrame;

var isOverPopup = false;

function checkFocus() {
  if (focus) {
    // if (focus == lastFocus) {
      // User has definitely been here a while
      showHAFEWindow(focus);
    // } else {
    // }
    // lastFocus = focus;
  }
}

function aMouseOver(evt) {
  if (evt.currentTarget.tagName !== "A") {
          alert(decodeURIComponent("not link"));
    return;
  }
  if (!focus) {
    focus = evt.currentTarget;
    // setTimeout('checkFocus();',focusReactionTime);
    // Hack to bring the popup back immediately if we've gone back to the same link.
    if (hafeFrame && focus.href && hafeFrame.href == focus.href) {
      showHAFEWindow(focus,evt);
    } else {
      if (timer) {
        clearTimeout(timer);
      }
      timer = setTimeout(checkFocus,focusReactionTime);
    }
  } else {
    window.status = "Already focused on a link wtf!";
  }
}

function aMouseOut(evt) {
  if (evt.currentTarget.tagName !== "A") {
    return;
  }
  focus = undefined;
  if (timer) {
    clearTimeout(timer);
  }
  // TESTING: Don't hide the popup if mouse is currently over the popup!
  timer = setTimeout(clearPopup,unfocusReactionTime);
}

function clearPopup(e) {
  if (isOverPopup || focus)
    return;
  if (hafePopup) {
    // hafePopup.parentNode.removeChild(hafePopup);
    // hafePopup = undefined; // eww cache it!
    hafePopup.style.display = 'none';
  }
}

// DONE: If the user clicks a link, this isn't really a hover, so we should not
// activate and just let the user's click be processed!
function aClick(evt) {
  focus = undefined;
}

function createPopup() {
  // Create frame
  hafePopup = document.createElement('DIV');
  /** Seems style does not work for Konqueror this way. **/
  hafePopup.innerHTML =
    "<STYLE type='text/css'> .hafediv { background-color: #21242C; margin: 0px; padding: 2px; border: 1px solid dodgerblue; border-radius: 4px; text-align: center; box-sizing: border-box; } .hafediv a { font-family: Helvetica; font-size: 14px; color: white; text-decoration: none; padding: 0 5px; box-shadow: inset 0 0 0 0 #21242C; transition: all 0.4s ease-in-out 0s; border-radius: 3px; box-sizing: border-box; } .hafediv a:hover { box-shadow: inset 0 300px 0 0 dodgerblue; color: white; } </STYLE>"
    +
    "<DIV class='hafediv' width='" + (window.innerWidth * 0.75) + "' height='" + (window.innerHeight*0.75) + "' src='about:blank'></DIV>";
  hafePopup.addEventListener("mouseover", function(evt) { isOverPopup=true; }, false);
  hafePopup.addEventListener("mouseout", function(evt) { isOverPopup=false; setTimeout(clearPopup,unfocusReactionTime); }, false);
  document.documentElement.appendChild(hafePopup);
  hafePopup.style.position = "absolute";
  hafePopup.style.zIndex = "10000";
  hafeFrame = hafePopup.getElementsByTagName('DIV')[0];
}

function insertLink(linkAddress, linkText) {
  var newLink = document.createElement('a');
  newLink.href = linkAddress
  newLink.textContent = linkText
  hafeFrame.append(newLink);
  hafeFrame.append(document.createTextNode(" "));
}

function insertLinks(link) {
  hafeFrame.innerHTML = "";

  var linkHost = link.href.match(/^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i);
  var linkPath = link.href.replace(linkHost[0], "");

  for (var domain in domains) {
    for (var target=0; target < domains[domain].length; target++) {
      if (linkHost[0].includes(domains[domain][target])) {
        switch (domain) {
          case "youtube":
          if (linkHost[1].includes("studio")) {
            return false;
          } else {
            for (var index=0; index < youtubeFrontends.length; index++) {
              insertLink(link.href.replace(linkHost[1], youtubeFrontends[index].address), youtubeFrontends[index].name);
            }
          }
          break;

          case "twitter":
          if (linkHost[1].includes("pbs") || linkHost[1].includes("video")) {
            for (var index=0; index < twitterFrontends.length; index++) {
              insertLink(linkHost[0].replace(linkHost[1], twitterFrontends[index].address) + "pic/" + encodeURIComponent(link.href), twitterFrontends[index].name);
            }
          } else {
            for (var index=0; index < twitterFrontends.length; index++) {
              insertLink(link.href.replace(linkHost[1], twitterFrontends[index].address), twitterFrontends[index].name);
            }
          }
          break;

          case "reddit":
          if (domains[domain][target] === "i.redd.it") {
            for (var index=0; index < redditFrontends.length; index++) {
              if (redditFrontends[index].name.includes("Libreddit")) {
                insertLink(linkHost[0].replace(linkHost[1], redditFrontends[index].address) + "img/" + linkPath, redditFrontends[index].name);
              }
            }
          } else {
            for (var index=0; index < redditFrontends.length; index++) {
              insertLink(link.href.replace(linkHost[1], redditFrontends[index].address), redditFrontends[index].name);
            }
          }
          break;

          case "instagram":
          const instagramPaths = /\b(?:tv|reels?|u|p)\b/i;
          const instagramIgnore = /\b(?:stories|accounts|explore|topics)\b/i;
          var linkFolders = linkPath.split(/(?:\?|\/)+/);

          if (linkHost[1].includes("about") || linkHost[1].includes("help") || linkFolders[0].match(instagramIgnore)) {
            return false;
          } else if (linkFolders.length > 0 && linkFolders[0].match(instagramPaths)) {
            for (var index=0; index < instagramFrontends.length; index++) {
              insertLink(link.href.replace(linkHost[1], instagramFrontends[index].address), instagramFrontends[index].name);
            }
          } else if (linkFolders.length > 1 && linkFolders[1].match(instagramPaths)) {
            for (var index=0; index < instagramFrontends.length; index++) {
              insertLink(linkHost[0].replace(linkHost[1], instagramFrontends[index].address) + linkPath.replace(linkFolders[0] + "/", ""), instagramFrontends[index].name);
            }
          } else {
            for (var index=0; index < instagramFrontends.length; index++) {
              insertLink(link.href.replace(linkHost[1], instagramFrontends[index].address + "/u"), instagramFrontends[index].name);
            }
          }
          break;

          case "imgur":
          for (var index=0; index < imgurFrontends.length; index++) {
            insertLink(link.href.replace(linkHost[1], imgurFrontends[index].address), imgurFrontends[index].name);
          }
          break;

          case "medium":
          for (var index=0; index < mediumFrontends.length; index++) {
            insertLink(link.href.replace(linkHost[1], mediumFrontends[index].address), mediumFrontends[index].name);
          }
          break;

          case "wikipedia":
          for (var index=0; index < wikipediaFrontends.length; index++) {
            if (wikipediaFrontends[index].name.includes("Infogalactic")) {
              insertLink(linkHost[0].replace(linkHost[1], wikipediaFrontends[index].address) + linkPath.replace("wiki", "info"), wikipediaFrontends[index].name);
            } else {
              insertLink(link.href.replace(linkHost[1], wikipediaFrontends[index].address), wikipediaFrontends[index].name);
            }
          }
          break;

          default:
          alert(decodeURIComponent(link.href));
        }
        return true;
      }
    }
  }
  return false;
}

function showHAFEWindow(link,evt) {
  if (!hafeFrame) {
    createPopup();
  }
  if (insertLinks(link)) {
    hafePopup.style.display = '';
    hafePopup.style.top = ((link.getBoundingClientRect().top + window.scrollY) - hafePopup.getBoundingClientRect().height) + "px";
    hafePopup.style.left = link.getBoundingClientRect().left + "px";
  } else {
    hafePopup.style.display = 'none';
  }
}

function init() {
  for (var i=0; i < document.links.length; i++) {
    var link = document.links[i];
    /** Apparently deprecated. **/
    // link.onmouseover = aMouseOver;
    // link.onmouseout = aMouseOut;
    /** The new way: **/
    link.addEventListener("mouseover", aMouseOver, false);
    link.addEventListener("mouseout", aMouseOut, false);
    link.addEventListener("click", aClick, false);
    //addEvents(link);
    // link.addEventListener("mousemove", function(evt) { locate(evt); }, true);
  }
}

init();

var observer = new MutationObserver(init);

observer.observe(document.body, { subtree: true, childList: true });
//observer.disconnect();

// window.document.checkFocus = checkFocus;