Gelbooru Visited and Type Highlighter

Marks previously visited images on Gelbooru search pages, and extends animation highlighting from webm to also include gifs.

Verze ze dne 25. 07. 2020. Zobrazit nejnovější verzi.

// ==UserScript==
// @name         Gelbooru Visited and Type Highlighter
// @namespace    http://tampermonkey.net/
// @version      7.0.0
// @description  Marks previously visited images on Gelbooru search pages, and extends animation highlighting from webm to also include gifs.
// @author       Xerodusk
// @homepage     https://greasyfork.org/en/users/460331-xerodusk
// @match        https://gelbooru.com/index.php*
// @grant        none
// @icon         https://gelbooru.com/favicon.png
// ==/UserScript==
/* jshint esversion: 6 */

/*   configuration   */

// Highlight colors
// Values can be hexadecimal, rgb, rgba, hsl, hsla, color name, or whatever CSS color definitions your browser supports
const imgUnvistedColor = '#E1F5FE'; // Color for unvisted images
const imgVisitedColor = '#2E7D32'; // Color for visited images
const webmUnvistedColor = '#1565C0'; // Color for unvisted WebMs
const webmVisitedColor = '#C62828'; // Color for visited WebMs
const gifUnvisitedColor = '#FFD600'; // Color for unvisited animated gifs/pngs
const gifVisitedColor = '#6A1B9A'; // Color for visited animated gifs/pngs

// Whether to display visited/unvisited highlighting for your own favorites
// If false: Will only show visited/unvisited on other users' favorites pages
//           Animated GIF/WebM type highlighting will always be shown on all favorites
// If true:  Will also show visited/unvisited on your own favorites page
const displayCurrentUserFavoritesVisited = true;

/*-------------------*/

// Convert any image link URL to a single, standard form
function normalizeURL(linkURL) {
    'use strict';

    // Remove "tags" and "pool" attributes so the link being used for this
    // is constant for the same image across all searches it is included in
    let searchParams = new URLSearchParams(linkURL.search);
    searchParams.forEach((value, key) => {
        if (!['page', 's', 'id'].includes(key)) {
            searchParams.delete(key);
        }
    });
    let newLinkURL = new URL(linkURL);
    newLinkURL.search = searchParams.toString();
    return newLinkURL;
}

// Convert link formation so it is consistent across all pages
function normalizeLink(galleryLink) {
    'use strict';

    // Convert to absolute URL from whichever random form of relative URL they decided to use on this specific page with no real consistency
    let linkURL = new URL(galleryLink.getAttribute('href'), window.location.href);
    // Fix for Gelbooru's broken implementation of tags containing apostrophes under many navigational situations
    if (linkURL.href.includes(''')) {
        linkURL.href = linkURL.href.replace(''', '%27');
    }
    // Set click to go to the original url to preserve next/previous feature
    galleryLink.setAttribute('onmousedown', 'this.setAttribute("href", "' + linkURL + '")');
    galleryLink.setAttribute('onmouseup', 'this.setAttribute("href", "' + linkURL + '")');
    galleryLink.setAttribute('onkeydown', 'if(event.keyCode == 13) { this.setAttribute("href", "' + linkURL + '") }');
    // Convert URL to be the one URL for one image
    let newLinkURL = normalizeURL(linkURL);
    galleryLink.setAttribute('href', newLinkURL);
    galleryLink.setAttribute('onfocusout', 'this.setAttribute("href", "' + newLinkURL + '")');
}

// Get cookie by name
// From https://www.w3schools.com/js/js_cookies.asp
function getCookie(cname) {
    'use strict';

    var name = cname + "=";
    var decodedCookie = decodeURIComponent(document.cookie);
    var ca = decodedCookie.split(';');
    for (var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') {
            c = c.substring(1);
        }
        if (c.indexOf(name) == 0) {
            return c.substring(name.length, c.length);
        }
    }
    return "";
}

// Get current user's user ID, if exists
function getUserID() {
    'use strict';

    // Get user ID from cookie
    let userID = getCookie('user_id');

    return userID ? userID : -1;
}

