ImageBoard Viewer/Downloader

A simple quick and dirty image viewer various imageboard sites

// ==UserScript==
// @name         ImageBoard Viewer/Downloader
// @version      1.46
// @description  A simple quick and dirty image viewer various imageboard sites
// @author       PineappleLover69
// @include      https://gelbooru.com*
// @include      https://rule34.xxx*
// @include      https://danbooru.donmai*
// @include      https://chan.sankakucomplex.com*
// @include      https://idol.sankakucomplex.com*
// @include      https://rule34hentai.net*

// @connect      sankakucomplex.com
// @connect      gelbooru.com
// @grant        GM_xmlhttpRequest
// @grant        GM_download

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


(function () {

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

    var urlChecks = {};
    var GetSrcForImg;
    var siteObj, defaultSiteObject;
    siteObj = siteObj;
    SetUpSiteSwitchObjs();

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


    var tagDictionary = {};

    siteObj.SetVars();
    siteObj.Startup();

    var imgIndex = 0;
    var imgOpened = false;

    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);

        siteObj.posts.OnImgView();

        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 TagRequest(tagRequest) {
        let xhttp = new XMLHttpRequest();
        xhttp.onreadystatechange = function () {
            if (this.readyState == 4 && this.status == 200) {
                let 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 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, parentchildObj.child);
        SetImg();
        imgViewBtn.scrollIntoView();
    }

    function SetCurrentSrc() {
        currentSrc = GetSrcForImg(imgIndex);
        //console.log(currentSrc);
    }


    if (GetSrcForImg == undefined) {
        GetSrcForImg = function (getIndex) {
            if (postSources[getIndex]) {
                return postSources[getIndex];
            } else {
                var postReq = siteObj.posts.SinglePostSrc(getIndex);
                if (postReq == undefined)
                    return "";
                return postReq;
            }
        };
    }

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

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


    function SetImg() {
        SetCurrentSrc();
        if (currentSrc == "" || currentSrc == undefined) {
            imgViewImg.setAttribute("src", "");
            videoImg.setAttribute("src", "");
            return;
        }


        let dI = currentSrc.lastIndexOf(".");
        let fileExt = currentSrc.substring(dI + 1).split("?")[0];

        if (fileExt.toLowerCase() == "webm" || fileExt.toLowerCase() == "mp4") {
            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 = 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;

        //GM_download({url: currentSrc, name: imgName, saveAs: false});
        //GM_download(currentSrc, imgName);
        //console.log(imgName);
        //var dl = document.createElement("a");
        //dl.setAttribute("href", currentSrc);
        //dl.setAttribute("download", imgName);
        //dl.style.display = 'none';
        //document.body.appendChild(dl);
        //dl.click();
        //dl.remove();
        //document.body.focus();

        DownloadFile(currentSrc, imgName);
    }
    function DownloadFile(source, filename){
        GM_xmlhttpRequest({
			url: source,
			method: 'GET',
			context: {
				'url': source,
                'imgname': filename,
			},
			responseType: 'blob',
			onload: blibBlobDownloader,
		});
    }

    function blibBlobDownloader( xhr )
	{
		if( xhr.status !== 200 )
		{
			console.error("xhr.status: ", xhr.status, xhr.statusText);
			console.error("url: " + xhr.context.url);
			return;
		}
		var wndURL = window.webkitURL || window.URL,
			resource = wndURL.createObjectURL(xhr.response);
		fileDownloader(xhr.context.imgname, resource);
		wndURL.revokeObjectURL( resource );
	}

    function fileDownloader( name, resource )
	{
		var a = document.createElement('a'),
			body = document.body || document.getElementsByTagName('body')[0];
		a.setAttribute('download', name);
		a.href = resource;
		body.appendChild(a);
		a.click();
		body.removeChild(a);
	}

    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
        defaultSiteObject = {
            //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.

            SetVars: function () {
                buttonInsertionPoint = document.getElementsByClassName("contain-push")[0];
                imgList = document.getElementsByClassName("thumb");
                tagEntry = document.getElementById("tags-search");
                postSources = Array(imgList.length);
                siteObj.posts.RemoveTextFillerElements();


                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 = imgList;
            },
            GetObjectProperty: function (obj, propName) {
                return obj["@attributes"][propName];
            },
            Startup: function () {
                siteObj.posts.DisableImageLinks();
                siteObj.posts.BatchPostApiCall();
            },
            posts: {
                postIdReplaceChar: "s",
                postLimit: 42,
                postPageIdName: "pid",
                postLimitName: "limit",
                postTagsName: "tags",
                postIdName: "id",
                postFileUrlName: "file_url",
                postApiEndpoint: "/index.php?page=dapi&s=post&q=index",
                addSiteNameToFileUrl: false,
                OnImgView: function () {

                },
                DisableImageLinks: function () {
                    for (let i = 0; i < imgList.length;) {
                        try {
                            if (!imgList[i].getAttribute("openRef")) {
                                let tmpAnchor = imgList[i].childNodes[0];
                                imgList[i].setAttribute("openRef", tmpAnchor.getAttribute("href"));
                                if (DisableImageLinks) {
                                    tmpAnchor.onclick = null;
                                    tmpAnchor.removeAttribute("onclick");
                                    tmpAnchor.removeAttribute("href");
                                    tmpAnchor.addEventListener("click", ImgClick);
                                }
                            }
                            i++;
                        } catch (ex) {
                            imgList[i].remove();
                        }
                    }
                },
                RemoveTextFillerElements: function () {
                    for (let i = 0; i < imgList.length;) {
                        if (imgList[i].tagName === undefined) {
                            imgList[i].remove();
                        } else {
                            i++;
                        }
                    }
                },
                BatchPostApiCall: function () {
                    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);

                            siteObj.tags.CreateTagBase();
                        }
                    };
                    xhttp.open("GET", apiCallObj.request, true);
                    xhttp.send();
                },
                HandlePageId: function (value) {
                    return value / this.postLimit;
                },
                PostApiSelector: function (apiObj) {
                    let pid = 0;
                    apiObj.postLimit = this.postLimit;
                    if (apiObj[this.postPageIdName]) {
                        pid = this.HandlePageId(apiObj[this.postPageIdName]);
                    }
                    let tags = encodeUriSpecial(tagEntry.value);
                    apiObj.request = this.postApiEndpoint + "&" + this.postLimitName + "=" + apiObj.postLimit
                    + "&" + this.postTagsName + "=" + tags + "&" + this.postPageIdName + "=" + pid;
                },
                PostsJsonGetPost: function (index) {
                    let tmpPost = postsJson.posts.post[index];
                    return tmpPost;
                },
                PostMismatch: function (apiObj, index) {
                    imgList[index].remove();
                    imgList[imgList.length - 1].remove();
                    apiObj.postLimit -= 2;
                    console.log("removed 1: " + apiObj.postLimit + " : " + imgList.length);
                },
                PostSourcesSelector: function (apiObj) {
                    apiObj.postOffset = 0;
                    for (let i = 0; i < apiObj.postLimit;) {
                        let tmpPost = siteObj.posts.PostsJsonGetPost(i + apiObj.postOffset);
                        if (!tmpPost)
                            break;
                        let tmpId = imgList[i].id;
                        tmpId = tmpId.replace(this.postIdReplaceChar, "");

                        if (siteObj.GetObjectProperty(tmpPost, "id") != tmpId) {
                            siteObj.posts.PostMismatch(apiObj, i);
                        } else {
                            postSources[i] = siteObj.GetObjectProperty(tmpPost, this.postFileUrlName);
                            if (siteObj.posts.addSiteNameToFileUrl)
                                postSources[i] = window.location.hostname + postSources[i];
                            i++;
                        }
                    }
                },
                GetSinglePostApiRequest: function (tmpId) {
                    let request = JsonHttpRequest(this.postApiEndpoint + "&" + this.postIdName + "=" + tmpId.toString());
                    return siteObj.GetObjectProperty(request, this.postFileUrlName);
                },
                SinglePostSrc: function (getIndex) {
                    var tmpId = imgList[getIndex][this.postIdName];
                    tmpId = tmpId.replace(this.postIdReplaceChar, "");
                    var tmpSrc = siteObj.posts.GetSinglePostApiRequest(tmpId);

                    postSources[getIndex] = tmpSrc;
                    return tmpSrc;
                }
            },
            tags: {
                tagApiEndpoint: "/index.php?page=dapi&s=tag&q=index&names=",
                maxTagApiCount: 99,
                logTagErrors: true,
                tagApiSplitChar: " ",
                tagsSplitChar: " ",
                tagsPropertyName: "tags",
                tagCategoryPropertyName: "type",
                tagCountPropertyName: "count",
                tagNamePropertyName: "name",
                GetTagSidebarElement: function () {
                    return document.getElementById("searchTags");
                },
                CreateTagBase: function () {
                    let uniqueTagList = [];
                    siteObj.tags.GetSplitTagsPerPost(uniqueTagList);
                    uniqueTagList = mergeDedupe(uniqueTagList);

                    siteObj.tags.TagApiSelector(uniqueTagList);
                },
                GetSplitTagsPerPost: function (uniqueTagList) {
                    for (var i = 0; i < imgList.length; i++) {
                        var currentPost = siteObj.posts.PostsJsonGetPost(i);
                        var tags = siteObj.GetObjectProperty(currentPost, this.tagsPropertyName).toLowerCase();
                        var splitTags = tags.split(this.tagsSplitChar);

                        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 += this.tagApiSplitChar + uniqueTagList[i];
                        }
                        usCount++;
                        if (usCount > this.maxTagApiCount || i == uniqueTagList.length - 1) {
                            usCount = 0;
                            uniqueStringArray.push(uniqueTagString);
                            uniqueTagString = "";
                        }
                    }

                    for (i = 0; i < uniqueStringArray.length; i++) {
                        let request = this.tagApiEndpoint + encodeUriSpecial(uniqueStringArray[i]);
                        TagRequest(request);
                    }
                },
                GetTagJsonArray: function (tagJson) {
                    return tagJson.tags.tag;
                },
                TagDictionarySetup: function (tagsJson) {
                    let tmpArray = siteObj.tags.GetTagJsonArray(tagsJson);
                    if (!tagArray)
                        tagArray = tmpArray;
                    else {
                        tagArray = tagArray.concat(tmpArray);
                    }

                    for (i = 0; i < tmpArray.length; i++) {
                        tagDictionary[siteObj.GetObjectProperty(tmpArray[i], this.tagNamePropertyName).toLowerCase()] = tmpArray[i];
                    }
                },
                GetNodeIndex: function () {
                    return (window.location.hostname == "rule34.xxx") ? 4 : 7;
                },
                TagCountFormatter: function (count) {
                    var nCount = Number(count);
                    return nCount;
                },
                TagCloneNameSetter: function (tagClone, tagName) {
                    let nodeIndex = siteObj.tags.GetNodeIndex();
                    tagClone.childNodes[nodeIndex].innerHTML = tagName.replace(/_/g, " ");
                },
                TagCloneCountSetter: function (tagClone, tagCount) {
                    let nodeIndex = siteObj.tags.GetNodeIndex();
                    tagClone.childNodes[nodeIndex + 2].innerHTML = siteObj.tags.TagCountFormatter(tagCount);
                },
                AddTag: function (tagName, tagParent, tagToClone, stringToReplace) {
                    try {
                        var clonedTag = tagToClone.cloneNode(true);
                        tagParent.appendChild(clonedTag);
                        clonedTag.innerHTML = clonedTag.innerHTML.replaceAll("class=", "cla$$=");
                        clonedTag.innerHTML = clonedTag.innerHTML.replaceAll(stringToReplace, encodeUriSpecial(tagName));
                        clonedTag.innerHTML = clonedTag.innerHTML.replaceAll("cla$$=", "class=");

                        siteObj.tags.TagCloneNameSetter(clonedTag, tagName);

                        var jsonTag = tagDictionary[tagName];
                        var tagType = siteObj.GetObjectProperty(jsonTag, this.tagCategoryPropertyName);
                        var tagCount = siteObj.GetObjectProperty(jsonTag, this.tagCountPropertyName);

                        clonedTag.setAttribute("class", tagTypeLookup[tagType]);
                        siteObj.tags.TagCloneCountSetter(clonedTag, tagCount);
                    } catch (ex) {
                        if (this.logTagErrors) {
                            console.log("Failed tag: " + tagName);
                            console.log(ex);
                            console.log(tagDictionary);
                        }
                    }
                },
                FindStringToReplace: function (tag) {
                    let nodeIndex = siteObj.tags.GetNodeIndex();
                    return encodeUriSpecial(tag.childNodes[nodeIndex].innerHTML);
                },
                AddTags: function () {
                    let currentPost = siteObj.posts.PostsJsonGetPost(imgIndex);
                    let tags = siteObj.GetObjectProperty(currentPost, this.tagsPropertyName);
                    let splitTags = tags.split(this.tagsSplitChar);

                    let tagBar = siteObj.tags.GetTagSidebarElement();

                    let firstTag = tagBar.childNodes[0];

                    //let stringToReplace = firstTag.innerHTML.substring(firstTag.innerHTML.lastIndexOf("tags=") + 5, firstTag.innerHTML.lastIndexOf('</a>') - 3);
                    let stringToReplace = siteObj.tags.FindStringToReplace(firstTag).replace("%20", "_");

                    for (let i = 0; i < splitTags.length; i++) {
                        siteObj.tags.AddTag(splitTags[i], tagBar, firstTag, stringToReplace);
                    }

                    firstTag.remove();
                },
                RemoveTags: function () {
                    let tagBar = siteObj.tags.GetTagSidebarElement();
                    if (tagBar.childNodes[0].innerHTML === undefined) {
                        tagBar.childNodes[0].remove();
                    }
                    for (let i = tagBar.childNodes.length - 1; i >= 1; i--) {
                        tagBar.childNodes[i].remove();
                    }
                },
                RemoveEmptyTags: function () {
                    let tagBar = siteObj.tags.GetTagSidebarElement();
                    for (let i = tagBar.childNodes.length - 1; i >= 0; i--) {
                        let tAg = tagBar.childNodes[i];
                        try {
                            if (tAg.childNodes[4].innerHTML === "" || tAg.childNodes[7].innerHTML === "") {
                                tAg.remove();
                            }
                        } catch (ex) {

                        }
                    }
                }
            }
        };

        siteObj = cloneObject(defaultSiteObject);

        let hostName = window.location.hostname;
        //console.log(hostName + " : " + ());
        if (hostName.startsWith("danbooru.donmai"))
            hostName = "danbooru.donmai";
        if (hostName == "idol.sankakucomplex.com")
            hostName = "chan.sankakucomplex.com";

        //console.log(hostName);

        switch (hostName) {

            case "gelbooru.com":
                let tagBar = siteObj.tags.GetTagSidebarElement();
                let tagParent = tagBar.parentNode;

                tagParent.insertBefore(tagBar.childNodes[0], tagBar);
                tagParent.insertBefore(tagBar.childNodes[0], tagBar);
                tagParent.insertBefore(tagBar.childNodes[0], tagBar);
                tagParent.insertBefore(tagBar.childNodes[0], tagBar);
                tagParent.insertBefore(tagBar.childNodes[0], tagBar);

                break;

            case "rule34.xxx":
                siteObj.tags.logTagErrors = false;

                siteObj.tags.GetTagSidebarElement = function () {
                    return document.getElementById("tag-sidebar");
                };

                siteObj.SetVars = function () {
                    buttonInsertionPoint = document.getElementsByClassName("content")[0];
                    imgList = document.getElementsByClassName("thumb");
                    tagEntry = document.getElementById("tags");
                    postSources = Array(imgList.length);
                    siteObj.posts.RemoveTextFillerElements();

                    tagTypeLookup = {
                        0: "tag-type-general",
                        1: "tag-type-artist",
                        2: "tag-type-copyright",
                        3: "tag-type-copyright",
                        4: "tag-type-character"
                    };
                };

                break;


            case "danbooru.donmai":
                siteObj.SetVars = function () {
                    buttonInsertionPoint = document.getElementById("post-sections");
                    var postList = document.getElementById("posts");
                    imgList = postList.childNodes[1].childNodes;
                    tagEntry = document.getElementById("tags");
                    postSources = Array(imgList.length);
                    siteObj.posts.RemoveTextFillerElements();

                    tagTypeLookup = {
                        0: "category-0",
                        1: "category-1",
                        2: "category-2",
                        3: "category-3",
                        4: "category-4"
                    };
                };

                //danbooru post stuff
                siteObj.posts.postPageIdName = "page";
                siteObj.posts.postIdReplaceChar = "post_";
                siteObj.posts.postFileUrlName = "large-file-url";
                siteObj.posts.postApiEndpoint = "/posts.xml?";
                siteObj.posts.postLimit = 20;
                siteObj.posts.HandlePageId = function (value) {
                    return Number(value);
                };
                siteObj.GetObjectProperty = function (obj, propName) {
                    return obj[propName]["#text"];
                };
                siteObj.posts.GetSinglePostApiRequest = function (tmpId) {
                    let request = JsonHttpRequest("/posts/" + tmpId.toString() + ".xml?");
                    return siteObj.GetObjectProperty(request, siteObj.posts.postFileUrlName);
                };
                siteObj.posts.PostMismatch = function (apiObj, index) {
                    console.log(postsJson);
                    //imgList[index].remove();
                    apiObj.postLimit--;
                    apiObj.postOffset++;
                    console.log("removed 1: " + apiObj.postLimit + " : " + imgList.length);
                };

                //danbooru tag stuff
                siteObj.tags.tagApiEndpoint = "/tags.xml?search[name]=";
                siteObj.tags.tagApiSplitChar = ",";
                siteObj.tags.tagsPropertyName = "tag-string";
                siteObj.tags.tagCategoryPropertyName = "category";
                siteObj.tags.tagCountPropertyName = "post-count";
                siteObj.tags.maxTagApiCount = 19;
                siteObj.tags.GetTagSidebarElement = function () {
                    return document.getElementById("tag-box").childNodes[3];
                };
                siteObj.tags.FindStringToReplace = function (tag) {
                    let tmpStr = tag.childNodes[2].innerHTML.replace(/ /g, "_");
                    return encodeUriSpecial(tmpStr);
                };
                siteObj.tags.TagCloneNameSetter = function (tagClone, tagName) {
                    tagClone.childNodes[2].innerHTML = tagName.replace(/_/g, " ");
                };
                siteObj.tags.TagCountFormatter = function (count) {
                    var nCount = Number(count);
                    if (nCount < 1000) {
                        return nCount.toString();
                    } else if (nCount < 10000) {
                        return (nCount / 1000).toPrecision(2).toString() + "k";
                    } else {
                        nCount /= 1000;
                        return Math.round(nCount).toString() + "k";
                    }
                };
                siteObj.tags.TagCloneCountSetter = function (tagClone, tagCount) {
                    tagClone.childNodes[4].innerHTML = siteObj.tags.TagCountFormatter(tagCount);
                };

                break;

            case "rule34hentai.net":
                document.addEventListener("mousemove", function () {
                    console.log("horg");
                });
                siteObj.SetVars = function () {
                    buttonInsertionPoint = document.getElementById("imagelist").parentNode;
                    imgList = document.getElementsByClassName("thumb");
                    tagEntry = $('#Navigationleft > div > form > ul > li > input')[0];
                    postSources = Array(imgList.length);
                    siteObj.posts.RemoveTextFillerElements();
                    //buttonInsertionPoint = document.body;
                };

                GetSrcForImg = function (getIndex) {
                    let tmpImg = imgList[getIndex];
                    let srcIndex = tmpImg.childNodes[0].src;

                    let postId = tmpImg.getAttribute('data-post-id');
                    let postTags = tmpImg.getAttribute('data-post-id');
                    let postType = tmpImg.childNodes[0].getAttribute('title');
                    postType = postType.substring(postType.lastIndexOf("// ") + 3);
                    let imgSrc = srcIndex.replace("_thumbs", "_images").replace("thumb.jpg", postId + ' - ' + postTags + '.' + postType);

                    console.log(imgSrc);
                    return imgSrc;
                };

                let tImgClick = function(e) {
                    if (!imgOpened)
                        ImgView();

                    var parentchildObj = {};
                    parentchildObj.child = e.target.parentNode;
                    parentchildObj.parent = imgList;

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

                siteObj.posts.DisableImageLinks = function () {
                    for (let i = 0; i < imgList.length;) {
                        try {
                            if (!imgList[i].getAttribute("openRef")) {
                                let tmpAnchor = imgList[i];
                                imgList[i].setAttribute("openRef", tmpAnchor.getAttribute("href"));
                                if (DisableImageLinks) {
                                    tmpAnchor.onclick = null;
                                    tmpAnchor.removeAttribute("onclick");
                                    tmpAnchor.removeAttribute("href");
                                    tmpAnchor.addEventListener("click", tImgClick);
                                }
                            }
                            i++;
                        } catch (ex) {
                            imgList[i].remove();
                        }
                    }
                }
                //siteObj.posts.BatchPostApiCall = function () {
                //    siteObj.posts.DisableImageLinks();
                //    //let tCount = 0;
                //    //for (let i = 0; i < imgList.length; i++) {
                //    //    let tSrc = siteObj.posts.SinglePostSrc(i);
                //    //    if(tSrc !== undefined){
                //    //        tCount++;
                //    //    }
                //    //}
                //    //console.log(tCount + "/" + imgList.length);
                //};
                break;

            case "chan.sankakucomplex.com":
                //sankaku posts stuff

                document.addEventListener("mousemove", function () {
                    siteObj.posts.BatchPostApiCall();
                });

                siteObj.SetVars = function () {
                    buttonInsertionPoint = document.getElementsByClassName("content")[0];
                    imgList = document.getElementsByClassName("thumb");
                    tagEntry = document.getElementById("tags");
                    postSources = Array(imgList.length);
                    siteObj.posts.RemoveTextFillerElements();

                    tagTypeLookup = {
                        0: "tag-type-general",
                        1: "tag-type-artist",
                        2: "tag-type-copyright",
                        3: "tag-type-copyright",
                        4: "tag-type-character"
                    };

                    postSources = {};
                    if (document.getElementById("recommended"))
                        buttonInsertionPoint = document.getElementById("recommended");
                };

                GetSrcForImg = function (getIndex) {
                    let tmpImg = imgList[getIndex].childNodes[0].childNodes[0];
                    let srcIndex = tmpImg.src;

                    if (postSources[srcIndex]) {
                        return postSources[srcIndex];
                    } else {
                        var postReq = siteObj.posts.SinglePostSrc(getIndex);
                        if (postReq == undefined)
                            return "";
                        return postReq;
                    }
                };

                let redirectCheck = function (response) {
                    //console.log("rdc: " + response.finalUrl);
                    return !response.finalUrl.includes("redirect.png");
                };

                siteObj.posts.SinglePostSrc = function (index) {
                    let tmpImg = imgList[index].childNodes[0].childNodes[0];
                    let srcIndex = tmpImg.src;

                    let tImg = imgList[index];
                    let postUrl = tImg.getAttribute('openref');
                    let postHtml = GetHtmlFromUrl(postUrl);
                    let imageSrc = postHtml.substring(postHtml.indexOf('Original: <a href="') + 19);
                    //imageSrc = imageSrc.substring(imageSrc.indexOf('src="')+5, imageSrc.indexOf('>'));
                    imageSrc = 'http:' + imageSrc.substring(0, imageSrc.indexOf('"')).replace('&amp;', '&');

                    postSources[srcIndex] = imageSrc;
                    return postSources[srcIndex];

                    //old


                    if (postSources[srcIndex] === undefined) {
                        let tmpUrl = tmpImg.src;
                        tmpUrl = tmpUrl.replace("/preview/", "/").replace("c.sank", "cs.sank").replace("i.sank", "is.sank");
                        tmpUrl = tmpUrl.substring(0, tmpUrl.lastIndexOf("."));
                        let tmpId = "?" + imgList[index].id.substring(1);
                        let tmpTags = tmpImg.getAttribute("title");
                        tmpTags = tmpTags.substring(0, tmpTags.lastIndexOf("Rating:"));

                        if (tmpTags.includes("animated") || tmpTags.includes("video") || tmpTags.includes("mp4") || tmpTags.includes("webm") || tmpTags.includes("animated_gif")) {
                            if (tmpTags.includes("mp4")) {
                                //AsyncUrlCheck(tmpUrl + ".mp4", index, redirectCheck);
                                postSources[srcIndex] = tmpUrl + ".mp4" + tmpId;
                            } else if (tmpTags.includes("webm")) {
                                //AsyncUrlCheck(tmpUrl + ".webm", index, redirectCheck);
                                postSources[srcIndex] = tmpUrl + ".webm" + tmpId;
                            } else if (tmpTags.includes("animated_gif")) {
                                //AsyncUrlCheck(tmpUrl + ".gif", index, redirectCheck);
                                postSources[srcIndex] = tmpUrl + ".gif" + tmpId;
                            } else {
                                NoApiFindUrlAsync(index, srcIndex, tmpUrl, true, tmpId, redirectCheck);
                            }
                            return postSources[srcIndex];
                        } else {
                            NoApiFindUrlAsync(index, srcIndex, tmpUrl, false, tmpId, redirectCheck);
                        }
                    } else {
                        return postSources[srcIndex];
                    }
                };

                siteObj.posts.BatchPostApiCall = function () {
                    siteObj.posts.DisableImageLinks();
                    //let tCount = 0;
                    //for (let i = 0; i < imgList.length; i++) {
                    //    let tSrc = siteObj.posts.SinglePostSrc(i);
                    //    if(tSrc !== undefined){
                    //        tCount++;
                    //    }
                    //}
                    //console.log(tCount + "/" + imgList.length);
                };

                break;

        }

        //end switch setup
    }


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

    function encodeUriSpecial(str) {
        return encodeURIComponent(str).replace(/\(/g, "%28").replace(/\)/g, "%29");
    }

    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 cloneObject(obj) {
        if (obj === null || typeof obj !== 'object') {
            return obj;
        }

        let temp = obj.constructor(); // give temp the original obj's constructor
        for (let key in obj) {
            temp[key] = cloneObject(obj[key]);
        }

        return temp;
    }

    function AsyncHtmlDocHandler(index, callback) {
        try {
            let url = imgList[index].getAttribute("openRef");

            var xhr = new XMLHttpRequest();

            xhr.onload = function () {
                if (this.status = 200 && this.readyState == 4 && callback && typeof( callback ) === 'function') {
                    callback(this.response);
                }
            };

            xhr.open('GET', url);
            xhr.responseType = 'document';
            xhr.send();
        } catch (ex) {

        }
    }

    function NoApiFindUrlAsync(index, srcIndex, url, checkAnimated = false, append = "", extraCheck = function (r) {
        return true;
    }) {
        if (urlChecks[url])
            return;
        urlChecks[url] = {
            jpg: null,
            jpeg: null,
            png: null,
            gif: null,
            mp4: null,
            webm: null,
            abortAll: function () {
                if (this.jpg)
                    this.jpg.abort();
                if (this.jpeg)
                    this.jpeg.abort();
                if (this.png)
                    this.png.abort();
                if (this.gif)
                    this.gif.abort();
                if (this.mp4)
                    this.mp4.abort();
                if (this.webm)
                    this.webm.abort();

                jpg = null;
                jpeg = null;
                png = null;
                gif = null;
                mp4 = null;
                webm = null;
            }
        };
        if (!checkAnimated) {
            AsyncUrlCheck(url, append, index, srcIndex, "jpg", extraCheck,);
            AsyncUrlCheck(url, append, index, srcIndex, "jpeg", extraCheck);
            AsyncUrlCheck(url, append, index, srcIndex, "png", extraCheck);
        } else {
            AsyncUrlCheck(url, append, index, srcIndex, "mp4", extraCheck);
            AsyncUrlCheck(url, append, index, srcIndex, "gif", extraCheck);
            AsyncUrlCheck(url, append, index, srcIndex, "webm", extraCheck);
        }
    }

    function AsyncUrlCheck(url, append, index, srcIndex, ext, extraCheck) {
        let modUrl = url + "." + ext + append;
        try {
            urlChecks[url][ext] = GM_xmlhttpRequest({
                method: "HEAD",
                url: modUrl,
                onreadystatechange: function (response) {
                    if (response.status == 0)
                        return;

                    if (response.status == 200 && extraCheck(response)) {
                        //urlChecks[url] = false;
                        //console.log(response.readyState +" : " + response.status + " : " + response.finalUrl);
                        console.log("got: " + modUrl);
                        postSources[srcIndex] = modUrl;
                        if (imgOpened && imgIndex == index) {
                            SetImg();
                        }
                        urlChecks[url].abortAll();
                    } else if (response.status == 404) {
                        console.log("not found: " + modUrl);
                        urlChecks[url][ext].abort();
                    } else {
                        console.log("something else: " + response.status + " : " + response.readyState + " : " + response.finalUrl);
                        urlChecks[url] = null;
                    }
                }
            });
        } catch (ex) {
            console.log(ex);
            urlChecks[url] = null;
        }
    }

    function GetHtmlFromUrl(url){
        var xhr = new XMLHttpRequest();
        xhr.open("GET", url, false);
        xhr.send();
        //xhr.onreadystatechange = function(){
        //
        //}
        return xhr.response;
    }

    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;
    }


})
();