您需要先安装一个扩展,例如 篡改猴、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.11.a // @grant none // ==/UserScript== //If true, each added page retains its paginator. If false, elements are smoothly joined together. var pageBreak = false //Minimum amount of window left to scroll, maintained by loading more pages. var 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. var timeToFailure = 15000 var previewLocked = false //============================================================================ //=========================Script initialization============================== //============================================================================ var nextPage, mainTable, mainParent, timeout, iframe const SUGGEST_WIDTH = 300 let previewIframe, previewImage, previewImageDiv, previewDialog let imagesList = [] let currentImage = 0 let pending = ref(false, (v) => document.getElementById('loader').classList.toggle('hidden', !v)) 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 || !pageBreak) 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; } #title { display: none !important; } #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; } ` 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: 10px; right: 10px; content: url('https://api.iconify.design/mdi:cards-heart.svg?color=%23f37e92&height=24'); 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) => { console.log(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') previewImage.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 } } let liked = false 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 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) { 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) } 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() console.log(content) } function like(idx) { let image = imagesList[idx] vote(image.id, 3) image.liked = true image.dom.classList.toggle('liked', image.liked) } function unlike(idx) { let image = imagesList[idx] vote(image.id, 2) image.liked = false image.dom.classList.toggle('liked', image.liked) }