Gelbooru Endless Scroll

Adds endless scroll function to various boorus

  1. // ==UserScript==
  2. // @id gelbooru-endless-scroll
  3. // @name Gelbooru Endless Scroll
  4. // @version 1.6.9
  5. // @namespace intermission
  6. // @author intermission
  7. // @description Adds endless scroll function to various boorus
  8. // @match *://gelbooru.com/index.php?*
  9. // @match *://rule34.xxx/index.php?*
  10. // @match *://e621.net/post/index/*
  11. // @match *://*.booru.org/index.php?*
  12. // @match *://rule34.paheal.net/post/list*
  13. // @match *://yande.re/post*
  14. // @match *://lolibooru.moe/*
  15. // @match *://konachan.com/*
  16. // @match *://atfbooru.ninja/*
  17. // @match *://safebooru.org/*
  18. // @match *://hypnohub.net/*
  19. // @match *://tbib.org/*
  20. // @run-at document-end
  21. // @grant none
  22. // ==/UserScript==
  23.  
  24. /* This program is free software. It comes without any warranty, to
  25. * the extent permitted by applicable law. You can redistribute it
  26. * and/or modify it under the terms of the Do What The Fuck You Want
  27. * To Public License, Version 2, as published by Sam Hocevar. See
  28. * http://www.wtfpl.net/ for more details. */
  29.  
  30. (function(){
  31. "use strict";
  32. var d = document, url, host = location.hostname.match(/[^\.]+\.[^\.]+$/)[0],
  33. v = ".thumb",
  34. vis = function() {
  35. var rect = target().getBoundingClientRect();
  36. return rect.x === 0 && rect.y === 0 ? false :
  37. rect.top + rect.height >= 0 &&
  38. document.documentElement.clientHeight - rect.bottom + rect.height >= 0;
  39. }, total, myImuoto = ~["yande.re", "lolibooru.moe", "konachan.com", "hypnohub.net"].indexOf(host),
  40. page = function(doc) {
  41. try {
  42. var images, pageNo = d.createElement("paheal.net" === host ? "div" : "span"), frag = d.createDocumentFragment(), container,
  43. fn = (e) => {
  44. (e = e.target.parentNode.parentNode).style.overflow = "";
  45. if (!e.getAttribute("style")) e.removeAttribute("style");
  46. };
  47. images = [...doc.querySelectorAll(myImuoto ? "li[id^='p'][class*='creator-id-']" : v)];
  48. if (images.length === 0)
  49. throw Error("API error");
  50. 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>";
  51. pageNo.className = "thumb";
  52. if ("paheal.net" === host)
  53. pageNo.setAttribute("style", "transform: translateY(-180px); margin-bottom: -180px");
  54. frag.appendChild(pageNo);
  55. for (let a of images) {
  56. let img = a.querySelector("img");
  57. a.style.overflow = "hidden";
  58. img.addEventListener("load", fn, { once : true });
  59. frag.appendChild(a);
  60. }
  61. container = d.querySelector(v).parentNode;
  62. if (container.lastElementChild.tagName === "SPAN") container.appendChild(frag);
  63. else container.insertBefore(frag, container.querySelector(v + ":last-of-type + *"));
  64. target().classList.remove("loadingu");
  65. if (paginator.go) paginator();
  66. if (list.length > 0) {
  67. events(true);
  68. process();
  69. } return;
  70. } catch(err) { console.error(err); }
  71. }, req = _ => fetch(url.href).then(
  72. x => x.text().then(
  73. text => page((new DOMParser()).parseFromString(text, "text/html"))
  74. )
  75. ).catch(err => {
  76. target().classList.remove("loadingu");
  77. if (typeof err !== "undefined") {
  78. if (_.attempt < 10) {
  79. console.error(`An error occured, ${~_.attempt + 11} retries left\n`, err.message, err.stack);
  80. ++_.attempt;
  81. setTimeout(req, 5000, _);
  82. } else
  83. console.error("Maximum number of retries reached\n", err.message, err.stack);
  84. }
  85. }), process = function(_override) {
  86. if ((!d.hidden && vis()) || (typeof _override === "boolean" ? _override : false)) {
  87. target().classList.add("loadingu");
  88. events();
  89. url = list.shift();
  90. return req({attempt: 0});
  91. }
  92. }, events = function(_on) {
  93. var name = _on ? "addEventListener" : "removeEventListener", obj = { passive : true };
  94. ["scroll", "resize", "visibilitychange"].forEach(evt => window[name](evt, process, obj));
  95. }, list = [], r = /(page=|pid=|index\/)([0-9]+)|(list\/(?:[^\/]+\/)?)([0-9]+)$/, paginator = function() {
  96. var el = target(), rect1 = d.querySelector(".thumb:last-of-type"),
  97. el2 = d.querySelector(location.href.startsWith("https://gelbooru.com/") ? ".hidden-xs" : ".sidebar"), rect2;
  98. if (el.classList.contains("pagination")) el = el.parentNode;
  99. rect1 = rect1.offsetTop + rect1.offsetHeight;
  100. rect2 = el2.offsetTop + el2.offsetHeight - 9;
  101. if (rect2 >= rect1) {
  102. el.style.position = "absolute";
  103. el.style.top = rect1 + "px";
  104. el.style.left = "calc(50vw + " + el2.getBoundingClientRect().width / 2 + "px)";
  105. el.style.transform = 'translateX(-50%)';
  106. } else {
  107. el.style.position = "";
  108. el.style.top = "";
  109. el.style.left = "";
  110. el.style.transform = "";
  111. paginator.go = false;
  112. }
  113. };
  114. const target = () => d.querySelector("div.pagination") || d.querySelector("div#paginator") || d.querySelector("section#paginator");
  115. {
  116. let style = d.createElement("style");
  117. style.appendChild(d.createTextNode(`.loadingu {
  118. position: relative;
  119. }
  120. .loadingu::after {
  121. content: "" ! important;
  122. display: block;
  123. position: absolute;
  124. top: 0;
  125. left: 0;
  126. width: 100%;
  127. height: 100%;
  128. min-width: 34px;
  129. min-height: 34px;
  130. 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);
  131. }`));
  132. d.head.appendChild(style);
  133. }
  134. if (target() && /\/post\/?|page=post/.test(location.href)) {
  135. let pid = location.href.match(r),
  136. p = ((pid && pid[1] === "index/" || myImuoto) ? target().lastElementChild.previousElementSibling : target().lastElementChild).href,
  137. n = d.querySelectorAll(v).length,
  138. start_index, end_index, increment = 1, index = 0;
  139. if (host === "paheal.net") {
  140. let ayy = [...target().children[0].children];
  141. p = ayy[ayy.indexOf(ayy.find(el => !!~el.textContent.indexOf("Random"))) + 2].href;
  142. }
  143. if (!p) return;
  144. switch(host) {
  145. case "booru.org":
  146. start_index = pid ? pid[2] / n : 0;
  147. increment = n;
  148. end_index = p.match(r)[2] / n;
  149. index = 1;
  150. break;
  151. case "e621.net":
  152. start_index = pid ? +pid[2] : 2;
  153. end_index = +p.match(r)[2];
  154. break;
  155. case "yande.re":
  156. case "lolibooru.moe":
  157. case "konachan.com":
  158. case "hypnohub.net":
  159. start_index = pid ? +pid[2] : 1;
  160. end_index = +p.match(r)[2];
  161. break;
  162. case "paheal.net":
  163. start_index = pid ? +pid[4] : 1;
  164. end_index = +p.match(r)[4];
  165. break;
  166. default:
  167. start_index = pid ? pid[2] / n : 0;
  168. increment = n;
  169. end_index = p.match(r)[2] / n;
  170. index = 1;
  171. paginator.go = true;
  172. break;
  173. }
  174. while(start_index < end_index)
  175. list.push({
  176. href: p.replace(r, (host === "paheal.net" ? "$3" : "$1") + ++start_index * increment),
  177. index: start_index + index
  178. });
  179. total = end_index + index;
  180. } else
  181. throw Error("*shrug*");
  182. if (paginator.go) paginator();
  183. window.addEventListener("keypress", e => {
  184. if (typeof url !== "object" && vis()) process();
  185. }, { once : true });
  186. d.addEventListener("gelbooru-slide", e => {
  187. if (list.length > 0)
  188. events(e.detail);
  189. }, false);
  190. d.addEventListener("gelbooru-slide-next", e => {
  191. if (list.length > 0) {
  192. clearTimeout(e.detail);
  193. d.querySelector(".loadingu").classList.remove("loadingu");
  194. process(true);
  195. }
  196. }, false);
  197. events(true);
  198. if (vis()) process();
  199. }());