Gelbooru Endless Scroll

Adds endless scroll function to Gelbooru

Från och med 2017-06-05. Se den senaste versionen.

// ==UserScript==
// @id             gelbooru-endless-scroll
// @name           Gelbooru Endless Scroll
// @version        1.6.7
// @namespace      intermission
// @author         intermission
// @license        WTFPL; http://www.wtfpl.net/about/
// @description    Adds endless scroll function to Gelbooru
// @include        http://gelbooru.com/index.php?*
// @include        https://gelbooru.com/index.php?*
// @include        http://rule34.xxx/index.php?*
// @include        https://rule34.xxx/index.php?*
// @include        http://*.booru.org/index.php?*
// @include        https://*.booru.org/index.php?*
// @include        http://e621.net/post/index/*
// @include        https://e621.net/post/index/*
// @include        http://rule34.paheal.net/post/list/*
// @include        https://rule34.paheal.net/post/list/*
// @run-at         document-end
// @grant          none
// ==/UserScript==

/* This program is free software. It comes without any warranty, to
 * the extent permitted by applicable law. You can redistribute it
 * and/or modify it under the terms of the Do What The Fuck You Want
 * To Public License, Version 2, as published by Sam Hocevar. See
 * http://www.wtfpl.net/ for more details. */

(function() {
  "use strict";
  var d = document, $ = (a, b) => (b || d).querySelector(a), a = ($("#paginater") || $("body > #paginator[hidden]")), b, rm = a => a.parentNode.removeChild(a);
  if (~location.hostname.indexOf("gelbooru.com")) {
    if (a) {
      let p = a, fn = () => {
        for (let a of p.children) {
          if (!a.classList.contains("pagination")) rm(a);
        }
      }, o = new MutationObserver(m => m.forEach(fn));
      o.observe(p, { childList: true, subtree: true });
      fn();
      d.head.lastElementChild.insertAdjacentHTML("afterend", `<style>div.pagination a {
  margin: 0 3px;
  padding: 2px 6px;
  font-weight: normal;
  border: 1px solid #EAEAEA;
}</style>`);
      [...a.attributes].map(b => a.removeAttribute(b.nodeName));
      a.setAttribute("style", 'display: block; padding: 0 0 15px; font-size: 12px; text-align: center; font-weight: bold; clear: both;');
      a.dataset.skip = "yes";
    }
    if (a = $("body > .paginator > #paginator")) {
      [...a.attributes].map(b => a.removeAttribute(b.nodeName));
      a.parentNode.style.textAlign = "center";
      for (let p of a.children) p.setAttribute("style", 'margin: 0 3px; padding: 2px 6px; font-weight: normal; border: 1px solid #EAEAEA;');
    }
    if ((a = $("#image")) || (a = $("#gelcomVideoPlayer")))
      while((b = a.previousElementSibling) != a.parentNode.firstElementChild)
        rm(b);
    if (a = $(".noticeError"))
      rm(a);
    if (a = $(".thumb")) {
      a.parentNode.insertBefore(rm($("div.sidebar")), a);
      $("div.content").style.width = "100%";
      a.parentNode.dataset.skip = "yes";
    }
    for (a of d.querySelectorAll("#post-list div.content > :not([data-skip])"))
      if (a.firstElementChild && a.firstElementChild.tagName == "H1")
        continue;
      else
        rm(a);
    if (a = $("#post-view .sidebar2.sidebar4 center > div.sidebar3"))
      while ((b = a.lastElementChild) && !~b.textContent.indexOf("Related Posts"))
        rm(b);
    if (a = $("[id^='post-'] .sidebar2.sidebar4>center"))
      while (a.children.length > 1)
        rm(a.lastChild);
    if ($("#edit_form"))
      d.addEventListener("dblclick", e => (e.preventDefault(), history.go(-1)), false);
    if (a = $("#resized_notice")) {
      a.replaceChild(d.createElement("a"), a.firstElementChild);
      a = a.firstElementChild;
      a.textContent = "here";
      a.setAttribute("href", "#");
      a.onclick = e => {
        let img = $("#image");
        e.preventDefault();
        [...img.attributes].map(b => !/^(?:id|src|style|alt)$/.test(b.nodeName) && img.removeAttribute(b.nodeName));
        img.src = $("ul>li>a[style*='font-weight:']").href;
        img.style.maxWidth = "calc(100vw - 275px - 5em)";
        e.target.parentNode.hidden = true;
        return false;
      };
    }
    if (a = $("table.highlightable")) {
      let links = a.querySelectorAll("td[style*='padding']:not([style*='width']) > a");
      if (!links) links = a.querySelectorAll("td > span.tag-type-general > a");
      if (links.length) {
        a.removeAttribute("style");
        1 === links.length && links[0].click();
      }
    }
    if (a = $("form#comment_form")) {
      while((b = a.previousSibling).nodeName !== "BR")
        rm(b);
      while(b = a.nextSibling)
        rm(b);
      a.previousElementSibling.outerHTML = `<br><a href="javascript:;" onclick="var d=document,f=d.querySelector('#comment_form'),t=this.children[0];if(f.style.length){t.textContent='Hide';f.style=''}else{t.textContent='Show';f.style.display='none'}"><span>Show</span> comment form</a><br>`;
    }
    if (a = $("li.tag-type-general")) {
      let fn = function(e) {
        let t = e.target,
          tag = t.parentNode.lastElementChild.previousElementSibling.href.match(/tags=([^&]+)/i)[1],
          r = tag.replace(/\(/g, "\\("),
          f = $("input[name='tags']"), b = "\\b", s = "\\s?", i = "i";
        tag = decodeURIComponent(tag);
        switch(t.textContent) {
        case "+":
          if (new RegExp(s + "-" + r + b, i).test(f.value)) f.value = f.value.replace(new RegExp(s + "-" + r + s, i), " ");
          else if (!new RegExp(b + r + b, i).test(f.value)) f.value += " " + tag;
          break;
        case "-":
          if (new RegExp(b + r + b, i).test(f.value) && f.value[f.value.indexOf(tag) - 1] != "-") f.value = f.value.replace(new RegExp(s + r + s), " ");
          else if (!new RegExp(s + "-" + r + b, i).test(f.value)) f.value += " -" + tag;
          break;
        }
        f.value = f.value.replace(/^\s*/, "").replace(/\s*$/, "");
        f.focus();
      };
      for (let el of d.querySelectorAll("ul#tag-sidebar>li[class^='tag-type-']")) {
        a = [...el.children]; a.shift(); a.pop(); a.pop();
        for (let r of a) rm(r);
        el.style.marginLeft = "-21px";
        el.insertAdjacentHTML("afterbegin", `<a href="javascript:;">-</a> <a href="javascript:;">+</a> `);
        el.children[0].addEventListener("click", fn);
        el.children[1].addEventListener("click", fn);
      }
    }
  } else if (~location.hostname.indexOf(".booru.org")) {
    if (a = $("#image"))
      a.setAttribute("style", "max-width: calc(100% - 4em)");
    d.body.style.backgroundImage = 'url("chrome://global/skin/media/imagedoc-darknoise.png")';
    d.body.style.color = "#ddd";
    while (a = $("div#footer").nextElementSibling) rm(a);
  }
}());

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

(function(){
  "use strict";
  var d = document, url, host = location.hostname.match(/[^\.]+\.[^\.]+$/)[0],
  v = ".thumb",
  vis = function() {
    var rect = target().getBoundingClientRect();
    return rect.x === 0 && rect.y === 0 ? false :
      rect.top + rect.height >= 0 &&
      document.documentElement.clientHeight - rect.bottom + rect.height >= 0;
  }, total,
  page = function(doc) {
    try {
      var images, pageNo = d.createElement("paheal.net" === host ? "div" : "span"), frag = d.createDocumentFragment(), container,
      fn = (e) => {
        (e = e.target.parentNode.parentNode).style.overflow = "";
        if (!e.getAttribute("style")) e.removeAttribute("style");
      };
      images = [...doc.querySelectorAll(host === "yande.re" ? "li[id^='p'][class*='creator-id-']" : v)];
      if (images.length === 0)
        throw Error("API error");
      pageNo.innerHTML = '<span style="height:' + ("paheal.net" === host ? "180px" : "inherit") + ';display:flex;flex-direction:column;">Page ' + url.index + '~<span style="margin:auto 0 30px">out of ' + total + "</span></span>";
      pageNo.className = "thumb";
      if ("paheal.net" === host)
        pageNo.setAttribute("style", "transform: translateY(-180px); margin-bottom: -180px");
      frag.appendChild(pageNo);
      for (let a of images) {
        let img = a.querySelector("img");
        a.style.overflow = "hidden";
        img.addEventListener("load", fn, { once : true });
        frag.appendChild(a);
      }
      container = d.querySelector(v).parentNode;
      if (container.lastElementChild.tagName === "SPAN") container.appendChild(frag);
      else container.insertBefore(frag, container.querySelector(v + ":last-of-type + *"));
      target().classList.remove("loadingu");
      if (paginator.go) paginator();
      if (list.length > 0) {
        events(true);
        process();
      } return;
    } catch(err) { console.error(err); }
  }, req = _ => fetch(url.href).then(
    x => x.text().then(
      text => page((new DOMParser()).parseFromString(text, "text/html"))
    )
  ).catch(err => {
    target().classList.remove("loadingu");
    if (typeof err !== "undefined") {
      if (_.attempt < 10) {
        console.error(`An error occured, ${~_.attempt + 11} retries left\n`, err.message, err.stack);
        ++_.attempt;
        setTimeout(req, 5000, _);
      } else
        console.error("Maximum number of retries reached\n", err.message, err.stack);
    }
  }), process = function(_override) {
    if (vis() || (typeof _override === "boolean" ? _override : false)) {
      target().classList.add("loadingu");
      events();
      url = list.shift();
      return req({attempt: 0});
    }
  }, events = function(_on) {
    var name = _on ? "addEventListener" : "removeEventListener", obj = { passive : true };
    ["scroll", "resize", "visibilitychange"].forEach(evt => window[name](evt, process, obj));
  }, list = [], r = /(page=|pid=|index\/)([0-9]+)|(list\/(?:[^\/]+\/)?)([0-9]+)$/, paginator = function() {
    var el = target(), rect1 = d.querySelector(".thumb:last-of-type"),
      el2 = d.querySelector(".sidebar"), rect2;
    if (el.classList.contains("pagination")) el = el.parentNode;
    rect1 = rect1.offsetTop + rect1.offsetHeight;
    rect2 = el2.offsetTop + el2.offsetHeight - 9;
    if (rect2 >= rect1) {
      el.style.position = "absolute";
      el.style.top = rect1 + "px";
      el.style.left = "calc(50vw + " + el2.getBoundingClientRect().width / 2 + "px)";
      el.style.transform = 'translateX(-50%)';
    } else {
      el.style.position = "";
      el.style.top = "";
      el.style.left = "";
      el.style.transform = "";
      paginator.go = false;
    }
  };
  const target = () => d.querySelector("div.pagination") || d.querySelector("div#paginator") || d.querySelector("section#paginator");
  {
    let style = d.createElement("style");
    style.appendChild(d.createTextNode(`.loadingu {
  position: relative;
}
.loadingu::after {
  content: "" ! important;
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  min-width: 34px;
  min-height: 34px;
  background: rgba(0,0,0,.5) no-repeat center center url(data:image/svg+xml,%3Csvg%20width%3D%2234px%22%20height%3D%2234px%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20100%20100%22%20preserveAspectRatio%3D%22xMidYMid%22%20class%3D%22uil-ring-alt%22%3E%3Crect%20x%3D%220%22%20y%3D%220%22%20width%3D%22100%22%20height%3D%22100%22%20fill%3D%22none%22%20class%3D%22bk%22%3E%3C%2Frect%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2240%22%20stroke%3D%22%23b4b197%22%20fill%3D%22none%22%20stroke-width%3D%2210%22%20stroke-linecap%3D%22round%22%3E%3C%2Fcircle%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2240%22%20stroke%3D%22%23f4efcc%22%20fill%3D%22none%22%20stroke-width%3D%226%22%20stroke-linecap%3D%22round%22%3E%3Canimate%20attributeName%3D%22stroke-dashoffset%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20from%3D%220%22%20to%3D%22502%22%3E%3C%2Fanimate%3E%3Canimate%20attributeName%3D%22stroke-dasharray%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20values%3D%22150.6%20100.4%3B1%20250%3B150.6%20100.4%22%3E%3C%2Fanimate%3E%3C%2Fcircle%3E%3C%2Fsvg%3E);
}`));
    d.head.appendChild(style);
  }
  if (target() && /\/post\/?|page=post/.test(location.href)) {
    let pid = location.href.match(r),
    p = ((pid && pid[1] === "index/" || host === "yande.re") ? target().lastElementChild.previousElementSibling : target().lastElementChild).href,
    n = d.querySelectorAll(v).length,
    start_index, end_index, increment = 1, index = 0;
    if (host === "paheal.net") {
      let ayy = [...target().children[0].children];
      p = ayy[ayy.indexOf(ayy.find(el => !!~el.textContent.indexOf("Random"))) + 2].href;
    }
    if (!p) return;
    switch(host) {
    case "gelbooru.com": case "rule34.xxx":
      start_index = pid ? pid[2] / n : 0;
      increment = n;
      end_index = p.match(r)[2] / n;
      index = 1;
      paginator.go = true;
      break;
    case "booru.org":
      start_index = pid ? pid[2] / n : 0;
      increment = n;
      end_index = p.match(r)[2] / n;
      index = 1;
      break;
    case "e621.net":
      start_index = pid ? +pid[2] : 2;
      end_index = +p.match(r)[2];
      break;
    case "yande.re":
      start_index = pid ? +pid[2] : 1;
      end_index = +p.match(r)[2];
      break;
    case "paheal.net":
      start_index = pid ? +pid[4] : 1;
      end_index = +p.match(r)[4];
      break;
    }
    while(start_index < end_index)
      list.push({
        href: p.replace(r, (host === "paheal.net" ? "$3" : "$1") + ++start_index * increment),
        index: start_index + index
      });
    total = end_index + index;
  } else
    throw Error("*shrug*");
  if (paginator.go) paginator();
  d.addEventListener("gelbooru-slide", e => {
    if (list.length > 0)
      events(e.detail);
  }, false);
  d.addEventListener("gelbooru-slide-next", e => {
    if (list.length > 0) {
      clearTimeout(e.detail);
      d.querySelector(".loadingu").classList.remove("loadingu");
      process(true);
    }
  }, false);
  events(true);
  if (vis()) process();
}());