(function() {
    'use strict';

    let searchParams = new URLSearchParams(window.location.search);

    if (!searchParams.has('page') || !searchParams.has('s')) {
        return false;
    }
    if (searchParams.get('page') === 'post') {
        if (searchParams.get('s') == 'list') { // Search page
            // Get search results area
            let galleryContainer = document.querySelector('.thumbnail-container');
            if (!!galleryContainer) {
                // Get all image thumbnail links
                let galleryLinks = galleryContainer.querySelectorAll('div.thumbnail-preview a');
                if (!!galleryLinks) {
                    galleryLinks.forEach(galleryLink => normalizeLink(galleryLink));
                }
            }
            // Apply borders
            let css = document.createElement('style');
            css.innerHTML = `
                div.thumbnail-preview {
                    background-color: transparent;
                }
                div.thumbnail-preview a img.thumbnail-preview:not(.webm) {
                    outline: 3px solid ` + imgUnvistedColor + `;
                }
                div.thumbnail-preview a:visited img.thumbnail-preview {
                    outline-color: ` + imgVisitedColor + `;
                }
                div.thumbnail-preview a img.thumbnail-preview.webm {
                    border-color: ` + webmUnvistedColor + ` !important;
                }
                div.thumbnail-preview a:visited img.thumbnail-preview.webm {
                    border-color: ` + webmVisitedColor + ` !important;
                }
                div.thumbnail-preview a img.thumbnail-preview[title*="animated_gif"],
                div.thumbnail-preview a img.thumbnail-preview[title*="animated_png"],
                div.thumbnail-preview a img.thumbnail-preview[title*="animated "]:not(.webm) {
                    outline-color: ` + gifUnvisitedColor + `;
                }
                div.thumbnail-preview a:visited img.thumbnail-preview[title*="animated_gif"],
                div.thumbnail-preview a:visited img.thumbnail-preview[title*="animated_png"],
                div.thumbnail-preview a:visited img.thumbnail-preview[title*="animated "]:not(.webm) {
                    outline-color: ` + gifVisitedColor + `;
                }
                div.thumbnail-preview a:focus img.thumbnail-preview:not(.webm) {
                    outline-color: #FFA726 !important;
                }
                div.thumbnail-preview a:focus img.thumbnail-preview.webm {
                    border-color: #FFA726 !important;
                }
            `;
            document.head.appendChild(css);
        } else if (searchParams.get('s') === 'view') { // Image page
            // Add URL without the "tags" attribute to the history
            // so as to make it match what the search results pages are modified for
            // (and avoid breaking back button functionality while we're at it)
            let url = new URL(window.location);
            url = normalizeURL(url);
            window.history.replaceState({}, '', '/' + url.href.substring(url.href.indexOf('/') + 1));
            // "More Like This" results, currently in beta, could break, but this code should hypothetically never break the page from this end
            // Unfortunately, type highlighting is impossible with their current implementation, but visited highlighting is still possible
            /*let mltContainer = document.getElementById('post-view');
            // Get all image thumbnail links
            let mltLinks = mltContainer.querySelectorAll('#right-col > div > div > a');
            if (!!mltLinks) {
                mltLinks.forEach(mltLink => normalizeLink(mltLink));
            }*/ // In case this becomes necessary later, what with this being a beta feature that could change rapidly and all
            let css = document.createElement('style');
            css.innerHTML = css.innerHTML + `
                a img.mltThumbs {
                    padding: 5px;
                    margin: 5px !important;
                    outline: 3px solid ` + imgUnvistedColor + `;
                }
                a:visited img.mltThumbs {
                    outline-color: ` + imgVisitedColor + `;
                }
            `;
            document.head.appendChild(css);
        }
    } else if (searchParams.get('s') === 'show' && searchParams.get('page') === 'pool') { // Pool page
        // Get image thumbnails area
        let galleryContainer = document.querySelector('.thumbnail-container');
        if (!!galleryContainer) {
            // Get all image thumbnail links
            let galleryLinks = galleryContainer.querySelectorAll('span a');
            if (!!galleryLinks) {
                galleryLinks.forEach(galleryLink => normalizeLink(galleryLink));
            }
        }
        // Apply borders
        let css = document.createElement('style');
        css.innerHTML = `
            div.thumbnail-container a img {
                outline: 3px solid ` + imgUnvistedColor + `;
            }
            div.thumbnail-container a:visited img {
                outline-color: ` + imgVisitedColor + `;
            }
            div.thumbnail-container a img[title*=" webm "] {
                outline: 5px solid ` + webmUnvistedColor + ` !important;
            }
            div.thumbnail-container a:visited img[title*=" webm "] {
                outline-color: ` + webmVisitedColor + ` !important;
            }
            div.thumbnail-container a img[title*="animated_gif"],
            div.thumbnail-container a img[title*="animated_png"],
            div.thumbnail-container a img[title*="animated "]:not([title*=" webm "]) {
                outline-color: ` + gifUnvisitedColor + `;
            }
            div.thumbnail-container a:visited img[title*="animated_gif"],
            div.thumbnail-container a:visited img[title*="animated_png"],
            div.thumbnail-container a:visited img[title*="animated "]:not([title*=" webm "]) {
                outline-color: ` + gifVisitedColor + `;
            }
            div.thumbnail-container a:focus img {
                outline-color: #FFA726 !important;
            }
        `;
        document.head.appendChild(css);
    } else if (searchParams.get('s') === 'view' && searchParams.get('page') === 'favorites') { // Favorites page
        // Apply borders
        let css = document.createElement('style');
        css.innerHTML = `
            .thumb {
                margin: 5px;
            }
            .thumb a img {
                padding: 5px;
                margin: 5px;
            }
            .thumb a img[title*="animated_gif"],
            .thumb a img[title*="animated_png"],
            .thumb a img[title*="animated "]:not([title*=" webm"]) {
                outline: 3px solid ` + gifUnvisitedColor + `;
            }
            .thumb a img[title*=" webm"] {
                outline: 5px solid ` + webmUnvistedColor + `;
            }
        `;

        let userID = displayCurrentUserFavoritesVisited ? -1 : getUserID();
        if (searchParams.has('id') && searchParams.get('id') != userID) {
            css.innerHTML = css.innerHTML + `
                .thumb a img {
                    outline: 3px solid ` + imgUnvistedColor + `;
                }
                .thumb a:visited img {
                    outline-color: ` + imgVisitedColor + `;
                }
                .thumb a:visited img[title*=" webm"] {
                    outline-color: ` + webmVisitedColor + `;
                }
                .thumb a:visited img[title*="animated_gif"],
                .thumb a:visited img[title*="animated_png"],
                .thumb a:visited img[title*="animated "]:not([title*=" webm"]) {
                    outline-color: ` + gifVisitedColor + `;
                }
            `;
        }
        document.head.appendChild(css);
    } else if (searchParams.get('s') === 'saved_search' && searchParams.get('page') === 'tags') { // Saved Searches page
        // Apply borders
        let css = document.createElement('style');
        css.innerHTML = `
            .container-fluid > .thumb a .thumbnail-preview {
                outline: 3px solid ` + imgUnvistedColor + `;
            }
            .container-fluid > .thumb a:visited .thumbnail-preview {
                outline-color: ` + imgVisitedColor + `;
            }
            .container-fluid > .thumb a .thumbnail-preview[alt*="animated_gif"],
            .container-fluid > .thumb a .thumbnail-preview[alt*="animated_png"],
            .container-fluid > .thumb a .thumbnail-preview[alt*="animated "]:not([alt*=" webm"]) {
                outline: 3px solid ` + gifUnvisitedColor + `;
            }
            .container-fluid > .thumb a .thumbnail-preview[alt*=" webm"] {
                outline: 5px solid ` + webmUnvistedColor + `;
                margin: 5px 7px;
            }
            .container-fluid > .thumb a:visited .thumbnail-preview[alt*=" webm"] {
                outline-color: ` + webmVisitedColor + `;
            }
            .container-fluid > .thumb a:visited .thumbnail-preview[alt*="animated_gif"],
            .container-fluid > .thumb a:visited .thumbnail-preview[alt*="animated_png"],
            .container-fluid > .thumb a:visited .thumbnail-preview[alt*="animated "]:not([alt*=" webm"]) {
                outline-color: ` + gifVisitedColor + `;
            }
        `;
        document.head.appendChild(css);
    }
})();