ImageBoard Viewer/Downloader

A simple quick and dirty image viewer for gelbooru.com and rule34.xxx supports all formats from gif to webm.

Ajankohdalta 15.5.2017. Katso uusin versio.

// ==UserScript==
// @name         ImageBoard Viewer/Downloader
// @version      1.3
// @description  A simple quick and dirty image viewer for gelbooru.com and rule34.xxx supports all formats from gif to webm.
// @author       PineappleLover69
// @include      https://gelbooru.com*
// @include      https://rule34.xxx*
// @include      http://rule34.xxx*

// @namespace https://greasyfork.org/users/120106
// ==/UserScript==


(function () {

    //Settings
    var StartImageHeight = 650;
    var AutoShowImageView = false;
    var DisableImageLinks = true;


    var siteInfo = {
        hostName: window.location.hostname,
        siteIndex_: "",
        get siteIndex() {
            if (!this.siteIndex_) {
                if (this.hostName == "gelbooru.com" || this.hostName == "rule34.xxx") {
                    this.siteIndex_ = "gel+r34";
                } else if (this.hostName == "chan.sankakucomplex.com") {
                    this.siteIndex_ = "sankaku";
                }
            }
            return this.siteIndex_;
        }
    };

    var siteObj, siteSwitchObj;
    SetUpSiteSwitchObjs();

    //this group of vars is to be set by SetVars() and depends on the current website
    var buttonInsertionPoint, posts, imgList, tagEntry, tagTypeLookup, postsJson, tagArray, postSources;


    var tagDictionary = {};

    siteObj.SetVars();
    BatchPostApiCall();

    var imgIndex = 0;
    var imgOpened = false;

    if (DisableImageLinks) {
        for (let i = 0; i < imgList.length;) {
            try {
                imgList[i].setAttribute("openRef", imgList[i].childNodes[0].getAttribute("href"));
                imgList[i].childNodes[0].removeAttribute("href");
                imgList[i].childNodes[0].addEventListener("click", ImgClick);
                i++;
            } catch (ex) {
                imgList[i].remove();
            }
        }
    }


    function ImgClick(e) {
        if (!imgOpened)
            ImgView();

        var parentchildObj = {};
        siteObj.ImgClickGetChildAndParent(parentchildObj, e);

        // The equivalent of parent.children.indexOf(child)
        imgIndex = Array.prototype.indexOf.call(parentchildObj.parent.children, parentchildObj.child);
        SetImg();
        imgViewBtn.scrollIntoView();
    }

    var imgViewBtn = document.createElement("button");
    imgViewBtn.innerHTML = "Image View";
    imgViewBtn.onclick = ImgView;
    var dlAllBtn = document.createElement("button");
    dlAllBtn.innerHTML = "Download All";
    dlAllBtn.onclick = dlAll;

    //imgViewBtn.setAttribute("class", "active");
    buttonInsertionPoint.insertBefore(dlAllBtn, buttonInsertionPoint.childNodes[0]);
    buttonInsertionPoint.insertBefore(imgViewBtn, buttonInsertionPoint.childNodes[0]);

    var imgViewImg, videoImg, preloadImg1, preloadImg2, preloadImg3, preloadImg4;

    function ImgView() {
        if (imgOpened)
            return;

        var holdDiv = document.createElement("div");
        holdDiv.setAttribute("align", "center");
        buttonInsertionPoint.insertBefore(holdDiv, buttonInsertionPoint.childNodes[2]);

        imgViewImg = document.createElement("img");
        imgViewImg.setAttribute("height", StartImageHeight);
        holdDiv.appendChild(imgViewImg);
        videoImg = document.createElement("video");
        videoImg.setAttribute("height", StartImageHeight);
        videoImg.setAttribute("autoplay", true);
        videoImg.setAttribute("controls", true);
        videoImg.setAttribute("loop", true);
        videoImg.setAttribute("hidden", true);
        holdDiv.appendChild(videoImg);

        preloadImg1 = document.createElement("img");
        preloadImg2 = document.createElement("img");
        preloadImg1.setAttribute("hidden", true);
        preloadImg2.setAttribute("hidden", true);
        holdDiv.appendChild(preloadImg1);
        holdDiv.appendChild(preloadImg2);

        preloadImg3 = document.createElement("img");
        preloadImg4 = document.createElement("img");
        preloadImg3.setAttribute("hidden", true);
        preloadImg4.setAttribute("hidden", true);
        holdDiv.appendChild(preloadImg3);
        holdDiv.appendChild(preloadImg4);

        imgViewImg.addEventListener('load', DoPreload);

        imgViewImg.addEventListener('mousedown', ImageMouseDown);
        imgViewImg.addEventListener('mouseup', ImageMouseUp);
        imgViewImg.addEventListener('mousemove', ImageMouseMove);
        imgViewImg.addEventListener('mouseleave', ImageMouseLeave);

        videoImg.addEventListener('mousedown', ImageMouseDown);
        videoImg.addEventListener('mouseup', ImageMouseUp);
        videoImg.addEventListener('mousemove', ImageMouseMove);
        videoImg.addEventListener('mouseleave', ImageMouseLeave);

        prevBtn = document.createElement("button");
        prevBtn.innerHTML = "Prev";
        prevBtn.onclick = PrevImg;
        nextBtn = document.createElement("button");
        nextBtn.innerHTML = "Next";
        nextBtn.onclick = NextImg;
        dlBtn = document.createElement("button");
        dlBtn.innerHTML = "Download";
        dlBtn.onclick = DownloadCurrent;
        opBtn = document.createElement("button");
        opBtn.innerHTML = "Open Src";
        opBtn.onclick = OpenSrc;
        spacer = document.createElement("img");
        spacer.setAttribute("width", 30);
        spacer2 = document.createElement("img");
        spacer2.setAttribute("width", 30);
        spacer3 = document.createElement("img");
        spacer3.setAttribute("width", 30);
        holdDiv.appendChild(document.createElement("br"));
        holdDiv.appendChild(prevBtn);
        holdDiv.appendChild(spacer);
        holdDiv.appendChild(dlBtn);
        holdDiv.appendChild(spacer2);
        holdDiv.appendChild(opBtn);
        holdDiv.appendChild(spacer3);
        holdDiv.appendChild(nextBtn);

        imgOpened = true;
        let header = document.getElementById("header");
        if (header)
            header.remove();
        header = document.getElementsByClassName("header")[0];
        if (header)
            header.remove();

        document.addEventListener("keydown", keyInput);
        SetImg();
    }

    if (AutoShowImageView)
        ImgView();


    function BatchPostApiCall() {
        var apiCallObj = getJsonFromUrl();
        siteObj.posts.PostApiSelector(apiCallObj);

        var xhttp = new XMLHttpRequest();
        xhttp.onreadystatechange = function () {
            if (this.readyState == 4 && this.status == 200) {
                postsJson = xmlToJson(this.responseXML);

                siteObj.posts.PostSourcesSelector(apiCallObj);

                CreateTagBase();
            }
        };
        xhttp.open("GET", apiCallObj.request, true);
        xhttp.send();
    }

    function CreateTagBase() {
        var uniqueTagList = [];
        siteObj.tags.GetSplitTagsPerPost(uniqueTagList);
        uniqueTagList = mergeDedupe(uniqueTagList);

        siteObj.tags.TagApiSelector(uniqueTagList);
    }

    function TagRequest(tagRequest) {
        var xhttp = new XMLHttpRequest();
        xhttp.onreadystatechange = function () {
            if (this.readyState == 4 && this.status == 200) {
                var tagPageJson = xmlToJson(this.responseXML);

                siteObj.tags.TagDictionarySetup(tagPageJson);

                if (imgOpened)
                    SetNewTags();
            }
        };
        xhttp.open("GET", tagRequest, true);
        xhttp.send();
    }


    function OpenSrc() {
        window.open(imgList[imgIndex].getAttribute("openRef"));
    }


    function SetCurrentSrc() {
        currentSrc = GetSrcForImg(imgIndex);
    }

    function GetSrcForImg(getIndex) {
        if (postSources[getIndex]) {
            return postSources[getIndex];
        } else {
            return siteObj.posts.SinglePostSrc(getIndex);
        }
    }

    function SetNewTags() {
        if (!tagArray)
            return;

        siteObj.tags.RemoveTags();
        siteObj.tags.AddTags();
        siteObj.tags.RemoveEmptyTags();
    }


    function SetImg() {
        SetCurrentSrc();
        var dI = currentSrc.lastIndexOf(".");
        var fileExt = currentSrc.substring(dI + 1);

        if (fileExt.toLowerCase() == "webm") {
            videoImg.setAttribute("src", currentSrc);
            videoImg.removeAttribute("hidden");
            videoImg.play();
            imgViewImg.setAttribute("hidden", true);
            setTimeout(DoPreload, 200);
        } else {
            imgViewImg.setAttribute("src", "");
            imgViewImg.removeAttribute("hidden");
            videoImg.setAttribute("hidden", true);
            videoImg.pause();

            setTimeout(SetImageAfterTimeout, 1);
        }

        SetNewTags();
    }

    function SetImageAfterTimeout() {
        imgViewImg.setAttribute("src", currentSrc);
    }


    function DoPreload() {
        var preIndex = imgIndex + 1;
        if (preIndex >= imgList.length)
            preIndex = 0;
        preloadImg1.src = GetSrcForImg(preIndex);

        preIndex++;
        if (preIndex >= imgList.length)
            preIndex = 0;
        preloadImg2.src = GetSrcForImg(preIndex);

        preIndex = imgIndex - 1;
        if (preIndex < 0)
            preIndex = imgList.length - 1;
        preloadImg3.src = GetSrcForImg(preIndex);

        //preIndex--;
        //if(preIndex < 0)
        //    preIndex = imgList.length - 1;
        //preloadImg4.src = GetSrcForImg(preIndex);
    }

    function DownloadCurrent() {
        SetCurrentSrc();
        var dI = currentSrc.lastIndexOf(".");
        var uI = currentSrc.lastIndexOf("/") + 5;
        var fileExt = currentSrc.substring(dI);
        var imgName = "tags-" + tagEntry.value + " ";
        if (tagEntry.value === "") {
            imgName = currentSrc.substring(uI, dI);
        } else {
            imgName += currentSrc.substring(uI, dI);
        }
        imgName += " id-" + imgList[imgIndex].childNodes[0].getAttribute("id");
        imgName += fileExt;
        //console.log(imgName);
        var dl = document.createElement("a");
        dl.setAttribute("href", currentSrc);
        dl.setAttribute("download", imgName);
        dl.click();
        dl.remove();

        document.body.focus();
    }

    function dlAll() {
        var prevIndex = imgIndex;
        for (imgIndex = 0; imgIndex < imgList.length;) {
            try {
                DownloadCurrent();
                imgIndex++;
            } catch (ex) {
                console.log(ex);
                imgIndex++;
                //imgList[imgIndex].remove();
            }
        }

        imgIndex = prevIndex;
    }


    function keyInput(e) {
        if (document.activeElement != tagEntry) {
            if (e.keyCode === 32) {
                e.preventDefault();
                return false;
            }
            if (e.keyCode === 37) {
                e.preventDefault();
                PrevImg();
                return false;
            }
            if (e.keyCode === 39) {
                e.preventDefault();
                NextImg();
                return false;
            }
            if (e.keyCode === 40) {
                e.preventDefault();
                DownloadCurrent();
                return false;
            }
        }
    }

    function SetUpSiteSwitchObjs() {
        ///this is the object that controls what should be done for each individual website supported
        siteSwitchObj = {
            //the default here serves as a master obj so that code bits can be reused if certain websites
            //use similar layouts in areas. it is based on the gelbooru design.
            default: {
                SetVars: function () {
                    buttonInsertionPoint = document.getElementsByClassName("content")[0];
                    posts = document.getElementById("post-list");
                    imgList = document.getElementsByClassName("thumb");
                    tagEntry = document.getElementById("tags");
                    postSources = Array(imgList.length);

                    tagTypeLookup = {
                        0: "tag-type-general",
                        1: "tag-type-artist",
                        2: "tag-type-copyright",
                        3: "tag-type-copyright",
                        4: "tag-type-character"
                    };
                },
                ImgClickGetChildAndParent: function (obj, e) {
                    obj.child = e.target.parentNode.parentNode;
                    obj.parent = obj.child.parentNode;
                },
                posts: {
                    PostApiSelector: function (apiObj) {
                        var pid = 0;
                        if (apiObj.pid)
                            pid = apiObj.pid / 42;
                        apiObj.postLimit = imgList.length;
                        var tags = encodeURIComponent(tagEntry.value);
                        apiObj.request = "/index.php?page=dapi&s=post&q=index&limit=" + apiObj.postLimit + "&tags=" + tags + "&pid=" + pid;
                    },
                    PostSourcesSelector: function (apiObj) {
                        for (var i = 0; i < apiObj.postLimit; i++) {
                            if (!postsJson.posts.post[i])
                                break;
                            postSources[i] = postsJson.posts.post[i]["@attributes"].file_url;
                        }
                    },
                    SinglePostSrc: function (getIndex) {
                        var tmpSrc = imgList[getIndex].id;
                        tmpSrc = tmpSrc.replace("s", "");

                        var thing = JsonHttpRequest("/index.php?page=dapi&s=post&q=index&id=" + tmpSrc.toString());

                        tmpSrc = thing.posts.post["@attributes"].file_url;
                        postSources[getIndex] = tmpSrc;
                        return tmpSrc;
                    }
                },
                tags: {
                    GetSplitTagsPerPost: function (uniqueTagList) {
                        for (var i = 0; i < imgList.length; i++) {
                            var currentPost = postsJson.posts.post[i];
                            var tags = currentPost["@attributes"].tags.toLowerCase();
                            var splitTags = tags.split(' ');

                            uniqueTagList.push(splitTags);
                        }
                    },
                    TagApiSelector: function (uniqueTagList) {
                        var uniqueTagString = "";
                        var uniqueStringArray = [];
                        var usCount = 0;
                        for (i = 0; i < uniqueTagList.length; i++) {
                            if (usCount === 0) {
                                uniqueTagString += uniqueTagList[i];
                            } else {
                                uniqueTagString += " " + uniqueTagList[i];
                            }
                            usCount++;
                            if (usCount > 99 || i == uniqueTagList.length - 1) {
                                usCount = 0;
                                uniqueStringArray.push(uniqueTagString);
                                uniqueTagString = "";
                            }
                        }

                        for (i = 0; i < uniqueStringArray.length; i++) {
                            var request = "/index.php?page=dapi&s=tag&q=index&names=" + encodeURIComponent(uniqueStringArray[i]);
                            TagRequest(request);
                        }
                    },
                    TagDictionarySetup: function (tagPageJson) {
                        let tmpArray = tagPageJson.tags.tag;
                        if (!tagArray)
                            tagArray = tmpArray;
                        else {
                            tagArray = tagArray.concat(tmpArray);

                        }
                        for (i = 0; i < tmpArray.length; i++) {
                            tagDictionary[tmpArray[i]["@attributes"].name.toLowerCase()] = tmpArray[i]["@attributes"];
                        }
                    },
                    AddTag: function (tagName, tagParent, tagToClone, stringToReplace) {
                        try {
                            var clonedTag = tagToClone.cloneNode(true);
                            tagParent.appendChild(clonedTag);
                            clonedTag.innerHTML = clonedTag.innerHTML.replaceAll(stringToReplace, encodeURIComponent(tagName));
                            clonedTag.childNodes[7].innerHTML = tagName.replace(/_/g, " ");

                            var jsonTag = tagDictionary[tagName];
                            var tagType = jsonTag.type;
                            clonedTag.setAttribute("class", tagTypeLookup[tagType]);
                            clonedTag.childNodes[9].innerHTML = jsonTag.count;
                        } catch (ex) {
                            console.log("Failed tag: " + tagName);
                            console.log(ex);
                            console.log(tagDictionary);
                        }
                    },
                    AddTags: function () {
                        var currentPost = postsJson.posts.post[imgIndex];
                        var tags = currentPost["@attributes"].tags;
                        var splitTags = tags.split(' ');

                        var tagBar = document.getElementById("tag-sidebar");
                        var firstTag = tagBar.childNodes[0];
                        var stringToReplace = firstTag.innerHTML.substring(firstTag.innerHTML.indexOf("search=") + 7, firstTag.innerHTML.indexOf('" title="Wiki"'));

                        for (var i = 1; i < splitTags.length; i++) {
                            this.AddTag(splitTags[i], tagBar, firstTag, stringToReplace)
                        }

                        firstTag.remove();
                    },
                    RemoveTags: function () {
                        var tagBar = document.getElementById("tag-sidebar");
                        for (var i = tagBar.childNodes.length - 1; i >= 1; i--) {
                            tagBar.childNodes[i].remove();
                        }
                    },
                    RemoveEmptyTags: function () {
                        var tagBar = document.getElementById("tag-sidebar");
                        for (var i = tagBar.childNodes.length - 1; i >= 0; i--) {
                            let tAg = tagBar.childNodes[i];
                            if (tAg.childNodes[7].innerHTML === "") {
                                tAg.remove();
                            }
                        }
                    }
                }
            },

            //this setup for gelbooru and r34 uses all the default calls
            "gel+r34": {
                SetVars: function () {
                    siteSwitchObj.default.SetVars();
                },
                ImgClickGetChildAndParent: function (obj, e) {
                    siteSwitchObj.default.ImgClickGetChildAndParent(obj, e);
                },
                posts: {
                    PostApiSelector: function (apiObj) {
                        siteSwitchObj.default.posts.PostApiSelector(apiObj);
                    },
                    PostSourcesSelector: function (apiObj) {
                        siteSwitchObj.default.posts.PostSourcesSelector(apiObj);
                    },
                    SinglePostSrc: function (getIndex) {
                        return siteSwitchObj.default.posts.SinglePostSrc(getIndex);
                    }
                },
                tags: {
                    GetSplitTagsPerPost: function (uniqueTagList) {
                        siteSwitchObj.default.tags.GetSplitTagsPerPost(uniqueTagList);
                    },
                    TagApiSelector: function (uniqueTagList) {
                        siteSwitchObj.default.tags.TagApiSelector(uniqueTagList);
                    },
                    TagDictionarySetup: function (tagPageJson) {
                        siteSwitchObj.default.tags.TagDictionarySetup(tagPageJson);
                    },
                    RemoveTags: function () {
                        siteSwitchObj.default.tags.RemoveTags();
                    },
                    AddTag: function (tagName, tagParent, tagToClone, stringToReplace) {
                        siteSwitchObj.default.tags.AddTag(tagName, tagParent, tagToClone, stringToReplace);
                    },
                    AddTags: function () {
                        siteSwitchObj.default.tags.AddTags();
                    },
                    RemoveEmptyTags: function () {
                        siteSwitchObj.default.tags.RemoveEmptyTags();
                    }
                }
            },
            "sankaku": {}
        };

        siteObj = siteSwitchObj[siteInfo.siteIndex];
    }


    //----------everything below here is either utility or is pretty set in stone-------------------

    Element.prototype.remove = function () {
        if (this)
            this.parentElement.removeChild(this);
    };

    //String.prototype.replaceAll = function (search, replacement) {
    //    var target = this;
    //    return target.replace(new RegExp(search, 'g'), replacement);
    //};

    String.prototype.replaceAll = function (search, replacement) {
        var target = this;
        return target.split(search).join(replacement);
    };

    function mergeDedupe(arr) {
        return [...new Set([].concat(...arr))];
    }

    function PrevImg() {
        imgIndex--;
        if (imgIndex < 0)
            imgIndex = imgList.length - 1;
        SetImg();
    }

    function NextImg() {
        imgIndex++;
        if (imgIndex >= imgList.length)
            imgIndex = 0;
        SetImg();
    }

    var imgMouseDown = false;
    var imgDownPosX, imgDownPosY, imgDownHeight = 0;

    function ImageMouseDown(e) {
        e.preventDefault();
        imgMouseDown = true;
        imgDownPosX = e.screenX;
        imgDownPosY = e.screenY;
        imgDownHeight = Number(imgViewImg.getAttribute("height"));
        return false;
    }

    function ImageMouseUp(e) {
        e.preventDefault();
        imgMouseDown = false;
        return false;
    }

    function ImageMouseMove(e) {
        if (imgMouseDown) {
            e.preventDefault();
            var moveDist = e.screenY - Number(imgDownPosY);
            imgViewImg.setAttribute("height", imgDownHeight + moveDist * 2);
            videoImg.setAttribute("height", imgDownHeight + moveDist * 2);
            return false;
        }
    }

    function ImageMouseLeave(e) {
        e.preventDefault();
        imgMouseDown = false;
        return false;
    }

    function getJsonFromUrl() {
        var query = location.search.substr(1);
        var result = {};
        query.split("&").forEach(function (part) {
            var item = part.split("=");
            result[item[0]] = decodeURIComponent(item[1]);
        });
        return result;
    }

    function JsonHttpRequest(urlRequest) {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", urlRequest, false);
        xhr.send();
        return xmlToJson(xhr.responseXML);
    }

    // Changes XML to JSON
    function xmlToJson(xml) {

        // Create the return object
        var obj = {};

        if (xml.nodeType == 1) { // element
            // do attributes
            if (xml.attributes.length > 0) {
                obj["@attributes"] = {};
                for (var j = 0; j < xml.attributes.length; j++) {
                    var attribute = xml.attributes.item(j);
                    obj["@attributes"][attribute.nodeName] = attribute.nodeValue;
                }
            }
        } else if (xml.nodeType == 3) { // text
            obj = xml.nodeValue;
        }

        // do children
        if (xml.hasChildNodes()) {
            for (var i = 0; i < xml.childNodes.length; i++) {
                var item = xml.childNodes.item(i);
                var nodeName = item.nodeName;
                if (typeof(obj[nodeName]) == "undefined") {
                    obj[nodeName] = xmlToJson(item);
                } else {
                    if (typeof(obj[nodeName].push) == "undefined") {
                        var old = obj[nodeName];
                        obj[nodeName] = [];
                        obj[nodeName].push(old);
                    }
                    obj[nodeName].push(xmlToJson(item));
                }
            }
        }
        return obj;
    }


})
();