A Gelbooru Viewer

Adds a download button and makes thumbnails a little bigger. Make shure to add (.png, .jpeg, .jpg, etc) to the tampermonkey download whitelist

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         A Gelbooru Viewer
// @namespace    http://tampermonkey.net/
// @version      1.0.1
// @description  Adds a download button and makes thumbnails a little bigger. Make shure to add (.png, .jpeg, .jpg, etc) to the tampermonkey download whitelist
// @author       Anonymous
// @match        https://gelbooru.com/*page=post*s=list*
// @grant        GM_download
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function() {
    'use strict';
    // Your code here...

    function setCustomStyles(){
        const customStyles = document.createElement("style");
        customStyles.innerHTML = "" +
            ".thumbnail-preview {" +
            "max-width: unset;"+
            "width: 30%;" +
            "min-height: 200px;" +
            "max-height: unset;" +
            "height: 500px;"+
            "}" +
            ".thumbnail-preview span {display: contents;}"+
            ".thumbnail-preview a {display: contents;}"+
            ".thumbnail-preview button {width: 100%; height: 25px; background-color: blue; color: white; text-decoration:none;border:none;}"+
            ".thumbnail-preview button[disabled] {background-color: gray; color: black;}"+
            ".thumbnail-preview button[data-done] {background-color: green;}"+
            ".thumbnail-preview button.secondary {background-color: gray;}"+
            ".thumbnail-preview img {"+
            "object-fit: contain;" +
            "width: 100%;" +
            "height: calc(100% - 50px);" +
            "} "+
            ".next-page-message-container {display:flex;align-items:center;width:100%;height:100px;margin:0;background-color:darkgray;} "+
            ".next-page-message-container p {width:fit-content;margin:auto;font-size:3em;}";

        document.head.appendChild(customStyles);
    }

    function parsePaginationInfo(doc){
        let currentPage,
            nextPage, nextPageUrl,
            previousPage,previousPageUrl;

        const currentPageEl = doc.querySelector(".pagination b");
        if (currentPageEl){
            currentPage = currentPageEl.innerText;
        } else {
            console.debug("cant parse current page");
        }

        const aList = Array.from(doc.querySelectorAll(".pagination a"));

        for (let i=0; i < aList.length; i++){
            const a = aList[i];
            const altText = a.getAttribute("alt");
            if (!altText) { continue }

            switch(altText){
                case "next": {
                    nextPage = altText;
                    nextPageUrl = a.href;
                    break;
                }
                case "back": {
                    previousPage = altText;
                    previousPageUrl = a.href;
                    break;
                }
            }
        }

        const alertIfNotFound = (name, value) => {
            if (!value) { console.debug( name + " was not found"); }
        }
        alertIfNotFound("nextPage", nextPage);
        alertIfNotFound("previousPage", previousPage);

        return {currentPage,
                nextPage, nextPageUrl,
                previousPage,previousPageUrl}
    }

    function parseThumbPreview(el){
        const a = el.querySelector("a");
        const img = el.querySelector("img");

        if (!a || !img){
            throw new Error("parse error " + el.outerHTML);
        }

        const thumbUrl = img.src;
        const pageUrl = a.href;

        if (!thumbUrl || !pageUrl) {
            throw new Error("parse error " + el.outerHTML);
        }

        return {thumbUrl, pageUrl}
    }

    function parseDetailPage(doc){
        let originalImageUrl = undefined;
        doc.querySelectorAll("#tag-list a").forEach(a=>{
            if (a.innerText === "Original image"){
                originalImageUrl = a.href;
            }
        });

        if (!originalImageUrl) {
            throw new Error("parse error");
        }

        return {originalImageUrl}
    }

    async function getDetailPageDoc(url){
        const response = await fetch(url, {method:"GET"});
        if (!response.ok){ throw new Error("request error", response) }
        const text = await response.text();
        return new DOMParser().parseFromString(text,"text/html");
    }

    async function downloadImage(url, saveAs=false){
        const name = url.substring(url.lastIndexOf("/")+1);

        return new Promise((resolve, reject)=>{
            GM_download({
                url,
                name,
                saveAs,
                onload: (data)=>{
                    console.debug("GM_download.onload", data);
                    resolve();
                },
                onerror: (e)=>{
                    console.debug("GM_download.onerror", e);
                    reject(e);
                }
            });
        });
    }

    async function downloadOriginalFromDetailPage(url){
        const doc = await getDetailPageDoc(url);
        console.debug("page fetched");
        const data = parseDetailPage(doc);
        console.debug("page parsed", data);

        await downloadImage(data.originalImageUrl);
        console.debug("image downloaded");
        return;

    }

    // https://stackoverflow.com/questions/1038727/how-to-get-browser-width-using-javascript-code
    function getWidth() {
        return Math.max(
            document.body.scrollWidth,
            document.documentElement.scrollWidth,
            document.body.offsetWidth,
            document.documentElement.offsetWidth,
            document.documentElement.clientWidth
        );
    }

    function getHeight() {
        return Math.max(
            document.body.scrollHeight,
            document.documentElement.scrollHeight,
            document.body.offsetHeight,
            document.documentElement.offsetHeight,
            document.documentElement.clientHeight
        );
    }

    function openChildWindow(url,name="page",w=null,h=null) {
        // http://www.nigraphic.com/blog/java-script/how-open-new-window-popup-center-screen

        if (!w) { w = getWidth() * 0.8; }
        if (!h) { h = getHeight() * 0.8; }

        const dualScreenLeft = window.screenLeft != undefined ? window.screenLeft : screen.left;
        const dualScreenTop = window.screenTop != undefined ? window.screenTop : screen.top;

        const width = window.innerWidth ? window.innerWidth : document.documentElement.clientWidth ? document.documentElement.clientWidth : screen.width;
        const height = window.innerHeight ? window.innerHeight : document.documentElement.clientHeight ? document.documentElement.clientHeight : screen.height;

        const left = ((width / 2) - (w / 2)) + dualScreenLeft;
        const top = ((height / 2) - (h / 2)) + dualScreenTop;
        const windowref = window.open(url, name, `width=${w},height=${h},top=${top},left=${left},resizable,location=no,toolbar=no,menubar=no,status=no,titlebar=0`);
    }


    function setOnScrollBottomHandler(handler){
        window.onscroll = function(e) {
            let pageHeight=document.documentElement.offsetHeight,
                windowHeight=window.innerHeight,
                scrollPosition=window.scrollY || window.pageYOffset || document.body.scrollTop + (document.documentElement && document.documentElement.scrollTop || 0);

            if (pageHeight*0.98 <= (windowHeight+scrollPosition)) {
                console.debug('bottom reached');
                handler(e);
            }
        };
    }

    function addButtons(){
        document.querySelectorAll(".thumbnail-preview").forEach(el=>{
            const item = parseThumbPreview(el);

            const button = document.createElement("button");
            button.innerText = "download";
            button.addEventListener("click", async e=> {
                try{
                    button.setAttribute("disabled","disabled");
                    button.innerText = "downloading..."
                    await downloadOriginalFromDetailPage(item.pageUrl);
                    button.removeAttribute("disabled");
                    button.dataset.done=true;
                    button.innerText = "done";
                }
                catch(e) {
                    button.innerText = "error";
                    console.error(e);
                }

            });

            const openPageButton = document.createElement("button");
            openPageButton.classList.add("secondary");
            openPageButton.innerText = "open details";
            openPageButton.onclick = (e) => {
                e.preventDefault();
                openChildWindow(item.pageUrl, "Detail");
            }

            el.appendChild(button);
            el.appendChild(openPageButton);
        });
    }

    function addLoadOnScrollBottom(){
        const pagination = parsePaginationInfo(document);

        if (pagination.nextPage) {
            const nextPageMessageContainer = document.createElement("footer");
            const nextPageMessageEl = document.createElement("p");
            nextPageMessageEl.innerText = "loading next page";
            nextPageMessageContainer.classList.add("next-page-message-container");
            nextPageMessageContainer.appendChild(nextPageMessageEl);
            document.body.appendChild(nextPageMessageContainer);

            // Hide lasts br to show next page message sooner
            const containerPushBr = Array.from(document.querySelectorAll(".contain-push br"));
            let count = 0, limit=6;
            for (let i=containerPushBr.length - 1;i >= 0 && count < limit ;i--){
                containerPushBr[i].style.display = "none";
                count++;
            }

            setOnScrollBottomHandler(e=>{window.location.replace(pagination.nextPageUrl)})
        }
    }

    function init(){
        setCustomStyles();
        addButtons();
        addLoadOnScrollBottom();
    }

    init();

})();