iSkile / Replace lightshot links

// ==UserScript==
// @name         Replace lightshot links
// @version      1.4.2
// @description  Makes links to pictures from "Ligthshot" by direct
// @updateURL    https://openuserjs.org/meta/iSkile/Replace_lightshot_links.meta.js
// @copyright    2017, iSkile (https://openuserjs.org/users/iSkile)
// @author       iSkile
// @license      MIT
// @run-at       document-start
// @include      *://*/*
// @connect      prnt.sc
// @connect      image.prntscr.com
// @grant        GM_openInTab
// @grant        GM_xmlhttpRequest
// ==/UserScript==
/* jshint -W097 */
'use strict';

function loadWithReferrer(imgUrl, url, cb) {
  GM_xmlhttpRequest({
    method: "GET",
    url: imgUrl,
    responseType: 'blob',
    headers: {
      'referer': url,
      'origin': 'https://prnt.sc'
    },
    onload: function (resp) {
      if (resp.status === 200) {
        return cb(window.URL.createObjectURL(resp.response));
      }

      return loadWithProxy(url + '/direct', cb);
    },
  });
}

function loadWithProxy(url, cb) {
  GM_xmlhttpRequest({
    method: "POST",
    url: url,
    data: {
      url,
    },
    responseType: 'blob',
    onload: function (resp) {
      cb(window.URL.createObjectURL(resp.response));
    },
  });
}

let tooltip = null;

function getViewportSize(doc) {
  // Width : getViewportSize()[0]
  // Height: getViewportSize()[1]

  doc = doc || document;
  var elem = doc.compatMode === 'CSS1Compat' ? doc.documentElement : doc.body;
  return [elem.clientWidth, elem.clientHeight];
}

function Tooltip() {
  this.el = null;
  this.noLoadedAttr = 'data-load';

  //this.maxWidth = this.maxHeight = 500; //px

  this.maxWidth = getViewportSize()[0] / 2; //px
  this.maxHeight = getViewportSize()[1] / 2; //px

  this.imgSize = [this.maxWidth, this.maxHeight]; // w h, in px
  this.mousePos = [0, 0]; //x y, last mouse position
  this.init();
}

Tooltip.prototype.init = function () {
  if (!this.el) {
    let el = document.createElement('div');
    el.style.cssText = `
position: fixed;
transition: all .2s linear;
z-index: 2147483647;
box-shadow: black 0px 0px 20px 1px;
display: flex;
align-items: center;
justify-content: center;
background: #000000;
overflow: hidden;`;
    document.body.appendChild(el);

    // hotfix
    el.onclick = el.onmouseout = () => this.hide();

    this.el = el;
    this.hide();
  }

  return this.el;
};

Tooltip.prototype.resize = function (maxW, maxH) {
  //console.log(this, maxW, maxH);
  this.el.style.maxWidth = maxW + 'px';
  this.el.style.maxHeight = maxH + 'px';

  let w = this.el.offsetWidth;
  let h = this.el.offsetHeight;

  this.imgSize = [w, h];
};

Tooltip.prototype.move = function (x, y) {
  const Space = 14; // margin in px
  let w = getViewportSize()[0];
  let h = getViewportSize()[1];

  let elPos = [0, 0]; // x, y
  this.mousePos = [x, y];

  let newW = this.imgSize[0] + Space * 2 > w ? x - Space : this.imgSize[0];
  let newH = this.imgSize[1] + Space * 2 > h ? y - Space : this.imgSize[1];
  if (this.imgSize[0] != newW || this.imgSize[1] != newH) {
    this.resize(newW, newH);
  }

  if (x + this.imgSize[0] + Space * 2 > w) {
    elPos[0] = x - this.imgSize[0] - Space;
    if (elPos[0] < 0) elPos[0] = Space;
  }
  else {
    elPos[0] = x + Space;
  }

  if (y + this.imgSize[1] + Space * 2 > h) {
    elPos[1] = y - this.imgSize[1] - Space;
    if (elPos[1] < 0) elPos[1] = Space;
  }
  else {
    elPos[1] = y + Space;
  }

  this.el.style.left = elPos[0] + 'px';
  this.el.style.top = elPos[1] + 'px';
};

