您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds infinite scroll and inline expansion on the search page and artists' works pages. For manga mode a two-step expansion is used.
当前为
// ==UserScript== // @name Pixiv Infinite Scroll/Download Links // @description Adds infinite scroll and inline expansion on the search page and artists' works pages. For manga mode a two-step expansion is used. // @namespace https://github.com/an-electric-sheep/userscripts // @match *://www.pixiv.net/search* // @match *://www.pixiv.net/member_illust* // @match *://www.pixiv.net/new_illust* // @match *://www.pixiv.net/bookmark_new_illust* // @version 0.3.3 // @grant none // @run-at document-start // ==/UserScript== "use strict"; var Maybe = function (wrapped) { if (typeof this !== "object" || Object.getPrototypeOf(this) !== Maybe.prototype) { var o = Object.create(Maybe.prototype); o.constructor.apply(o, arguments); return o; } this.wrapped = wrapped; } Maybe.prototype.isEmpty = function(){return null == this.wrapped} Maybe.prototype.orElse = function(other){return this.isEmpty() ? Maybe(other) : this} Maybe.prototype.apply = function(f){if(!this.isEmpty()){f.apply(null, [this.wrapped].concat(Array.slice(arguments, 1)))};return this;} Maybe.prototype.map = function(f){return this.isEmpty() ? this : Maybe(f.apply(null, [this.wrapped].concat(Array.slice(arguments,1))));} Maybe.prototype.get = function(){return this.wrapped;} var paginator; var loading = false; var imgContainerSelector = "._image-items, .image-items, .display_works > ul"; document.addEventListener("DOMContentLoaded", function() { for(var e of document.querySelectorAll("iframe, .ad-printservice, .popular-introduction")){e.remove()} var sheet = document.querySelector("head").appendChild(document.createElement("style")).sheet; [ // global "#wrapper {width: unset;}", // search page ".layout-body {width: 85vw;}", // member page ".layout-a {width: unset;}", ".layout-a .layout-column-2 {width: calc(100vw - 190px);}", // member works list ".display_works {width: unset;}", ".display_works .image-item {float: none; }", // search and member works list "._image-items, .image-items, .display_works > ul {display: flex;flex-wrap: wrap;}", ".image-item img {padding: 0px; border: none;}", ".inline-expandable {cursor: pointer;}", ".image-item.expanded {width: 100%; height: unset;}", ".image-item.expanded img {max-width: -moz-available;}", ".manga-item {background-color: #f3f3f3 !important;}", ".image-item img.manga-medium {max-width: 156px; max-height: 230px; cursor: pointer;}", // animated content inlined in the search page ".exploded-animation-scroller {overflow-x: auto; width: 100%; margin: 5px 0px; box-shadow: 0px 0px 4px 1px #444;}", ".exploded-animation {display: flex; width: -moz-fit-content; }", ".exploded-animation img {margin-left: 5px;}", ".control-elements {display: flex; justify-content: space-around;align-items: center;}", ".control-elements > * {position: relative;}", ].forEach(r => sheet.insertRule(r,0)) paginator = Maybe(document.querySelectorAll(".pager-container")).map(paginators => paginators[paginators.length-1]).get(); window.addEventListener("scroll", isNextNeeded) window.addEventListener("resize", isNextNeeded) for(var e of document.querySelectorAll(".image-item")){customizeImageItem(e)} isNextNeeded(); }) function inViewport (el) { var rect = el.getBoundingClientRect(); return ( rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth) ); } function mangaItemExpand() { this.removeEventListener("click", mangaItemExpand) var container = this.parentElement; var newImg = document.createElement("img"); // just try to load the big image, this may fail for some older images, just expand in that case newImg.src = this.src.replace(/(_p\d+)/, "_big$1") newImg.addEventListener("load", () => {container.replaceChild(newImg, this);container.classList.add("expanded")}) newImg.addEventListener("error", () => container.classList.add("expanded")) newImg.className = "manga" } function insertMangaItems(parentItem,url) { var req = new XMLHttpRequest req.open("get", url) req.onload = function() { var rsp = this.responseXML var nextItem = parentItem.nextSibling for(var e of rsp.querySelectorAll(".item-container")) { let mediumImg = e.querySelector(".image") let bigUrl = e.querySelector(".full-size-container").href let item = document.createElement("li") item.className = "image-item manga-item" let img = document.createElement("img") img.src = mediumImg.dataset.src img.className = "manga-medium" img.addEventListener("click", mangaItemExpand) item.appendChild(img) parentItem.parentNode.insertBefore(item, nextItem) } } req.responseType = "document" req.send() } function insertAnimationItems(container, mediumDoc) { var script = mediumDoc.querySelector("#wrapper script") // it's not a strong sandbox. it just avoids the loaded script writing to the main window var sandbox = document.createElement("iframe") sandbox.src = window.location.href sandbox.seamless = true sandbox.setAttribute("srcdoc", "<!DOCTYPE html><html><head><script async src='https://cdn.jsdelivr.net/jszip/2.2.2/jszip.min.js'></script><script>window.pixiv = {context: {}}</script><script>"+ script.firstChild.data +"</script></head></html>") sandbox.onload = () => { var sandboxWindow = sandbox.contentWindow var illustData = sandboxWindow.pixiv.context.ugokuIllustFullscreenData var req = new sandboxWindow.XMLHttpRequest req.open("get", illustData.src) req.responseType = "arraybuffer" req.onload = function () { var controlElements = document.createElement("div") controlElements.className = "control-elements" var oldElements = document.createElement("div") while(container.hasChildNodes()){oldElements.appendChild(container.firstChild)} controlElements.appendChild(oldElements) container.appendChild(controlElements) var downloadInfo = document.createElement("div") var buffer = this.response var zip = new sandboxWindow.JSZip(buffer) var downloadLink = document.createElement("a") downloadLink.className = "animation-download" downloadLink.innerHTML = downloadLink.download = sandboxWindow.pixiv.context.illustId + ".zip" downloadInfo.appendChild(document.createTextNode("Download: ")) downloadInfo.appendChild(downloadLink) downloadInfo.appendChild(document.createElement("br")) downloadInfo.appendChild(document.createTextNode("pixiv2webm and pixiv2gif available ")) downloadInfo.appendChild(Maybe(document.createElement("a")).apply(e => {e.href = "https://github.com/an-electric-sheep/userscripts"; e.innerHTML = "on github"}).get()) controlElements.appendChild(downloadInfo) var scrollContainer = document.createElement("div") var explodedAnimation = document.createElement("div") scrollContainer.className = "exploded-animation-scroller" explodedAnimation.className = "exploded-animation" scrollContainer.appendChild(explodedAnimation) container.appendChild(scrollContainer) var timingInformation = [] for(var name in zip.files){ let file = zip.file(name) let img = document.createElement("img") let imgBuf = file.asArrayBuffer() let imgBlob = new Blob([imgBuf]) img.src = URL.createObjectURL(imgBlob) timingInformation.push(file.name +"\t"+ illustData.frames.find((e) => e.file == name).delay) explodedAnimation.appendChild(img) } container.classList.add("expanded") zip.file("frame_delays.txt", timingInformation.join("\n")) downloadLink.href = URL.createObjectURL(zip.generate({type: "blob"})) sandbox.remove(); } req.send() } document.body.appendChild(sandbox) } function listItemExpand() { var container = this.parentNode var mediumLink = container.querySelector("a.work").href var req = new XMLHttpRequest req.open("get", mediumLink) req.onload = function() { var rsp = this.responseXML; if(rsp.querySelector("._ugoku-illust-player-container")) { insertAnimationItems(container, rsp) } Maybe(rsp.querySelector('.works_display a[href*="mode"]')).apply((modeLink) => { var modeLinkUrl = modeLink.href var mediumSrc = modeLink.querySelector("img").src var mode = modeLinkUrl.match(/mode=(.+?)&/)[1] if(mode == "big") { var img = container.querySelector("img") img.src = mediumSrc.replace("_m.", "."); container.classList.add("expanded") } if(mode == "manga"){ insertMangaItems(container, modeLinkUrl) } }) } req.responseType = "document" req.send() this.removeEventListener("click", listItemExpand) } const greasedImageItems = new WeakMap; function customizeImageItem(e) { if(greasedImageItems.has(e)) return; greasedImageItems.set(e, true); var workLink = e.querySelector("a.work") var img = workLink.querySelector("img") img.classList.add("inline-expandable") img.dataset.thumbSrc = img.src e.insertBefore(img, workLink) img.addEventListener("click", listItemExpand) } function loadNext() { if(loading) return; loading = true; var nextLink = paginator.querySelector("a[rel=next]") if(nextLink) { var req = new XMLHttpRequest(); req.open("get", nextLink.href) req.onload = function() { var rsp = this.responseXML; var container = document.querySelector(imgContainerSelector) for(var e of rsp.querySelectorAll(".image-item")){ var imageItem = document.importNode(e, true) container.appendChild(imageItem) customizeImageItem(imageItem) } while(paginator.hasChildNodes()) paginator.firstChild.remove() for(var e of rsp.querySelector(".pager-container").childNodes){paginator.appendChild(document.importNode(e, true) )} loading = false; isNextNeeded(); } req.responseType = "document" req.send() } } function isNextNeeded() { if(loading) return; if(paginator && inViewport(document.querySelector(".image-item:last-child"))) { loadNext(); } }