// ==UserScript==
// @id gelbooru-slide
// @name Gelbooru Image Viewer
// @version 1.9.9.9
// @namespace intermission
// @author intermission
// @license WTFPL; http://www.wtfpl.net/about/
// @description Adds a fullscreen image view option when you click on images and various other neat features
// @match *://gelbooru.com/*
// @match *://rule34.xxx/*
// @match *://e621.net/*
// @match *://*.booru.org/*
// @match *://*.paheal.net/*
// @match *://yande.re/post*
// @match *://lolibooru.moe/*
// @match *://konachan.com/*
// @match *://atfbooru.ninja/*
// @match *://safebooru.org/*
// @match *://hypnohub.net/*
// @match *://tbib.org/*
// @match *://booru.splatoon.ink/*
// @match *://*.sankakucomplex.com/*
// @exclude http://www.sankakucomplex.com/*
// @exclude https://www.sankakucomplex.com/*
// @run-at document-start
// @grant GM_registerMenuCommand
// @grant GM_xmlhttpRequest
// @grant GM_info
// ==/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. */
/* global GM_registerMenuCommand, GM_xmlhttpRequest, GM_info, unsafeWindow */
"use strict";
/**
* DEBUG
*/
const WIREFRAME = 0; // 0 - off, 1 - on during image viewer, 2 - always on
const SAFE_DEBUG = false;
/**
* BASIC CONSTANTS
*/
const d = document;
const w = window;
const stor = localStorage;
const uW = typeof unsafeWindow !== "undefined" ? unsafeWindow : w;
const domain = location.hostname.match(/[^.]+\.[^.]+$/)[0];
const scriptInfo = GM_info;
const ns = "gelbooru-slide";
const fullImage = stor[ns] === "true";
const passive = { passive: true };
const once = { once: true };
const svg = '<svg xmlns="http://www.w3.org/2000/svg" ';
const httpOk = [200, 302, 304];
/**
* `site` OBJECT CONSTRUCTION
* site: {
* name: hostname of current site
* inject: string if a site needs a script to be injected to work this userJS
* [name]: dynamic property, hostname of current site w/o TLD, returns true
* }
*/
const site = (() => {
const info = scriptInfo.scriptMetaStr;
const regex = /match.*?[/.](\w+\.\w+)\//g;
const ret = Object.setPrototypeOf({ name: "", inject: "" }, null);
let line, name;
while (line = regex.exec(info)) { // eslint-disable-line no-cond-assign
if (line[1] === domain) {
ret[name = domain.split(".")[0]] = true;
ret.name = name;
break;
}
}
const qS = `document.querySelector("#post-list > .content`;
const iB = "insertBefore";
switch(name) {
case "hypnohub":
ret.inject += `Object.defineProperties(window.PostModeMenu, {\n "post_mouseover": {\n value() {}\n },\n "post_mouseout": {\n value() {}\n }\n});`;
break;
case "sankakucomplex":
ret.inject += `Object.defineProperty(${qS}"), "${iB}", {\n value: function ${iB}(newEl, pos) {\n if (newEl.nodeType !== 11)\n return (pos.nodeType === 3 ? pos.nextElementSibling : pos).insertAdjacentElement("beforebegin", newEl);\n else {\n const s = ${qS} > div:first-of-type");\n for (const t of newEl.firstElementChild.children) {\n t.classList.remove("blacklisted");\n t.removeAttribute("style");\n s.appendChild(t);\n }\n }\n return newEl;\n }\n})`;
}
return ret;
})();
/**
* VARIABLE FOR CSS
* paheal's thumbnails are 200x200 instead of 180x180 (every other booru)
*/
const paheal = site.paheal ? 20 : 0;
/**
* SVG CONSTANTS
*/
const SVG = {
play: `${svg}width="50" height="50"><rect rx="5" height="48" width="48" y="1" x="1" fill="#fff" /><polygon fill="#000" points="16 12 16 38 36 25" /></svg>`,
pause: `${svg}width="50" height="50"><rect fill="#fff" x="1" y="1" width="48" height="48" rx="5" /><rect fill="#000" x="12" y="12" width="10" height="26" /><rect fill="#000" x="28" y="12" width="10" height="26" /></svg>`,
next: ((z, a, b, c, r) => `${svg}viewBox="0 0 118 118"><style>.a{fill:none;stroke-linejoin:round;stroke-width:4;stroke:#fff}</style><path d="M78.6 71.8l-7.6-1.6${a}${z}-1-3.7l5.8-5.2a4.3 4.3${z}-4.2-7.3l-7.4 2.4a29.7 29.7${z}-2.7-2.7l2.4-7.4a4.3 4.3${z}-7.3-4.2L51.5 48${a}${z}-3.7-1l-1.6-7.6a4.3 4.3${z}-8.4 0l-1.6 7.6${a}${z}-3.7 1l-5.2-5.8a4.3 4.3${z}-7.3 4.2l2.4 7.4a29.7 29.7${z}-2.7 2.7l-7.4-2.4a4.3 4.3${z}-4.2 7.3L14 66.5${a}${z}-1 3.7l-7.6 1.6a4.3 4.3${z} 0 8.4l7.6 1.6${a}${z} 1 3.7l-5.8 5.2a4.3 4.3${z} 4.2 7.3l7.4-2.4a29.8 29.8${z} 2.7 2.7l-2.4 7.4a4.3 4.3${z} 7.3 4.2l5.2-5.8${a}${z} 3.7 1l1.6 7.6a4.3 4.3${z} 8.4 0l1.6-7.6${a}${z} 3.7-1l5.2 5.8a4.3 4.3${z} 7.3-4.2l-2.4-7.4a29.8 29.8${z} 2.7-2.7l7.4 2.4a4.3 4.3${z} 4.2-7.3L70 85.5${a}${z} 1-3.7l7.6-1.6A4.3 4.3${z} 78.6 71.8zM42 92.5A16.5 16.5 0 1 1 58.5 76 16.5 16.5 0 0 1 42 92.5z${r}0 42 76.051" to="360 42 76.051" dur=3${c}<path d="M113.2 24.5l-6.9-1.6${b}${z}-1.1-3l3.7-5.9a3.6 3.6${z}-5-5L98 12.8${b}${z}-3-1.1l-1.6-6.9a3.6 3.6${z}-7 0l-1.6 6.9a16.9 16.9${z}-2.8 1.2l-6-3.8a3.6 3.6${z}-5 5l3.8 6a16.9 16.9${z}-1.2 2.8l-6.9 1.6a3.6 3.6${z} 0 7l6.9 1.6${b}${z} 1.1 3l-3.7 5.9a3.6 3.6${z} 5 5L82 43.2${b}${z} 3 1.1l1.6 6.9a3.6 3.6${z} 7 0l1.6-6.9a16.9 16.9${z} 2.8-1.2l6 3.8a3.6 3.6${z} 5-5l-3.8-6a16.9 16.9${z} 1.2-2.8l6.9-1.6A3.6 3.6${z} 113.2 24.5z${r}360 89.97 28" to="0 89.97 28" dur=2${c}<circle r=8.4 style="fill:none;stroke-width:4;stroke:#fff" cx=89.97 cy=28></circle></svg>`)(" 0 0 0", "a29.3 29.3", "a20.6 20.6", "s></animateTransform></path>", '" class=a><animateTransform attributeName=transform type=rotate repeatCount=indefinite from="'),
debug: (l => `<div style="width:calc(100vw - 2px);height:${198+paheal}px;position:absolute;top:0;left:0;border:1px solid cyan"></div><div style="width:calc(100vw - 2px);height:calc(100vh - ${202+paheal}px);position:absolute;bottom:0;border:1px solid red;display:block">${svg}viewBox="0 0 200 200" preserveAspectRatio=none style="width: 100%;height: 100%;"><style>.d{fill:none;stroke-width:1px;stroke:#ff0}</style>${l}d y2=15 x2=200 y1=15></line>${l}d y2=185.5 x2=200 y1=185.5></line>${l}d x2=15 y1=200 x1=15></line>${l}d x2=185 y1=200 x1=185></line></svg></div><div style="width:100vw;height:100vh;position:absolute;top:0;left:0">${svg}viewBox="0 0 200 200" preserveAspectRatio=none style="width: 100%;height: 100%;"><style>.b{fill:none;stroke-width:1px;stroke:#0f0}</style>${l}b y2=100 x2=200 y1=100></line>${l}b y2=200 x2=100 x1=100></line></svg></div>`)('<line vector-effect="non-scaling-stroke" shape-rendering="crispEdges" class='),
gif: `${svg}viewBox="-10 -3 36 22"><path d="M26 16c0 1.6-1.3 3-3 3H-7c-1.7 0-3-1.4-3-3V0c0-1.7 1.3-3 3-3h30c1.7 0 3 1.3 3 3v16z" opacity=".6"/><path fill="#FFF" d="M22-1H-6c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h28c1.1 0 2-.9 2-2V1c0-1.1-.9-2-2-2zM6.3 13.2H4.9l-.2-1.1c-.4.5-.8.9-1.3 1.1-.5.2-1 .3-1.4.3-.8 0-1.5-.1-2.1-.4s-1.1-.6-1.5-1.1-.7-1-1-1.6C-2.9 9.7-3 9-3 8.3c0-.7.1-1.4.3-2.1.2-.6.5-1.2 1-1.7s.9-.8 1.5-1.1C.5 3.2 1.2 3 1.9 3c.5 0 1 .1 1.5.2.5.2.9.4 1.3.7.4.3.7.7 1 1.1.2.5.4 1 .4 1.5H4c-.1-.5-.3-.9-.7-1.2-.4-.3-.8-.4-1.4-.4-.5 0-.9.1-1.2.3-.4.1-.7.4-.9.7-.2.3-.3.7-.4 1.1s-.1.8-.1 1.3c0 .4 0 .8.1 1.2s.3.8.5 1.1c.2.3.5.6.8.8s.8.3 1.3.3c.7 0 1.3-.2 1.7-.6.4-.4.6-.9.7-1.6H2.1V7.8h4.2v5.4zm4 0H8.1v-10h2.2v10zm8.9-8.1h-4.8v2.3h4.2v1.7h-4.2v4.1h-2.2v-10h7v1.9z"/></svg>`,
warn: `${svg}viewBox="0 0 1000 1000"><style>path.a{fill:red;stroke-width:8px;stroke:black;}</style><path d="M500 673c-17 0-34 4-48 11-24 12-44 33-55 58-5 14-9 28-9 44 0 62 50 113 112 113 63 0 113-50 113-113C613 723 562 673 500 673zM500 843c-32 0-58-26-58-58 0-32 26-58 58-58 32 0 59 26 59 58C558 817 532 843 500 843z" class="a"/><path d="M285 643c4 3 8 5 12 5l132-138c-57 14-109 47-149 94C272 617 273 634 285 643z" class="a"/><path d="M606 522L565 565c43 13 83 39 112 75 5 7 13 10 21 10 6 0 12-2 17-6 11-9 13-27 3-38C689 568 650 539 606 522z" class="a"/><path d="M500 384c16 0 32 1 48 3l46-48c-30-7-61-10-93-10-137 0-265 61-351 167-10 11-8 29 4 38 5 4 11 7 17 7 8 0 15-4 21-10C267 438 379 384 500 384z" class="a"/><path d="M729 393l-38 40c45 24 86 58 119 98 10 12 27 14 39 4 12-9 13-27 4-38C817 454 776 420 729 393z" class="a"/><path d="M685 244l41-43c-70-28-147-42-226-42-188 0-364 84-484 230-9 12-7 29 4 39 5 4 11 6 17 6 8 0 16-3 21-10 109-133 270-210 442-210C564 214 626 224 685 244z" class="a"/><path d="M984 389c-38-48-83-88-133-121l-38 40c49 32 93 71 130 117 9 11 27 13 38 4C991 418 994 401 984 389z" class="a"/><path d="M907 110c-11-10-28-10-38 1L181 828c-10 11-10 28 1 38 5 5 12 8 19 8 7 0 14-3 20-8L908 148C918 138 918 120 907 110z" class="a"/></svg>`
};
/**
* GLOBAL VARIABLE INDICATING WHETHER WE ARE IN SLIDESHOW MODE OR NOT
*/
let slideshow;
/**
* NAMESPACES
*/
let Pos, Menu, Btn, Main, Prog, Hover;
/**
* SET AN INITIAL VALUE ON FIRST RUN
*/
if (!stor[ns]) stor[ns] = "false";
/**
* PAHEAL AND BOORU.ORG DON'T USE SAMPLES
*/
switch(site.name) {
case "splatoon": case "booru": case "paheal":
break;
default:
GM_registerMenuCommand(`Current image mode: ${fullImage ? "Always original size" : "Sample only"}`, () => { stor[ns] = fullImage ? "false" : "true"; location.reload(); });
break;
}
/**
* JQUERY INSPIRED UTILITIES
* details won't be documented, just look at the code
*/
const $$ = (a, b = d) => Array.prototype.slice.call(b.querySelectorAll(a));
const $ = (a, b = d) => b.querySelector(a);
$.keys = Object.keys;
$.extend = function(obj, props) {
const arr = $.keys(props);
for (let i = 0, key, len = arr.length; i < len; ++i) {
obj[key = arr[i]] = props[key];
}
return obj;
};
$.extend($, {
cache(id, b) {
if (site.sankakucomplex) return "loading";
const val = fullImage ? "original" : "sample";
let ret, obj, temp;
if (!b) {
const json = $.safe(JSON.parse, null, stor[ns + id]);
if (json !== $.safe.error) ret = json[val];
ret = ret || "loading";
}
else {
const json = $.safe(JSON.parse, null, stor[ns + id]);
if (json !== $.safe.error) temp = json;
obj = temp || {};
obj[val] = b;
stor[ns + id] = JSON.stringify(obj);
ret = b;
}
return ret;
},
base: a => a.match(Main.r[5]),
id: (a, b = d) => b.getElementById(a),
current: src => $(`a[data-id] > img[src*="${$.base(src || Main.el.dataset.src)}"]`).parentNode,
_find(method, el, a) {
el = el[`${method ? "next" : "previous"}ElementSibling`];
a = $("a[data-full]", el);
return [el, a];
},
find(el, method) {
const { _find, safe, safe: { error } } = $;
let a, search;
el = el.parentNode;
do {
if ((search = safe(_find, null, method, el, a)) === error) return false;
else ({ 0: el, 1: a } = search);
if (a) return a;
} while(1); // eslint-disable-line no-constant-condition
},
preload() {
if (site.sankakucomplex) return;
const curr = $.current();
Main.req($.find(curr, true));
Main.req($.find(curr, false));
},
keyDown(e) {
let move, warn;
if (slideshow) return;
if (e.ctrlKey && Main.el.style.objectFit !== "none")
Main.el.style.objectFit = "none";
if (e.warn)
move = warn = true;
else {
switch(e.keyCode) {
case 32: case 39: // Space, →
if (e.preventDefault) e.preventDefault();
move = true;
break;
case 37: // ←
move = false;
break;
case 38: // ↑
return e.event ? Menu.fn(e.event) : w.location = $.current().href;
case 40: // ↓
e.preventDefault();
return Main.el.click();
}
}
if (typeof move !== "undefined") {
if (!warn && (e = $.find($.current(), move))) {
if (Prog.el) {
Prog.el.classList.remove("progdone");
Prog.el.style.width = 0;
}
Main.slide($("img", e).src);
Pos.fn(move);
$.preload();
}
else if (Hover.nextEl && !Hover.nextEl.classList.contains("loadingu") && move === true)
Hover.next(true);
else if (!$.keyDown.el) {
const el = $.keyDown.el = $.c("div");
el.classList.add("nomoreimages");
const side = move ? "right" : "left";
el.setAttribute("style", `background: linear-gradient(to ${side}, transparent, rgba(255,0,0,.5));${side}: 0;`);
$.add(el);
}
}
},
keyUp() {
if (slideshow) return;
if (Main.el.style.objectFit === "none") {
$.zoom.el = $.rm($.zoom.el);
Main.el.removeAttribute("style");
}
},
zoom(e) {
if (Main.gif.hasAttribute("style")) return;
if (!e.ctrlKey) {
$.zoom.el = $.rm($.zoom.el);
return Main.el.removeAttribute("style");
}
else Main.el.style.objectFit = "none";
if (slideshow) return;
const minY = 200 + paheal, maxX = w.innerWidth, maxY = w.innerHeight,
{ clientX: x, clientY: y } = e,
tall = Main.el.naturalHeight > maxY, wide = Main.el.naturalWidth > maxX;
if (!$.zoom.el) {
const el = $.zoom.el = $.c("span");
el.id = "zoom_top";
el.innerHTML = "<span></span>";
$.add(el);
}
if ((wide || tall) && minY < y && maxY >= y && 0 <= x && maxX >= x) {
let xPos = 50, yPos = 50;
if (tall) {
const margin = (maxY - minY) * 0.075;
const height = maxY - minY - margin * 2;
if (y < minY + margin)
yPos = 0;
else if (y > maxY - margin)
yPos = 100;
else
yPos = (y - minY - margin) / height * 100;
}
if (wide) {
const margin = maxX * 0.075;
const width = maxX - margin * 2;
if (x < margin)
xPos = 0;
else if (x > maxX - margin)
xPos = 100;
else
xPos = (x - margin) / width * 100;
}
$.zoom.el.removeAttribute("style");
if (tall && yPos < 10) {
const w = maxX / 40 + 30;
$.zoom.el.style.left = `${x - (w < 55 ? 55 : w)}px`;
void $.zoom.el.offsetWidth; // reflow hack
$.zoom.el.style.animationName = "topglowything";
}
Main.el.style.objectPosition = `${xPos}% ${yPos}%`;
}
else if (!Main.el.style.objectPosition) {
Main.el.style.objectPosition = "50% 50%";
}
},
c: c => d.createElement(c),
r: (() => {
let queue = [];
d.addEventListener("DOMContentLoaded", () => {
for (let i = 0, len = queue.length; i < len; ++i) {
const obj = queue[i], copy = [obj.fn, null];
copy.push.apply(copy, obj.args);
$.safe.apply($, copy);
}
queue = null;
}, once);
return (fn, ...args) => queue ?
queue[queue.length] = { fn, args } :
$.safe(fn, null, ...args);
})(),
rm: el => { if (el) el.parentNode.removeChild(el); },
ins: (el, m, t) => el.insertAdjacentHTML(m, t),
eval(text = "") {
const script = $.c("script");
script.innerHTML = text;
$.add(script, d.documentElement);
},
in: (obj => {
const arr = function inArray(key) {
for (let i = 0, len = this.length; i < len; ++i) {
if (key === this[i]) return true;
}
return false;
};
return (k, o) => o && (typeof o.length === "number" ? arr : obj).call(o, k);
})(Object.prototype.hasOwnProperty),
add(el, to = d.body) {
$.safe(to.appendChild, to, el);
return el;
},
safe: (safeError => {
function safe(fn, context) {
const k = arguments.length, args = [];
{
let i = 1, p = -1;
while (++i < k) args[++p] = arguments[i];
}
try { return fn.apply(context, args); }
catch(e) {
if (SAFE_DEBUG) console.error("$.safe debug:", e);
return safeError;
}
}
safe.error = safeError;
return safe;
})(Symbol()),
_evt() {
if (typeof arguments[0] === "string") {
return d[this](arguments[0], arguments[1], arguments[2]);
}
return arguments[0][this](arguments[1], arguments[2], arguments[3]);
},
on() { return $._evt.apply("addEventListener", arguments); },
off() { return $._evt.apply("removeEventListener", arguments); },
u: void 0
});
/**
* IF SOMETHING CHANGES IN HOW WE STORE CACHED URLS THEN MODIFY VERSION HERE
*/
{
const version = "1.8.8";
if (stor[ns + "-firstrun"] !== version) {
const r = /^gelbooru-slide./, arr = $.keys(stor), len = arr.length;
for (let i = 0; i < len; ++i) {
const a = arr[i];
r.test(a) && stor.removeItem(a);
}
stor[ns + "-firstrun"] = version;
}
}
/**
* MODULE FOR POSITIONAL DISPLAY IN BOTTOM LEFT CORNER
*/
Pos = {
fn(a) {
let el = Pos.el;
if (a != null) {
switch(typeof a) {
case "string":
$("span", el).innerHTML = ++a;
break;
case "boolean": {
const no = $("span", el);
no.innerHTML = Number(no.textContent) + (a ? 1 : -1); }
break;
case "number":
el.lastElementChild.lastChild.data = ` / ${a}`;
}
el.title = el.textContent;
if (Menu.el) {
const arr = $$("a:not([download])", Menu.el);
let i = arr.length;
while (~--i) arr[i].href = $.current().href;
Menu.download();
}
}
else {
if (Main.el && !el) {
const thumbs = $$(".thumb a[data-full]");
el = $.c("div");
$.ins(el, "beforeend", `<div><span>${thumbs.indexOf($.current()) + 1}</span> / ${thumbs.length}</div>`);
el.className = "posel";
el.title = el.textContent;
Main.el.insertAdjacentElement("afterend", el);
Pos.el = el;
}
else if (el) Pos.el = $.rm(el);
}
}
};
/**
* MODULE FOR MIDDLE CLICK MENU
*/
Menu = {
fn(e) {
const height = Main.gif.hasAttribute("style") ? 48 : 71,
_l = e.clientX + 1, _t = e.clientY + 1,
left = (_l > w.innerWidth - 139 ? _l - 139 : _l) + "px",
top = (_t > w.innerHeight - height ? _t - height : _t) + "px";
let el = Menu.el;
if (el) {
el.removeAttribute("class");
setTimeout($.safe, 10, el.classList.add, el.classList, "menuel");
}
else {
const href = $.current().href + '" style="margin-bottom: 2px"';
el = $.c("div");
el.id = "menuel";
$.ins(el, "beforeend", `<a href="${href}>Open in This Tab</a><a href="${href} target="_blank">Open in New Tab</a><a href="javascript:;">Save Image As...</a>`);
$.add(el);
Menu.el = el;
$.on(el.lastElementChild, "click", Menu.copyFilename);
el.classList.add("menuel");
if (Menu.realDl) Menu.download();
}
return $.extend(el.style, { left, top });
},
download() {
const el = Menu.realDl;
el.href = Main.el.src;
el.download = Main.el.src.split("/").pop() + "." + $.current().dataset.full.match(Main.r[2])[1];
},
copyFilename() {
const { copy, realDl } = Menu;
copy.value = realDl.getAttribute("download") || "";
copy.select();
if ($.safe(document.execCommand, document, "copy") === $.safe.error)
console.log("Failed to copy filename: %s", copy.value);
realDl.click();
}
};
/**
* MODULE FOR SLIDESHOW BUTTON AND SETTINGS IN BOTTOM RIGHT CORNER
*/
Btn = {
fn() {
const sel = "this.previousElementSibling.firstElementChild";
let el = Btn.el;
if (el) {
clearTimeout(Btn.hideTimer);
Btn.clear();
Btn.el = $.rm(Btn.el);
Hover.el.removeAttribute("style");
}
else {
el = $.c("div");
el.setAttribute("style", "opacity: .7;");
el.className = "slideshow";
$.ins(el, "beforeend", `<span title="Slideshow">${SVG.play}</span><div style="display:none;padding:10px 0">Options<hr><label>Loop: <input type="checkbox" checked></label> <label onclick="${sel}.checked=true;${sel}.disabled=!${sel}.disabled">Shuffle: <input type="checkbox"></label><br>Interval: <input type="number" value="5" style="width:100px"></div>`);
Btn.state = true;
el.firstElementChild.onclick = Btn.cb;
$.add(el);
Btn.el = el;
}
},
clear() {
clearTimeout(Number(Btn.el.dataset.timer));
Btn.el.removeAttribute("data-timer");
},
cb: ((thumbs, el, options, orig) => {
const sel = ".thumb a[data-full]",
validate = a => a.type === "number" ? (a.value >= 1 ? a.value : 1) * 1E3 : a.checked,
_fnS = () => { if (el.dataset.timer) el.dataset.timer = setTimeout(_fnT, options[2]); };
const _fnT = () => {
let _el;
if (options[1]) {
if (thumbs.length === 0) thumbs = $$(sel);
thumbs.splice(thumbs.indexOf($.current()), 1);
_el = thumbs[Math.random() * thumbs.length & -1];
}
else _el = $.find($.current(), true);
if (!_el && options[0]) _el = $(sel);
if (!_el) return Btn.cb();
$.on(Main.el, "load", _fnS, once);
Main.slide($("img", _el).src);
};
const cb = () => {
el = Btn.el;
Pos.fn();
slideshow = !!Btn.state;
el.firstElementChild.innerHTML = (Btn.state = !Btn.state) ? SVG.play : SVG.pause;
if (slideshow) {
thumbs = [];
options = $$("div input", el).map(validate);
el.dataset.timer = setTimeout(_fnT, options[2]);
el.style.opacity = 0.4;
Hover.el.setAttribute("style", "display: none !important");
orig = d.body.getAttribute("style");
$.on("mousemove", Btn.hide);
}
else {
Btn.clear();
el.style.opacity = ".7";
el.removeAttribute("data-timer");
Hover.el.removeAttribute("style");
Hover.center($.current().firstElementChild.src);
$.off("mousemove", Btn.hide);
clearTimeout(Btn.hideTimer);
if (orig) d.body.setAttribute("style", orig);
else d.body.removeAttribute("style");
}
};
cb.move = () => {
clearTimeout(Number(Btn.el.dataset.timer));
_fnT();
};
return cb;
})([]),
hide() {
const b = d.body;
b.style.cursor = "";
clearTimeout(Btn.hideTimer);
Btn.hideTimer = setTimeout(() => b.style.cursor = "none", 5E3);
}
};
/**
* MODULE FOR GENERAL DOWNLOADING, PROGRESS DISPLAY AND IMAGE HANDLING
*/
Prog = {
check: id => Main.el.dataset.id === id,
load(e) {
const {el} = Prog, {id, ext} = e.context;
delete Prog.reqs[id];
if (!el) throw Error("There was an event order issue with GM_xmlhttpRequest");
if (!$.in(e.status, httpOk) || !Main.r[5].test(e.finalUrl))
return Prog.error(e);
else {
const blobUrl = w.URL.createObjectURL(
new Blob([e.response], { type: `image/${ext.replace("jpeg", "jpg")}` })
);
$(`a[data-id="${id}"]`).dataset.blob = blobUrl;
if (Main.el && Prog.check(id)) {
el.classList.add("progdone");
Main.el.src = blobUrl;
if (Menu.el) Menu.download();
}
}
},
progress(e) {
let el = Prog.el;
if (!el) {
Prog.el = el = $.c("span");
el.setAttribute("style", "width:0");
el.classList.add("progress");
$.add(el);
}
if (!e) return el;
const id = e.context && e.context.id;
if (Main.el && Prog.check(id) && el) {
el.classList.remove("progfail");
el.style.width = `${$.in(id, Prog.reqs) ? e.loaded / e.total * 100 : 0}%`;
}
},
error(e) {
const el = Prog.el, id = e.context.id;
if (Main.el && Prog.check(id) && el) _: {
el.classList.add("progfail");
const req = Prog.reqs[id];
if (req == null) break _;
$.safe(req.abort, req);
delete Prog.reqs[id];
}
if (slideshow)
Main.el.dispatchEvent(new Event("load"));
stor.removeItem(ns + id);
if (!site.paheal)
$(`a[data-id="${id}"]`).dataset.full = $.cache(id, "loading");
},
parser: new DOMParser,
helper: class { then(r, e) {
Prog.reqs[this.id] = GM_xmlhttpRequest(Object.assign({}, this.details, {
onload: r,
onerror: e,
onabort: e,
ontimeout: e
}));
} },
async sankaku(url, id, extra) {
const req = new Prog.helper;
req.id = id;
const headers = {
Cookie: document.cookie,
Referer: location.href
};
req.details = {
url,
method: "GET",
headers
};
const res = await req;
extra.dataset.full = headers.Referer = res.finalUrl;
extra.removeAttribute("data-already-loading");
const doc = Prog.parser.parseFromString(res.responseText, "text/html");
const a = doc.querySelector("#image-link");
url = fullImage && a.href ? a.href : a.firstElementChild.src;
return [url, headers];
},
async fn(url, id, extra) {
if ($.in(id, Prog.reqs)) return;
let headers;
if (url.startsWith("//assets2")) {
url = `//gelbooru.com/${url.substr(22)}`;
}
else if (site.sankakucomplex) {
try { ({ 0: url, 1: headers } = await Prog.sankaku(url, id, extra)); }
catch (e) {
Prog.error({ context: { id } });
throw e;
}
}
if (Prog.el) Prog.el.style.width = 0;
Prog.reqs[id] = GM_xmlhttpRequest({
url,
headers,
context: { id, url, ext: url.match(Main.r[2])[1], extra },
method: "GET",
responseType: "arraybuffer",
onload: Prog.load,
onprogress: Prog.progress,
onerror: Prog.error,
onabort: Prog.error,
ontimeout: Prog.error
});
},
reqs: Object.setPrototypeOf({}, null)
};
/**
* MODULE FOR IMAGE LIST IN THE TOP 200 or 220 (PAHEAL) PIXELS OF THE VIEWPORT
*/
Hover = {
lastInList: {},
init() {
const el = $.c("div");
$.ins(el, "beforeend", `<div class="layover"></div><div class="tentcon"><div class="wrapthatshit"><div class="listimage"></div></div></div>${svg}style="width: 0; height: 0"><filter id="__dropshadow"><feGaussianBlur in="SourceAlpha" stdDeviation="2"></feGaussianBlur><feOffset result="offsetblur" dx="1" dy="1"></feOffset><feMerge><feMergeNode /><feMergeNode in="SourceGraphic" /></feMerge></filter></svg>`);
el.className = "viewpre";
$.add(el);
Hover.index = -1;
Hover.target = $(".listimage", el);
$.on(Hover.target, "click", Hover.click, passive);
$.on(Hover.target, "dragstart", e => e.preventDefault());
Hover.wrap = Hover.target.parentNode;
if (!site.sankakucomplex) {
$.add(Hover.nextEl = $.c("span"), Hover.target);
Hover.nextEl.className = "next";
$.on(Hover.nextEl, "click", Hover.next, passive);
}
{
const arr = $$("a[data-full]"), len = arr.length;
let i = -1;
while (++i < len) Hover.build(arr[i]);
}
Hover.el = el;
Hover.kinetic();
},
noGears() {
Hover.gears.style.opacity = 0;
setTimeout(() => Hover.gears = $.rm(Hover.gears), 300);
},
next(move) {
if (Hover.nextEl.classList.contains("loadingu")) return;
if (!Hover.gears) {
let el, fn = () => el.style.right = "3vw";
Hover.gears = el = $.c("div");
el.className = "nextgears";
$.ins(el, "beforeend", SVG.next);
$.add(el);
requestAnimationFrame(fn);
}
Hover.nextEl.className = "next loadingu";
Hover.lastInList = {
move,
id: Hover.nextEl.previousElementSibling.dataset.id
};
void Hover.nextEl.offsetWidth; // reflow hack
const timer = setTimeout(() => {
Hover.nextEl = $.rm(Hover.nextEl);
Hover.size();
Hover.noGears();
$.keyDown({warn: true});
}, 2E3);
d.dispatchEvent(new CustomEvent(ns + "-next", { detail: timer }));
},
size() {
const { children } = Hover.target;
let length = children.length;
Hover.target.style.width = length * (180 + paheal) + "px";
if (Pos.el) {
if (children[length - 1].className === "next") --length;
Pos.fn(length);
}
},
build(el) {
const span = $.c("span"), img = $.c("img");
img.src = $("img", el).src;
img.alt = img.dataset.nth = ++Hover.index;
img.title = el.firstElementChild.title;
span.dataset.id = el.dataset.id;
if (el.dataset.gif) img.style.outline = "2px solid lime";
if (el.dataset.res) span.dataset.res = el.dataset.res;
$.add(img, span);
if (Hover.nextEl) Hover.target.insertBefore(span, Hover.nextEl);
else $.add(span, Hover.target);
Hover.size();
if (Hover.lastInList.move === true && Hover.lastInList.id === Main.el.dataset.id) {
$.keyDown({keyCode: 39});
Hover.lastInList.move = false;
}
},
click(e) {
e = e.target.src;
if (!e) return;
if (Hover.prevent)
return Hover.prevent = null;
Main.slide(e);
Hover.center(e);
if (Pos.el) Pos.fn($(`img[src*="${$.base(e)}"]`, Hover.el).dataset.nth);
},
center(src) {
if (!(Hover.wrap || Hover.el)) return;
const base = $.base(src),
img = $(`img[src*="${base}"]`, Hover.el),
pos = img.dataset.nth,
scroll = Hover.wrap,
half = scroll.offsetWidth / 2,
width = 180 + paheal,
dist = width * pos + width / 2,
res = dist - half,
curr = $(".current", Hover.el);
if (curr) curr.removeAttribute("class");
Hover.cancel();
Hover.el.children[1].removeAttribute("style");
scroll.scrollLeft = res > 0 ? res : 0;
img.parentNode.setAttribute("class", "current");
},
kinetic() {
const view = Hover.wrap,
rm = () => Hover.el.classList.remove("showimagelist"),
unset = () => Hover.el.classList.add("showimagelist");
let offset, reference, velocity, frame, timestamp, ticker, amplitude, target, pressed = false;
function scroll(x) {
const max = view.scrollLeftMax;
offset = x > max ? max : x < 0 ? 0 : x;
view.scrollLeft = offset;
if (offset === 0 || offset === max) {
amplitude = 0;
Hover.cancel();
rm();
}
}
function track() {
let now = Date.now(),
elapsed = now - timestamp,
delta = offset - frame,
v = 1000 * delta / (1 + elapsed);
timestamp = now;
frame = offset;
velocity = 0.8 * v + 0.2 * velocity;
}
function autoScroll() {
if (amplitude) {
const elapsed = Date.now() - timestamp, delta = -amplitude * Math.exp(-elapsed / 15E2);
if (delta > 5 || delta < -5) {
unset();
scroll(target + delta);
Hover.kinetID = requestAnimationFrame(autoScroll);
}
else {
rm();
scroll(target);
}
}
}
$.on(view, "mousedown", function tap(e) {
Hover.prevent = !(pressed = true);
unset();
clearInterval(ticker);
velocity = amplitude = 0;
if (e.target === view) return pressed = false;
reference = e.clientX;
offset = view.scrollLeft;
frame = offset;
timestamp = Date.now();
ticker = setInterval(track, 100 / 3);
}, passive);
$.on(d.body, "mousemove", function drag(e) {
if (pressed) {
const x = e.clientX, delta = reference - x;
if (delta > 1 || delta < -1) {
Hover.prevent = true;
reference = x;
scroll(offset + delta);
}
}
}, passive);
$.on(d.body, "mouseup", function release() {
pressed = false;
clearInterval(ticker);
if (velocity > 10 || velocity < -10) {
amplitude = 0.8 * velocity;
target = offset + amplitude & -1;
timestamp = Date.now();
Hover.kinetID = requestAnimationFrame(autoScroll);
}
else rm();
}, passive);
},
cancel() {
$.safe(cancelAnimationFrame, null, Hover.kinetID);
}
};
/**
* MODULE FOR THE THINGS THAT GET THE BALL ROLLING
*/
Main = {
sel: ".thumb:not(a)",
animationEnd(e) {
switch(e.animationName) {
case "Outlined":
e.target.classList.remove("outlined");
break;
case "nomoreimages":
$.keyDown.el = $.rm(e.target);
break;
case "menuelement":
Menu.el = $.rm(e.target);
break;
case "warn":
$.rm(e.target);
break;
case "progfail": case "progdone":
Prog.el = $.rm(e.target);
}
},
finalizeCss() {
const style = $.extend($.c("style"), { type: "text/css" });
$.add(new Text(Main.css), style);
Object.defineProperty(Main, "css", {
get() {
return style.textContent;
},
set(moreCss) {
moreCss = new Text(style.textContent + moreCss);
{
const arr = style.childNodes;
let i = arr.length;
while (~--i) $.rm(arr[i]);
}
style.appendChild(moreCss);
return style.textContent;
}
});
$.add(style, d.documentElement);
},
async initDomainInfo() {
switch(site.name) {
case "gelbooru":
Main.r[0] = /<a href="?([^">]+)"?[^>]*>Orig/;
Main.r[1] = /src="?([^">]+)"? id="?image"?[^>]*\/>/;
Main.css += "span.thumb {\n float: left;\n display: inline-block;\n width: 180px;\n height: 180px;\n text-align: center\n}\nspan.thumb + center::before {\n content: '';\n display: block;\n clear: both\n}\n.slideshow hr {\n margin: initial;\nborder: initial;\n height: initial\n}";
break;
case "splatoon": case "booru":
Main.r[0] = Main.r[1] = /<img alt="img" src="([^"]+)/i;
Main.css += "span.thumb {\n float: left\n}\n[data-never-hide] {\n display: inline ! important;\n}";
$.r(() => {
const start = $(".thumb").parentNode;
let el, i = 10;
while (i) {
if ((el = start.nextSibling) && el.id !== "paginator") $.rm(el);
else --i;
}
start.dispatchEvent(new CustomEvent("scroll", { bubbles: true }));
});
break;
case "e621":
Main.css += ".thumb > a[data-id] {\n display: inline-block;\n margin-bottom: -3px\n}";
break;
case "tbib":
Main.r[0] = /<a href="?([^"> ]+)"? [^>]*?>\s*Orig/;
Main.r[1] = /<img[^>]*?src="?([^"> ]+)"? [^>]*?id="?image"?[^>]*>/;
Main.css += "div:not([style*='none;padding:10px 0']) {\n background-color: transparent\n}";
break;
case "sankakucomplex":
Main.sel = "#post-list div.content > div:first-of-type > .thumb";
case "hypnohub":
$.r($.eval, site.inject);
case "yande": case "lolibooru": case "konachan":
Main.r[1] = /id="?image"? [^>]*?src="?([^">]+)"?[^>]*\/>/;
Main.css += ".javascript-hide[id] {\n display: inline-block ! important\n}\nspan.thumb {\n width: 180px;\n height: 180px;\n text-align: center\n}\nspan.thumb, span.thumb a {\n display: inline-block\n}\nspan.thumb .preview, .listimage span img {\n max-width: 150px;\n max-height: 150px;\n overflow: hidden;\n display: inline-block\n}";
break;
case "atfbooru":
Main.sel = "div#posts article[id^='post']";
Main.css += "div > article.post-preview {\n overflow: initial\n}\narticle.post-preview > a {\n overflow: hidden\n}";
break;
case "safebooru":
Main.css += "img[title*=' rating:'][src*='.png'] {\n background-color: rgba(255,255,255,.5)\n}";
}
Main.finalizeCss();
},
click(e) {
if (e.button === 0) {
let target = e.target;
while (target && !target.hasAttribute("data-full")) target = target.parentNode;
e.preventDefault();
e.stopPropagation();
if (target) Main.fn(target);
}
},
process(node, full) {
let a, id, alt;
if (!(typeof node === "object" && node.nodeType === 1)) return;
if (node.tagName === "LI" && String(node.id)[0] === "p" && ~String(node.className).indexOf("creator-id-")) return setTimeout(Main.myImuoto, 0, node);
if (site.gelbooru && node.classList.contains("thumbnail-preview")) node = Main.gelbooruFix(node);
if (node.matches(Main.sel) && (a = node.firstElementChild) && !a.dataset.full) {
alt = $("img[alt]", node);
if (!(alt && (alt = alt.title || alt.alt)))
return;
id = (node.id || a.id || a.children[0].id).match(Main.r[4])[0];
switch(site.name) {
case "gelbooru":
a.setAttribute("href", `/index.php?page=post&s=view&id=${id}`);
break;
case "booru":
case "splatoon":
a.setAttribute("data-never-hide", "");
}
if (~alt.indexOf("animated_gif")) {
a.firstElementChild.style.border = "2px solid lime";
a.dataset.gif = "gif";
}
else if (Main.r[3].test(alt) || $$("img[src*='webm-preview.png']", node).length) return a.style.cursor = "alias";
a.dataset.id = id;
a.dataset.full = typeof full === "string" ? full : site.paheal ? node.children[2].href : site.atfbooru ? node.dataset[fullImage ? "largeFileUrl" : "fileUrl"] : $.cache(id);
node.removeAttribute("onclick");
a.removeAttribute("onclick");
if (Hover.el) Hover.build(a);
if (Hover.gears && Hover.gears.style.opacity !== "0")
Hover.noGears();
if (!Main.attachedClickListener) {
Main.attachedClickListener = true;
$.on(node.parentNode, "click", Main.click);
}
}
},
init() {
if (!(site.sankakucomplex || site.atfbooru) && location.pathname === "/")
return $.r(Main.front);
Main.initDomainInfo();
const observer = new MutationObserver(mutations => {
for (let i = 0, len1 = mutations.length; i < len1; ++i)
for (let j = 0, arr = mutations[i].addedNodes, len2 = arr.length; j < len2; ++j)
Main.process(arr[j]);
});
Main.offObj = [{ detail: true }, Main.off];
Main.onObj = [{ detail: false }, Main.on];
observer.observe(d, {
childList: true,
subtree: true
});
$.on("animationend", Main.animationEnd);
$.on(w, "keypress", e => {
if (e.keyCode === 13) {
if (slideshow)
Btn.cb();
else if (e.target.matches(".thumb > a[data-full]")) {
e.preventDefault();
Main.fn(e.target);
}
}
else if (slideshow && e.charCode === 32) {
Btn.cb.move();
}
});
$.on(w, "wheel", e => {
if (e.ctrlKey)
e.preventDefault();
if (Main.el)
$.keyDown({ keyCode: e.deltaY > 0 ? 39 : e.deltaY < 0 ? 37 : 0 });
});
$.r(Main.ready);
$.r(Hover.init);
},
ready() {
if (WIREFRAME) {
let debug = $.c("div");
debug.id = "debug";
$.add(debug);
$.ins(debug, "beforeend", SVG.debug);
Main.css += `#debug{display:${["none","block"][WIREFRAME-1]};position:fixed;z-index:10;top:0;left:0;width:100vw;height:100vh;pointer-events:none;display:block}.sliding > div#debug{display:block}#debug *{pointer-events:none}#zoom_top{border:1px solid red}`;
}
for (let i = 0, arr = [["textarea", "copy"], ["a", "realDl"]]; i < 2; ++i) {
const [tag, prop] = arr[i];
Menu[prop] = $.c(tag);
Menu[prop].classList.add("oFfScReEn");
$.add(Menu[prop]);
}
},
gelbooruFix(el) {
const img = el.querySelector("img");
if (!~String(img.getAttribute("src")).indexOf("/")) {
img.className = "preview";
let key = Main.gelbooruKey;
if (!key) {
for (let i = 0, arr = Object.entries(img.dataset), len = arr.length; i < len; ++i) {
const [k, v] = arr[i];
if (Main.r[5].test(v)) {
Main.gelbooruKey = key = k;
break;
}
}
}
img.setAttribute("src", img.getAttribute(`data-${key}`));
img.removeAttribute(`data-${key}`);
}
const span = el.firstElementChild;
el.parentNode.replaceChild(span, el);
return span;
},
myImuotoOnReady(root) {
const arr = root.childNodes;
let i = arr.length;
while (~--i) {
const child = arr[i];
if (child.nodeType !== 1) root.removeChild(child);
}
},
myImuoto(el) {
const thumb = $.c("span"), id = el.id.substr(1), img = el.children[0].children[0].children[0], full = el.lastElementChild.href;
if (!Main.myImuoto.readied) {
$.r(Main.myImuotoOnReady, el.parentNode);
Main.myImuoto.readied = true;
}
thumb.id = el.id;
thumb.className = `creator-id-${id} thumb`;
thumb.innerHTML = `<a id="s${id}" href="/post/show/${id}" data-res="${el.lastElementChild.lastElementChild.textContent.replace(Main.r[6], "\u00A0")}"><img class="preview" src="${img.src}" alt="${img.alt}" title="${img.alt}" /></a>`;
el.parentNode.replaceChild(thumb, el);
return Main.process(thumb, fullImage || full.match(Main.r[2])[1].toLowerCase() === "gif" ? full : null);
},
fn(node) {
const [msg, method] = Main.el ? Main.offObj : Main.onObj;
d.dispatchEvent(new CustomEvent(ns, msg));
const _ = $.safe(method, null, node);
if (_ === $.safe.error)
console.error(_);
},
off(a) {
const reqs = Prog.reqs;
for (let i = 0, arr = $.keys(reqs), len = arr.length; i < len; ++i) {
const _req = reqs[arr[i]];
$.safe(_req.abort, _req);
}
Prog.reqs = Object.create(null);
for (let i = 0, arr = $$("iframe.proxY"), len = arr.length; i < len; ++i)
$.rm(arr[i]);
if (slideshow) Btn.cb();
slideshow = !(a = $.current());
Main.el = $.rm(Main.el);
d.body.classList.remove("sliding");
a.classList.add("outlined");
$.off("mousemove", $.zoom);
$.off("keydown", $.keyDown);
$.off("keyup", $.keyUp);
if (a) {
let correction = 0;
if (site.sankakucomplex || site.e621 || site.atfbooru) {
a = a.firstElementChild;
correction = a.offsetParent.offsetTop;
}
w.scrollTo(0, a.offsetTop + correction + a.offsetHeight / 2 - w.innerHeight / 2);
a.focus();
Btn.fn(); Pos.fn();
}
if (site.sankakucomplex) uW.Sankaku.Pagination.auto_enabled = true;
for (let i = 0, arr = [[Main, "gif"], [Prog], [Menu], [$.keyDown], [Hover, "gears"]]; i < 5; ++i) {
const [p, el = "el"] = arr[i];
p[el] = $.rm(p[el]);
}
},
_on: e => e.button === 1 && $.keyDown({ keyCode: 38, event: e }),
on(a) {
if (site.sankakucomplex) uW.Sankaku.Pagination.auto_enabled = false;
d.body.classList.add("sliding");
{
const arr = $$("a.outlined[data-full]");
let i = arr.length;
while (~--i) arr[i].classList.remove("outlined");
}
$.add($.extend(Main.el = $.c("img"),
{ id: "slide", alt: "Loading...", onclick: Main.fn, onmouseup: Main._on }
));
$.add(Main.gif = $.extend($.c("span"), { innerHTML: SVG.gif, className: "gif" }));
const _ = $.safe(Main.slide, null, $("img", a).src);
if (_ === $.safe.error) {
console.error(_);
return Main.off();
}
$.on("keyup", $.keyUp);
$.on("keydown", $.keyDown);
$.on("mousemove", $.zoom);
Pos.fn(); Btn.fn(); $.preload();
},
isGif(match) {
return match && match[1] ? match[1].toLowerCase() === "gif" : null;
},
slide(src) {
if (!slideshow)
Main.el.src = src;
if ($.zoom.el)
$.zoom.el = $.rm($.zoom.el);
Hover.center(src);
Main.el.dataset.src = src;
Main.gif.removeAttribute("style");
Main.el.removeAttribute("style");
const curr = $.current(src), data = curr.dataset.full, id = curr.dataset.id;
Main.el.dataset.id = id;
/* dirty hack ahead because GIF doesn't want to play as a blob and doesn't
* give proper progress info for GM_xmlhttpRequest for whatever reason */
const isGif = Main.isGif(data.match(Main.r[2]));
if (data === "loading") Main.req(curr);
else if (curr.dataset.blob) {
Main.el.src = curr.dataset.blob;
if (Menu.el) Menu.download();
if (Prog.el) Prog.el = $.rm(Prog.el);
const el = Prog.progress();
el.style.width = "100%";
el.classList.add("progdone");
}
// hack start
else if (isGif !== false) {
if (isGif) {
Main.el.removeAttribute("src");
Main.el.src = data;
Main.gif.setAttribute("style", "display: inline-block");
}
else Main.el.dispatchEvent(new Event("load"));
}
// hack end
else Prog.fn(data, id);
},
r: [/file_url[=>]"?([^" <]+)"?/i,/* 0 */ /sample_url[=>]"?([^" <]+)"?/i,/* 1 */ /\.(gif|png|jpe?g)/i,/* 2 */ /\b(webm|video|mp4|flash)\b/i,/* 3 */ /\d+/,/* 4 */ /[a-f0-9]{32}/,/* 5 */ / /g]/* 6 */,
processHttp(x) { if (!$.in(x.status, httpOk)) throw `HTTP status: ${x.status}`; return x.text(); },
validateHtml(img, el) {
return img.match(el.dataset.gif || fullImage ? Main.r[0] : Main.r[1])[1];
},
checkPreviewId(img) {
return ~Main.el.dataset.src.indexOf($.base(img));
},
processText(img, node, id) {
if ((img = $.safe(Main.validateHtml, null, img, node)) === $.safe.error)
throw "API error";
node.dataset.full = $.cache(id, img);
const _ = $.safe(Main.checkPreviewId, null, img);
if (typeof _ === "number" && _)
$.safe(Main.slide, null, img);
$("img", node).style.outline = "";
node.removeAttribute("data-already-loading");
},
getApiInfo(node) {
switch(domain) {
case "e621.net":
return [node.parentNode.id.substr(1), "/post/show.xml?id="];
case "sankakucomplex.com":
return [node.dataset.id, "/post/show/"];
case "yande.re":
case "lolibooru.moe":
case "konachan.com":
case "hypnohub.net":
return [node.parentNode.id.substr(1), "/post/show/"];
case "booru.org":
case "tbib.org":
case "splatoon.ink":
case "gelbooru.com":
return [node.dataset.id, "/index.php?page=post&s=view&id="];
default:
return [node.dataset.id, "/index.php?page=dapi&s=post&q=index&id="];
}
},
async req(node) {
if (!node) return;
const { dataset } = node;
if (dataset.alreadyLoading || dataset.full !== "loading") return;
const { 0: id, 1: api } = Main.getApiInfo(node);
dataset.alreadyLoading = "true";
try {
if (site.sankakucomplex) return await Prog.fn(api + id, id, node);
const request = await fetch(api + id);
const text = await Main.processHttp(request);
Main.processText(text, node, id);
}
catch (e) {
Main.warn();
$("img", node).style.outline = "6px solid red";
console.error(`Main.req failure:\n\n${location.origin + api + id} |`, e);
node.removeAttribute("data-already-loading");
}
},
warn() {
const warn = $.c("span");
warn.innerHTML = SVG.warn;
warn.classList.add("warn");
if ($$(".sliding>.warn").length === 0) $.add(warn);
if (slideshow)
Main.el.dispatchEvent(new Event("load"));
},
front() {
let target, method = "beforeend", el;
switch(domain) {
case "e621.net":
target = "#mascot_artist";
method = "afterend";
break;
case "booru.org":
case "rule34.xxx":
case "splatoon.ink":
target = "#static-index > div:last-child > p:first-child";
break;
case "lolibooru.moe":
target = "#links + *";
break;
case "safebooru.org":
target = "div.space + div > p";
method = "afterend";
break;
case "hypnohub.net":
target = "form > div";
break;
case "tbib.org":
target = "div.space + div > p";
method = "afterend";
break;
default:
target = "form:last-of-type + div";
break;
}
for (let i = 0, arr = ["tags", "tags-search"]; i < 2; ++i) {
if (el = $.id(arr[i])) {
el.focus();
break;
}
}
$.ins($(target), method, `<br><br>Gelbooru Enhancement:<br><pre style="font-size: 11px;text-align: left;display: inline-block;margin-top: 5px;">- Gelbooru Image Viewer ${scriptInfo.script.version}</pre>`);
},
css: `@keyframes Outlined {\n 0% { outline: 6px solid orange }\n 60% { outline: 6px solid orange }\n 100% { outline: 6px solid transparent }\n}\n@keyframes nomoreimages {\n 0% { opacity: 0 }\n 20% { opacity: 1 }\n 100% { opacity: 0 }\n}\n@keyframes menuelement {\n 0% { opacity: 1 }\n 80% { opacity: 1 }\n 100% { opacity: 0 }\n}\n@keyframes progfail {\n 0% { opacity: 1 }\n 80% { opacity: 1 }\n 100% { opacity: 0 }\n}\n@keyframes warn {\n 0% { opacity: 0; bottom: 5vh }\n 25% { opacity: 1; bottom: 10vh }\n 90% { opacity: 1 }\n 100% { opacity: 0 }\n}\n@keyframes topglowything {\n 0% { opacity: 1 }\n 80% { opacity: 1 }\n 100% { opacity: 0 }\n}\nbody.sliding > * {\n display: none\n}\nbody.sliding * {\n -webkit-box-sizing: initial;\n -moz-box-sizing: initial;\n box-sizing: initial;\n line-height: initial\n}\nbody.sliding > .warn {\n z-index: 2;\n display: inline-block;\n position: absolute;\n width: 10vw;\n left: 45vw;\n bottom: 10vh;\n animation-duration: 2s;\n animation-name: warn;\n pointer-events: none\n}\n#slide {\n position: absolute;\n z-index: 1;\n width: 100vw;\n height: 100vh;\n object-fit: contain;\n display: inherit;\n top: 0;\n left: 0\n}\n.outlined {\n outline: 6px solid transparent;\n animation-duration: 4s;\n animation-name: Outlined\n}\nbody.sliding > div.nextgears {\n display: block;\n position: absolute;\n z-index: 2;\n width: 10vw;\n filter: url(#__dropshadow);\n right: -30vw;\n top: 50%;\n transform: translateY(-50%);\n min-width: 50px;\n transition: all ease .3s\n}\nbody.sliding > .nomoreimages {\n z-index: 4;\n pointer-events: none;\n display: block;\n width: 33vw;\n height: 100vh;\n top: 0;\n position: fixed;\n animation-duration: 1s;\n animation-name: nomoreimages\n}\nspan.thumb {\n max-width: 180px;\n max-height: 180px\n}\n#menuel {\n z-index: 3;\n opacity: 1;\n position: fixed;\n display: block;\n padding: 2px;\n background: black;\n width: 139px;\n height: 67px;\n overflow: hidden;\n animation-duration: 1s\n}\n.gif[style] ~ #menuel {\n height: 44px\n}\nbody.sliding > .menuel {\n animation-name: menuelement\n}\n#menuel:hover {\n animation-name: keepalive\n}\n#menuel a {\n background: #fff;\n color: #006FFA;\n display: block;\n font-size: 16px;\n font-family: verdana, sans-serif;\n font-weight: unset\n}\n#menuel a:hover {\n color: #33CFFF ! important\n}\n#menuel a:visited {\n color: #006FFA\n}\n#zoom_top {\n position: fixed;\n top: ${200 + paheal}px;\n left: 0;\n pointer-events: none;\n width: calc(5vw + 60px);\n height: 60px;\n min-width: 110px;\n z-index: 2;\n overflow: hidden;\n animation-duration: .6s;\n opacity: 0\n}\n#zoom_top[style] {\n display: block\n}\n#zoom_top > span {\n position: absolute;\n top: 1px;\n transform: translateY(-100%);\n box-shadow: 0 0 30px cyan;\n background: black;\n left: 30px;\n width: 5vw;\n height: 1.6vw;\n border-radius: .8vw;\n min-width: 50px\n}\nbody.sliding > .slideshow {\n z-index: 2;\n display: block;\n position: fixed;\n bottom: 20px;\n right: 20px;\n font-size: 16px;\n font-family: verdana, sans-serif\n}\nbody.sliding > .progress {\n z-index: 6;\n display: block;\n background-color: rgb(128,200,255);\n height: 1vh;\n position: absolute;\n top: 0;\n left: 0;\n box-shadow: 0 .5vh 10px rgba(0,0,0,.7), inset 0 0 .1vh black;\n transition: ease-in-out .08s width;\n min-height: 3px;\n min-width: 5px ! important;\n max-width: 100vw;\n pointer-events: none;\n will-change: width, opacity\n}\nbody.sliding > .progfail {\n background-color: red;\n width: 100% ! important;\n animation-name: progfail;\n animation-duration: 1.5s\n}\nbody.sliding > .progdone {\n width: 100% ! important;\n animation-name: progfail;\n animation-duration: .6s\n}\n.slideshow:hover:not([data-timer]) > div {\n background: white;\n color: black;\n position: fixed;\n display: block ! important;\n bottom: 70px;\n right: 20px\n}\nbody.sliding > .gif {\n z-index: 5;\n pointer-events: none;\n position: absolute;\n top: 3vh;\n right: 2vw;\n width: 3.5vw;\n opacity: .7;\n min-width: 50px;\n transition: ease .15s margin-top;\n margin-top: 0;\n will-change: margin-top\n}\nbody.sliding {\n padding: 0;\n overflow: hidden\n}\nbody.sliding > .viewpre {\n display: block\n}\nbody.sliding > .viewpre.showimagelist > .tentcon {\n transform: unset\n}\n.viewpre > div {\n display: inherit;\n z-index: 5;\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: ${200 + paheal}px\n}\n.viewpre > .tentcon {\n transform: translateY(-100%);\n transition: ease .15s transform;\n background: linear-gradient(to bottom, black, transparent);\n will-change: transform\n}\n.viewpre:hover > .tentcon {\n transform: unset\n}\n.viewpre .wrapthatshit, .viewpre .wrapthatshit > .listimage {\n transform: rotateX(180deg);\n}\n.viewpre.showimagelist ~ .gif, .viewpre:hover ~ .gif {\n margin-top: ${200 + paheal * 2}px\n}\n.wrapthatshit {\n height: 100%;\n width: 100%;\n overflow-x: auto\n}\n.listimage {\n height: ${180 + paheal}px;\n -webkit-user-select: none;\n -moz-user-select: none;\n user-select: none;\n margin: 0 auto\n}\n.listimage span {\n height: ${180 + paheal}px;\n width: ${180 + paheal}px;\n text-align: center;\n display: table-cell ! important;\n vertical-align: middle\n}\n.listimage span img {\n cursor: pointer\n}\n.listimage .current {\n background: linear-gradient(to top, transparent 0%, hsla(204, 100%, 56%, .8) 2%, transparent 30%, transparent 100%)\n}\n.listimage .next::after {\n content: "LOAD\\ANEXT\\APAGE";\n font-size: 30px;\n text-transform: full-width;\n white-space: pre-wrap;\n color: white;\n filter: url(#__dropshadow)\n}\n.listimage .next {\n cursor: pointer\n}\nbody.sliding > .posel {\n position: fixed;\n bottom: 20px;\n left: 0;\n display: block;\n pointer-events: none;\n z-index: 2;\n font-size: 16px;\n font-family: verdana, sans-serif\n}\n.posel > div {\n position: relative;\n color: #fff;\n z-index: 2\n}\n.posel::before {\n content: attr(title);\n position: absolute;\n -webkit-text-stroke: 2px black;\n left: 0;\n z-index: 1\n}\n[data-res]:hover::after {\n content: attr(data-res);\n color: white;\n position: absolute;\n top: 8px;\n left: 50%;\n transform: translateX(-50%);\n padding: 3px 5px;\n background: rgba(0,0,0,.7);\n border-radius: 5px;\n border: 2px black solid;\n box-shadow: 0 0 2px 1px black;\n pointer-events: none\n}\n[data-res]:hover {\n position: relative;\n display: inline-block\n}\nbody:not(.sliding) > div.viewpre {\n display: none ! important\n}\nbody > .oFfScReEn {\n display: block;\n position: fixed;\n top: -500px\n}`
};
Main.init();