Tooltip.prototype.show = function (url, el) {
  while (this.el.hasChildNodes()) {
    this.el.removeChild(this.el.firstChild);
  }

  this.el.style.maxWidth = this.maxWidth + 'px';
  this.el.style.maxHeight = this.maxHeight + 'px';

  let img = document.createElement('img');

  img.style.cssText = `
display: block;
max-width: 100%;
height: auto;
max-height: 100%;
margin-left: auto;
margin-right: auto;`;

  img.onload = () => {
    let w = this.el.offsetWidth;
    let h = this.el.offsetHeight;

    this.imgSize = [w, h];
    this.move(this.mousePos[0], this.mousePos[1]);

    this.el.style.opacity = 1;
  };

  img.onerror = () => {
    let w = 16;
    let h = 16;

    this.imgSize = [w, h];
    this.move(this.mousePos[0], this.mousePos[1]);

    this.el.style.opacity = 1;
  };

  img.src = url;
  this.el.appendChild(img);
  this.el.style.display = 'flex';
};

Tooltip.prototype.hide = function (url) {
  this.el.style.display = 'none';
  this.el.style.opacity = 0;
};

function getRealUrl(el, cb) {
  const LoadTimeout = 5000;
  const UrlAttr = 'data-real-url';
  const UrlLoadAttr = 'data-real-url-load';
  const OldCursorAttr = 'data-old-cursor-name';
  const Regex = /<meta property="og:image" content="(.*?)"\/>/g;

  let url = el.href.replace('prntscr.com', 'prnt.sc');
  let attrUrl = el.getAttribute(UrlAttr);

  if (attrUrl) {
    return cb(attrUrl);
  }
  else {
    let oldCursorName = el.getAttribute(OldCursorAttr) || el.style.cursor;
    el.setAttribute(OldCursorAttr, oldCursorName);
    el.style.cursor = 'wait';

    let hardOpen = setTimeout(() => {
      el.removeAttribute(UrlLoadAttr);
      cb(false);
    }, LoadTimeout);
    let startTs = +new Date();

    if (el.getAttribute(UrlLoadAttr)) {
      return;
    }
    el.setAttribute(UrlLoadAttr, true);

    GM_xmlhttpRequest({
      method: 'GET',
      url: url,
      onload: function (response) {
        if (+new Date() - startTs >= LoadTimeout) {
          return false;
        }
        clearTimeout(hardOpen);

        if (response.status.toString().match(/^2/)) {
          let img = Regex.exec(response.response);
          //console.log(response.response, img);

          if (img) {
            let realURL = img[1];

            loadWithReferrer(realURL, url, resp => {
              realURL = resp;

              el.setAttribute(UrlAttr, realURL);
              el.removeAttribute(UrlLoadAttr);
              el.style.cursor = el.getAttribute(OldCursorAttr);

              cb(resp);
            });
          }
        }

        cb(false);
      }
    });
  }
}

function replace(root) {
  if (!root) return;

  let links = root.querySelectorAll('a[href*="://prntscr.com/"], a[href*="://prnt.sc/"], a[href*="://www.prntscr.com/"], a[href*="://www.prnt.sc/"]');

  for (let link of links) {
    link.onclick = function (e) {
      e.preventDefault();
      GM_openInTab(this.href, false);
    };

    link.onmouseenter = function (e) {
      getRealUrl(this, resp => {
        if (resp) {
          tooltip.move(e.x, e.y);
          tooltip.show(resp, this);
        }
      });

      //link.onmousemove = ev => tooltip.move(ev.x, ev.y);
      link.onmouseout = () => tooltip.hide();
    };
  }
}

let pingBody = setInterval(() => {
  if (document.body && document.links.length) {
    clearInterval(pingBody);
    tooltip = new Tooltip();
  }
}, 5);

if (document.defaultView.parent == document.defaultView) { // frame test
  //   if (~['prnt.sc'].indexOf(location.hostname)) {
  //     let interval = setInterval(() => {
  //       if (document.body) {
  //         document.body.style.display = 'none';
  //       }

  //       let under_img = document.querySelector('.under-image>img'); //#screenshot-image , meta[property="og:image"]
  //       let img = document.querySelector('#screenshot-image')

  //       if (under_img) {
  //         if (under_img.src != location.href) {
  //           let url = img.src;
  //           clearInterval(interval);

  //           document.write(`Redirect to: <a href="${url}">${url}</a>`); //stop load page
  //           location.replace(url);
  //         }
  //         img.onerror = () => {
  //           document.body.style.display = '';
  //           clearInterval(interval);
  //         }
  //       }
  //     }, 1);
  //   }

  var observer = new MutationObserver(function (mutations) {
    mutations.forEach(function (mutation) {
      mutation.addedNodes.forEach(function (addedNode) {
        if (addedNode.nodeType === 1) {
          replace(addedNode);
        }
      });
    });
  });

  observer.observe(document.documentElement, {
    childList: true,
    subtree: true
  });

  replace(document.body);
}