您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Refining yande.re
当前为
// ==UserScript== // @name yande.re refine // @namespace https://greasyfork.org/scripts/397612-yande-re-refine // @description Refining yande.re // @include *://behoimi.org/* // @include *://www.behoimi.org/* // @include *://*.donmai.us/* // @include *://konachan.tld/* // @include *://yande.re/* // @include *://chan.sankakucomplex.com/* // @version 2020.03.15.a // @grant none // ==/UserScript== const STORAGE_KEY = "yandere-refine-liked"; const SUGGEST_WIDTH = 300; //Minimum amount of window left to scroll, maintained by loading more pages. const scrollBuffer = 600; //Time (in ms) the script will wait for a response from the next page before attempting to fetch the page again. If the script gets trapped in a loop trying to load the next page, increase this value. const timeToFailure = 15000; //============================================================================ //=========================Script initialization============================== //============================================================================ var nextPage, mainTable, mainParent, timeout, iframe; let previewIframe, previewImage, previewImageDiv, previewDialog; let imagesList = []; let currentImage = 0; let pending = ref(false, v => document.getElementById("loader").classList.toggle("hidden", !v) ); let likedList = readLikeList(); let viewingFavorites = isViewingFavorites(); injectGlobalStyle(); initialize(); function initialize() { //Stop if inside an iframe if (window != window.top || scrollBuffer == 0) return; //Stop if no "table" mainTable = getMainTable(document); if (!mainTable) return; injectStyle(); initDOM(); addImages(getImages()); //Stop if no more pages nextPage = getNextPage(document); if (!nextPage) return; //Hide the blacklist sidebar, since this script breaks the tag totals and post unhiding. var sidebar = document.getElementById("blacklisted-sidebar"); if (sidebar) sidebar.style.display = "none"; //Other important variables: mainParent = mainTable.parentNode; pending.value = false; iframe = document.createElement("iframe"); iframe.width = iframe.height = 0; iframe.style.visibility = "hidden"; document.body.appendChild(iframe); //Slight delay so that Danbooru's initialize_edit_links() has time to hide all the edit boxes on the Comment index iframe.addEventListener( "load", function(e) { setTimeout(appendNewContent, 100); }, false ); window.addEventListener("scroll", testScrollPosition, false); testScrollPosition(); } //============================================================================ //============================Script functions================================ //============================================================================ //Some pages match multiple "tables", so order is important. function getMainTable(source) { //Special case: Sankaku post index with Auto Paging enabled if ( /sankaku/.test(location.host) && /auto_page=1/.test(document.cookie) && /^(post(\/|\/index\/?)?|\/)$/.test(location.pathname) ) return null; var xpath = [ ".//div[@id='c-favorites']//div[@id='posts']", // Danbooru (/favorites) ".//div[@id='posts']/div", // Danbooru; don't want to fall through to the wrong xpath if no posts ("<article>") on first page. ".//div[@id='c-pools']//section/article/..", // Danbooru (/pools/####) ".//div[@id='a-index']/table[not(contains(@class,'search'))]", // Danbooru (/forum_topics, ...), take care that this doesn't catch comments containing tables ".//div[@id='a-index']", // Danbooru (/comments, ...) ".//table[contains(@class,'highlight')]", // large number of pages ".//div[@id='content']/div/div/div/div/span[@class='author']/../../../..", // Sankaku: note search ".//div[contains(@id,'comment-list')]/div/..", // comment index ".//*[not(contains(@id,'popular'))]/span[contains(@class,'thumb')]/a/../..", // post/index, pool/show, note/index ".//li/div/a[contains(@class,'thumb')]/../../..", // post/index, note/index ".//div[@id='content']//table/tbody/tr[@class='even']/../..", // user/index, wiki/history ".//div[@id='content']/div/table", // 3dbooru user records ".//div[@id='forum']" // forum/show ]; for (var i = 0; i < xpath.length; i++) { getMainTable = (function(query) { return function(source) { var mTable = new XPathEvaluator().evaluate( query, source, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue; if (!mTable) return mTable; //Special case: Danbooru's /favorites lacks the extra DIV that /posts has, which causes issues with the paginator/page break. var xDiv = document.createElement("div"); xDiv.style.overflow = "hidden"; mTable.parentNode.insertBefore(xDiv, mTable); xDiv.appendChild(mTable); return xDiv; }; })(xpath[i]); var result = getMainTable(source); if (result) { //alert("UPW main table query: "+xpath[i]+"\n\n"+location.pathname); return result; } } return null; } function getNextPage(doc = document) { return (doc.querySelector("a.next_page") || {}).href; } function testScrollPosition() { if (!nextPage) return; //Take the max of the two heights for browser compatibility if ( !pending.value && window.pageYOffset + window.innerHeight + scrollBuffer > Math.max( document.documentElement.scrollHeight, document.documentElement.offsetHeight ) ) { console.log("loading " + nextPage); pending.value = true; timeout = setTimeout(function() { pending.value = false; testScrollPosition(); }, timeToFailure); iframe.contentDocument.location.replace(nextPage); } } function appendNewContent() { //Make sure page is correct. Using 'indexOf' instead of '!=' because links like "https://danbooru.donmai.us/pools?page=2&search%5Border%5D=" become "https://danbooru.donmai.us/pools?page=2" in the iframe href. clearTimeout(timeout); if (nextPage.indexOf(iframe.contentDocument.location.href) < 0) { setTimeout(function() { pending.value = false; }, 1000); return; } let images = getImages(iframe.contentDocument); addImages(images); if (!images.length) nextPage = null; else { nextPage = getNextPage(iframe.contentDocument); } if (nextPage) { history.pushState({}, iframe.contentDocument, nextPage); } else { // TODO: end of pages console.log("End of pages"); } pending.value = false; testScrollPosition(); } function injectGlobalStyle() { const s = document.createElement("style"); s.innerHTML = ` body { padding: 0; } #header { margin: 0 !important; text-align: center; } #header ul { float: none !important; display: inline-block;} #content > div:first-child > div.sidebar { position: fixed; left: 0; top: 0; bottom: 0; overflow: auto !important; z-index: 2; width: 200px !important; transform: translate(-200px, calc(-100vh + 30px)); border-bottom-right-radius: 30px; background: #171717dd; transition: all .2s ease-out; float: none !important; padding: 15px; } #content > div:first-child > div.sidebar:hover { transform: translateX(0); } div.content { width: 100vw; text-align: center; float: none } div.footer { clear: both !important; } div#paginator a { border: none; } #comments { max-width: unset !important; width: unset !important; padding: 20px; } .avatar { border-radius: 1000px; } form textarea { color: white; background: inherit; padding: 10px 5px; } .comment .content { text-align: left; } `; document.body.appendChild(s); } function injectStyle() { const s = document.createElement("style"); s.innerHTML = ` #gallery .row { width: 100vw; white-space: nowrap; height: var(--image-height); --image-height: 300px; } #gallery .row .thumb { position: relative; display: inline-block; transition: .2s ease-out; overflow: hidden; } #gallery .row .thumb.liked::after { position: absolute; top: 3px; right: 3px; content: url('https://api.iconify.design/mdi:cards-heart.svg?color=%23f37e92&height=20'); vertical-align: -0.125em; } #gallery .row .thumb:first-child { transform-origin: left; } #gallery .row .thumb:last-child { transform-origin: right; } #gallery .row .thumb img { height: var(--image-height); } #gallery .row:hover { z-index: 1; } #gallery .row .thumb:hover { transform: scale(1.3); z-index: 1; opacity: 1; box-shadow: 8px 8px 100px 10px rgba(0, 0, 0, 0.8); border-radius: 5px; } #loader { padding: 10px; text-align: center; } .hidden { display: none !important; } .preivew-dialog { position: fixed; top: 0; left: 0; height: 100vh; width: 100vw; background: rgba(0,0,0,0.7); z-index: 100; } .preivew-dialog iframe { position: absolute; height: 90vh; width: 80vw; top: 50%; left: 50%; transform: translate(-50%, -50%); background: grey; border: none; border-radius: 5px; overflow: hidden; } .preivew-dialog .image-host { position: fixed; top: 0; left: 0; height: 100vh; width: 100vw; overflow: auto; text-align: center; } .preivew-dialog .image-host img { margin: auto; } .preivew-dialog .image-host img.loading { filter: blur(3px); height: 100vh; } .preivew-dialog .image-host.full { overflow: hidden } .preivew-dialog .image-host.full img { max-width: 100vw; max-height: 100vh; } `; document.body.appendChild(s); } function initPreviewIframe() { previewDialog = document.createElement("div"); previewDialog.addClassName("preivew-dialog hidden"); previewDialog.onclick = e => { if (e.target === previewDialog) previewDialog.classList.toggle("hidden", true); }; window.onkeydown = e => { if (!previewDialog.classList.contains("hidden")) { if (e.key === "ArrowLeft") { currentImage = Math.max(0, currentImage - 1); openImage(currentImage); e.preventDefault(); } if (e.key === "ArrowRight") { currentImage = Math.min(imagesList.length - 1, currentImage + 1); openImage(currentImage); e.preventDefault(); } if (e.key === "Escape") { previewDialog.classList.toggle("hidden", true); e.preventDefault(); } if (e.key === "Tab") { openImage(currentImage, "page"); e.preventDefault(); } if (e.code === "Space") { previewImageDiv.classList.toggle("full"); e.preventDefault(); } if (e.code === "KeyL") { like(currentImage, 3); e.preventDefault(); } if (e.code === "KeyU") { unlike(currentImage, 2); e.preventDefault(); } } }; previewIframe = document.createElement("iframe"); previewImageDiv = document.createElement("div"); previewImageDiv.className = "image-host full"; previewImage = document.createElement("img"); previewImageDiv.onclick = e => { previewDialog.classList.toggle("hidden", true); }; previewDialog.appendChild(previewIframe); previewImageDiv.appendChild(previewImage); previewDialog.appendChild(previewImageDiv); document.body.appendChild(previewDialog); } function ref(v, handler) { let value = v; return new Proxy( {}, { get(obj, prop) { return value; }, set(obj, prop, v) { if (value !== v) { value = v; handler(value); } } } ); } function getImages(doc = document) { const result = Array.from( doc.querySelectorAll("ul#post-list-posts > li") ).map(li => { const page = (li.querySelector("a.thumb") || {}).href; const thumb = (li.querySelector("a.thumb img") || {}).src; const large = (li.querySelector("a.largeimg") || {}).href; const id = page.split("/").slice(-1)[0]; const resText = (li.querySelector(".directlink-res") || {}).innerText; let res = undefined; if (resText && resText.includes("x")) { let [height, width] = resText.split(" x ").map(i => +i); res = { height, width, radio: width / height }; } if (viewingFavorites) setLiked(id, true); let liked = isLiked(id); return { page, thumb, large, id, res, liked }; }); doc.getElementById("post-list-posts").remove(); return result; } function initDOM() { const list = document.getElementById("post-list"); const gallery = document.createElement("div"); gallery.id = "gallery"; list.appendChild(gallery); const loader = document.createElement("div"); loader.id = "loader"; loader.innerText = "Loading..."; list.appendChild(loader); } function addImages(images) { const gallery = document.getElementById("gallery"); const RADIO = Math.round(window.innerWidth / SUGGEST_WIDTH); images.forEach((info, i) => { let idx = imagesList.length + i; let row = gallery.querySelector(".row:last-child:not(.full)"); if (!row) { row = document.createElement("div"); row.className = "row"; gallery.appendChild(row); } row.dataset.width = +(row.dataset.width || 0) + 1 / info.res.radio; if (+row.dataset.width >= RADIO) { row.classList.toggle("full", true); row.style = `--image-height: calc(100vw / ${row.dataset.width})`; } const thumb = document.createElement("div"); thumb.className = "thumb"; row.appendChild(thumb); const img = document.createElement("img"); img.src = info.thumb; thumb.appendChild(img); info.dom = thumb; thumb.classList.toggle("liked", info.liked); let lastClicked = -Infinity; let timer = null; img.onclick = e => { e.preventDefault(); // double click if (Date.now() - lastClicked < 300) { if (info.liked) unlike(idx); else like(idx); clearTimeout(timer); // click } else { lastClicked = +Date.now(); timer = setTimeout(() => openImage(idx), 400); } return false; }; }); imagesList.push(...images); } function openImage(idx, type = "image") { currentImage = idx; const img = imagesList[idx]; const { page, large, id, thumb } = img; if (!previewIframe) initPreviewIframe(); if (!large) type = "page"; if (type === "image") { previewImage.classList.toggle("loading", true); previewImage.src = thumb; previewImage.onload = () => { previewImage.src = large; previewImage.onload = () => { previewImage.classList.toggle("loading", false); previewImage.onload = null; }; }; previewIframe.classList.toggle("hidden", true); previewImageDiv.classList.toggle("hidden", false); } else { previewIframe.onload = () => { if ( previewIframe.contentWindow.location.href !== page && !previewIframe.contentWindow.location.pathname.startsWith("/post/show/") ) { location.href = previewIframe.contentWindow.location.href; previewDialog.classList.toggle("hidden", true); previewIframe.onload = null; } }; previewIframe.src = page; previewIframe.classList.toggle("hidden", false); previewImageDiv.classList.toggle("hidden", true); } previewDialog.classList.toggle("hidden", false); } function getFavoritesLike() { return document.querySelector(".user .submenu li:nth-child(3) a").href; } function isViewingFavorites() { let fav = getFavoritesLike(); if (!fav) return false; let a = (new URL(fav).searchParams.get("tags") || "") .toLowerCase() .split(" ") .sort(); let b = (new URL(location.href).searchParams.get("tags") || "") .toLowerCase() .split(" ") .sort(); return a[0] == b[0] && a[1] == b[1]; } async function vote(id, score) { let body = new FormData(); body.append("id", id); body.append("score", score); const rawResponse = await fetch("https://yande.re/post/vote.json", { method: "POST", headers: { "X-CSRF-Token": document.querySelector("meta[name=csrf-token]").attributes .content.value }, body }); const content = await rawResponse.json(); } function readLikeList() { return Object.fromEntries( (localStorage.getItem(STORAGE_KEY) || "").split(",").map(i => [i, true]) ); } function isLiked(id) { return !!likedList[id]; } function setLiked(id, v) { likedList[id] = v; localStorage.setItem( STORAGE_KEY, Object.entries(likedList) .map(([i, v]) => (v ? i : null)) .filter(i => i) .join(",") ); } function like(idx) { let image = imagesList[idx]; vote(image.id, 3); image.liked = true; setLiked(image.id, true); image.dom.classList.toggle("liked", image.liked); } function unlike(idx) { let image = imagesList[idx]; vote(image.id, 2); image.liked = false; setLiked(image.id, false); image.dom.classList.toggle("liked", image.liked); }