您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a fullscreen image view option when you click on images and various other neat features
// ==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();