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

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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();

})();