Reddit Image Search Add-on

Create instant reverse image search links for image posts

// ==UserScript==
// @name         Reddit Image Search Add-on
// @namespace    https://greasyfork.org/en/users/76021-dalimcodes
// @version      1.0
// @description  Create instant reverse image search links for image posts
// @author       DaLimCodes
// @match        https://*.reddit.com/*
// @require      https://cdn.rawgit.com/uzairfarooq/arrive/cfabddbd2633a866742e98c88ba5e4b75cb5257b/minified/arrive.min.js
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

var extraCSS = document.createElement("style");
extraCSS.textContent = `
.clickable {
    color:green !important;
    cursor:pointer;
    font-weight: bold;
}
div.reverse-search-div{
    position: absolute;
    padding: 4px 20px;
    z-index: 1000;
    border: 1px solid black;
    background-color: white
}
div#reverse-search-setting-div {
    position: absolute;
    border: 1px solid black;
    background-color: white;
    padding: 4px 8px;
    z-index: 1001;
    display: none
}
div#debug-div {
    position: fixed,
    top: 40px,
    right: 40px,
    border: 1px solid black;
    background-color: red,
    padding: 8px,
    z-index: 1000
}
`;
document.head.appendChild(extraCSS);

const imageSupportedRegex = /\.(gif|jpe?g|png)/i;
const debug = false

$(document).ready(generateLinks)
$(document).arrive('div.sitetable', generateLinks)
$(document).ready(function () {
    var settingDiv = $(`<div id="reverse-search-setting-div" class="floatDiv"></div>`)
    settingDiv.append(`
<div> Display link:<br>
<input type="radio" name="display_setting" value="inline">In-line<br>
<input type="radio" name="display_setting" value="dropdown">Dropdown
</div>`)
    $("body").append(settingDiv)
    if (GM_getValue("link-display-setting") == "dropdown") {
        $('input[name=display_setting][value=dropdown]').prop('checked', true)
    } else {
        $('input[name=display_setting][value=inline]').prop('checked', true)
        GM_getValue("link-display-setting", "inline")
    }
    $('input:radio[name="display_setting"]').change(function () {
        changeLinkDisplaySetting($(this).attr("value"))
        settingDiv.toggle()
    });
})

if (debug) {
    var debugDiv = $(`<div id="debug-div" class="floatDiv"><label>Debug Panel:</label></div>`);
    $("body").append(debugDiv)

    debugDiv.append(`<div><button id="generatorButton">Generate Links</button></div>`)
    $("#generatorButton").click(generateLinks)

    debugDiv.append(`<div><button id="switchInlineButton">Switch Link Display to Inline</button></div>`)
    $("#switchInlineButton").click(function () {
        changeLinkDisplaySetting("inline")
    })

    debugDiv.append(`<div><button id="switchDropdownButton">Switch Link Display to Dropdown</button></div>`)
    $("#switchDropdownButton").click(function () {
        changeLinkDisplaySetting("dropdown")
    })
}

function changeLinkDisplaySetting(newSetting) {
    if (GM_getValue("link-display-setting") != newSetting) {
        GM_setValue("link-display-setting", newSetting)
        generateLinks()
    }
}

