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

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

Um dieses Skript zu installieren, müssen Sie eine Erweiterung wie Tampermonkey oder Violentmonkey installieren.

Um dieses Skript zu installieren, müssen Sie eine Erweiterung wie Tampermonkey oder Violentmonkey installieren.

Um dieses Skript zu installieren, müssen Sie eine Erweiterung wie Tampermonkey oder Userscripts installieren.

Um dieses Skript zu installieren, müssen Sie eine Erweiterung wie Tampermonkey installieren.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

Um dieses Design zu installieren, müssen Sie eine Erweiterung wie Stylus installieren.

Um dieses Design zu installieren, müssen Sie eine Erweiterung wie Stylus installieren.

Um dieses Design zu installieren, müssen Sie eine Erweiterung wie Stylus installieren.

Um diesen Stil zu installieren, müssen Sie eine Erweiterung für den Benutzerstil Verwaltung installieren.

Um diesen Stil zu installieren, müssen Sie eine Erweiterung für den Benutzerstil Verwaltung installieren.

Um diesen Stil zu installieren, müssen Sie eine Erweiterung für den Benutzerstil Verwaltung installieren.

(Ich habe bereits einen Benutzerstil Verwaltung, ich möchte ihn installieren!)

// ==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();

})();