function generateLinks() {
    if (debug) {
        console.log("Generating links!")
    }
    $(".reverse-search-button").remove()
    $(".reverse-search-div").remove()
    $(".reverse-search-link").remove()

    var hasRES = document.getElementsByClassName("res-ner-listing").length
    var obj, link, i, index = 0
    if (hasRES) {
        var things = $("div.thing.link")
        for (i = 0; i < things.length; i++) {
            obj = $(things[i])
            link = obj.attr("data-url")
            if (link.match("imgur.com") && !link.match("imgur.com/a/") && !link.match(imageSupportedRegex)) {
                link += ".jpg"
            }
            link = encodeURIComponent(link)
            if (link.match(imageSupportedRegex)) {
                index++
                // Check if it's a three or four letter image extension
                if (link.charAt(link.search(imageSupportedRegex) + 4).match(/[a-zA-Z]/)) {
                    link = link.substr(0, link.search(imageSupportedRegex) + 5)
                } else {
                    link = link.substr(0, link.search(imageSupportedRegex) + 4)
                }
                appendLinks(obj.find("ul.buttons"), link, GM_getValue("link-display-setting", "inline"), index)
                if (debug) {
                    console.log("Image link:", link)
                }
            } else {
                if (debug) {
                    console.log("Non-image link:", link)
                }
            }
        }
    } else {
        // first pass to find links inside expando
        entry = $("a.title")
        for (i = 0; i < entry.length; i++) {
            obj = $(entry[i])
            link = obj.attr("href")
            if (link.match("imgur.com") && !link.match("imgur.com/a/") && !link.match(imageSupportedRegex)) {
                link += ".jpg"
            }
            link = encodeURIComponent(link)
            if (link.match(imageSupportedRegex)) {
                index++
                // Check if it's a three or four letter image extension
                if (link.charAt(link.search(imageSupportedRegex) + 4).match(/[a-zA-Z]/)) {
                    link = link.substr(0, link.search(imageSupportedRegex) + 5)
                } else {
                    link = link.substr(0, link.search(imageSupportedRegex) + 4)
                }
                appendLinks(obj.closest("div.entry").find("ul.buttons"), link, GM_getValue("link-display-setting", "inline"), index)
                if (debug) {
                    console.log("Image link:", link)
                }
            } else {
                if (debug) {
                    console.log("Non-image link:", link)
                }
            }

        }

        // second pass to find links in the title
        var entry = $(".expando")
        for (i = 0; i < entry.length; i++) {
            obj = $(entry[i])
            if (obj.closest("div.entry").find("li.reverse-search-button").length == 0) {
                if (obj.attr("data-cachedhtml") !== undefined) {
                    var cachedhtml = $($.parseHTML(obj.attr("data-cachedhtml").trim()));
                    link = cachedhtml.find("a.may-blank").attr("href");
                    if (link !== undefined) {
                        index++;
                        if (link.match("imgur.com") && !link.match("imgur.com/a/") && !link.match(imageSupportedRegex)) {
                            link += ".jpg";
                        }
                        link = encodeURIComponent(link);
                        appendLinks(obj.closest("div.entry").find("ul.buttons"), link, GM_getValue("link-display-setting", "inline"), index)
                    }
                }
            }
        }
    }
}

function appendLinks(jQueryObjToAppend, imageLink, option, index) {
    var settingButton = $(`<li class="reverse-search-button"><a class="clickable">RIS Setting⚙</a></li>`)
    jQueryObjToAppend.append(settingButton);
    settingButton.on("click", settingButton, function () {
        $("#reverse-search-setting-div").toggle();
        $("#reverse-search-setting-div").css({
            'top': getOffset(settingButton[0]).top + settingButton.height() + "px",
            'left': -8 + getOffset(settingButton[0]).left + "px",
        });
    })
    if (option == "inline") {
        createLinks(jQueryObjToAppend, imageLink)
    } else if (option == "dropdown") {
        var element = $(`<li id="reverse-search-button_${index}" class="reverse-search-button" status="not open"><a class="clickable">Reverse Search <label>▾</label></a></li>`)
        jQueryObjToAppend.append(element)
        element.on("click", element, function () {
            if ($(this).attr("status") == "not open") {
                $(this).attr("status", "open")
                $($(this).find("label")[0]).text("▴")
                var linkDiv = $(`<div id="reverse-search-div_${index}" class="reverse-search-div floatDiv"></div>`)
                createLinks(linkDiv, imageLink)
                var pos = getOffset(this)
                linkDiv.css({
                    "top": pos.top + $(this).height() + "px",
                    "left": 8 + pos.left + "px",
                })
                $("body").append(linkDiv)
            } else {
                $(this).attr("status", "not open")
                $($(this).find("label")[0]).text("▾")
                var id = $(this).attr("id").split("_")[1]
                $("#reverse-search-div_" + id).remove()
            }
        })
    } else {
        console.log("error, no link appended")
    }
}

function createLinks(jQueryObjToAppend, imageLink) {
    jQueryObjToAppend.append(`<li class="reverse-search-link google"><a class="clickable" href="https://images.google.com/searchbyimage?image_url=${imageLink}" target="_blank">Google</a></li>`);
    jQueryObjToAppend.append(`<li class="reverse-search-link tineye"><a class="clickable" href="https://www.tineye.com/search?url=${imageLink}" target="_blank">TinEye</a></li>`);
    jQueryObjToAppend.append(`<li class="reverse-search-link saucenao"><a class="clickable" href="https://saucenao.com/search.php?url=${imageLink}" target="_blank">SauceNAO</a></li>`);
    jQueryObjToAppend.append(`<li class="reverse-search-link iqdb"><a class="clickable" href="http://iqdb.org/?url=${imageLink}" target="_blank">Iqdb</a></li>`);
}

// Credit to Adam Grant: http://stackoverflow.com/questions/442404/retrieve-the-position-x-y-of-an-html-element
function getOffset(el) {
    el = el.getBoundingClientRect()
    return {
        left: el.left + window.scrollX,
        top: el.top + window.scrollY
    }
}