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 30. 10. 2020. Zobrazit nejnovější verzi.

// ==UserScript==
// @name         Gelbooru Visited and Type Highlighter
// @namespace    http://tampermonkey.net/
// @version      10.1.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
// @include      https://gelbooru.com/index.php*page=post*s=list*
// @include      https://gelbooru.com/index.php*page=post*s=view*
// @include      https://gelbooru.com/index.php*page=pool*s=show*
// @include      https://gelbooru.com/index.php*page=favorites*s=view*
// @include      https://gelbooru.com/index.php*page=tags*s=saved_search*
// @include      https://gelbooru.com/index.php*page=wiki*s=view*
// @include      https://gelbooru.com/index.php*page=account*s=profile*
// @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;

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

function binarySearch(items, value, first, last) {
    if (items.length === 0) {
        return -1;
    }
    const middle = (last + first) >> 1;
    if (last - first <= 1) {
        return (value < items[middle]) ? middle - 1 : middle;
    }
    if (value === items[middle]) {
        return middle;
    }
    if (value < items[middle]) {
        return binarySearch(items, value, first, middle);
    }
    return binarySearch(items, value, middle, last);
}

// Tests whether value is in items
function truthyBinarySearch(items, value) {
    if (items.length === 0 || value > items[items.length - 1] || value < items[0]) {
        return false;
    }
    if (value === items[0] || value === items[items.length - 1]) {
        return true;
    }

    const index = binarySearch(items, value, 0, items.length - 1);

    return (items[index] === value);
}

// Inserts value in items if not already present, returns whether insertion took place
function binaryInsertion(items, value) {
    if (items.length === 0 || value > items[items.length - 1]) {
        items.push(value);
        return true;
    }
    if (value < items[0]) {
        items.unshift(value);
        return true;
    }

    const index = binarySearch(items, value, 0, items.length - 1);

    if (items[index] !== value) {
        items.splice(index + 1, 0, value);
        return true;
    }
    return false;
}

// Check if link is in visited list
function markIfVisited(galleryLink, visitedIDs) {
    const linkURL = new URL(galleryLink.getAttribute('href'), window.location.href);
    const linkSearchParams = new URLSearchParams(linkURL.search);
    const id = parseInt(linkSearchParams.get('id'));

    if (truthyBinarySearch(visitedIDs, id)) {
        galleryLink.classList.add('visited');
    }
}

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

    const name = cname + "=";
    const decodedCookie = decodeURIComponent(document.cookie);
    const ca = decodedCookie.split(';');
    for (let i = 0; i < ca.length; i++) {
        let 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
    const userID = getCookie('user_id');

    return userID ? parseInt(userID) : -1;
}

(function() {
    'use strict';

    // Find out what kind of page we're on
    const searchParams = new URLSearchParams(window.location.search);

    if (!searchParams.has('page') || !searchParams.has('s')) {
        return false;
    }

    const page = searchParams.get('page');
    const s = searchParams.get('s');

    if (page === 'post') {
        if (s === 'view') { // Image page
            // Get id of current image
            const url = new URL(window.location);
            const currentURLSearchParams = new URLSearchParams(url.search);
            const id = parseInt(currentURLSearchParams.get('id'));
            // Get list of visited images
            let visitedIDs;

            function updateVisitedIDs(event) {
                visitedIDs = JSON.parse(localStorage.getItem('visitedIDs')) || [];
                if (binaryInsertion(visitedIDs, id)) {
                    localStorage.setItem('visitedIDs', JSON.stringify(visitedIDs));
                } else {
                    window.removeEventListener('storage', updateVisitedIDs);
                }
            }
            window.addEventListener('storage', updateVisitedIDs); // Update changes if another image being loaded in a different window changes the list before this one
            updateVisitedIDs();

            // "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
            const mltContainer = document.getElementsByClassName('contain-push')[0];
            // Get all image thumbnail links
            const mltLinks = mltContainer.querySelectorAll('#right-col > div > div > a');
            if (!!mltLinks) {
                mltLinks.forEach(mltLink => markIfVisited(mltLink, visitedIDs));
            }

            const css = document.createElement('style');
            css.innerHTML = css.innerHTML + `
                a img.mltThumbs {
                    padding: 5px;
                    margin: 5px !important;
                    outline: 3px solid ` + imgUnvistedColor + `;
                }
                a:visited img.mltThumbs,
                a.visited img.mltThumbs {
                    outline-color: ` + imgVisitedColor + `;
                }
            `;
            document.head.appendChild(css);
        } else if (s === 'list') { // Search page
            // Get list of visited images
            const visitedIDs = JSON.parse(localStorage.getItem('visitedIDs')) || [];
            if (visitedIDs.length > 0) {
                // Get search results area
                const galleryContainer = document.querySelector('.thumbnail-container');
                if (!!galleryContainer) {
                    // Get all image thumbnail links
                    const galleryLinks = galleryContainer.querySelectorAll('div.thumbnail-preview a');
                    if (!!galleryLinks) {
                        galleryLinks.forEach(galleryLink => markIfVisited(galleryLink, visitedIDs));
                    }
                }
            }

            // Apply borders
            const 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,
                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,
                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),
                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 (page === 'pool' && s === 'show') { // Pool page
        // Get list of visited images
        const visitedIDs = JSON.parse(localStorage.getItem('visitedIDs')) || [];
        if (visitedIDs.length > 0) {
            // Get image thumbnails area
            const galleryContainer = document.querySelector('.thumbnail-container');
            if (!!galleryContainer) {
                // Get all image thumbnail links
                const galleryLinks = galleryContainer.querySelectorAll('span a');
                if (!!galleryLinks) {
                    galleryLinks.forEach(galleryLink => markIfVisited(galleryLink, visitedIDs));
                }
            }
        }
        // Apply borders
        const css = document.createElement('style');
        css.innerHTML = `
            div.thumbnail-container a img {
                outline: 3px solid ` + imgUnvistedColor + `;
            }
            div.thumbnail-container a:visited img,
            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 "],
            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 "]),
            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 (page === 'favorites' && s === 'view') { // Favorites page
        // Apply borders
        const 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 + `;
            }
        `;

        const userID = displayCurrentUserFavoritesVisited ? -1 : getUserID();
        if (searchParams.has('id') && parseInt(searchParams.get('id')) != userID) {
            // Get list of visited images
            const visitedIDs = JSON.parse(localStorage.getItem('visitedIDs')) || [];
            // Mark visited links
            if (visitedIDs.length > 0) {
                const galleryLinks = document.querySelectorAll('.thumb a[href*="page=post"]');
                galleryLinks.forEach(galleryLink => markIfVisited(galleryLink, visitedIDs));
            }
            css.innerHTML += `
                .thumb a img {
                    outline: 3px solid ` + imgUnvistedColor + `;
                }
                .thumb a:visited img,
                .thumb a.visited img {
                    outline-color: ` + imgVisitedColor + `;
                }
                .thumb a:visited img[title*=" webm"],
                .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"]),
                .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 (page === 'tags' && s === 'saved_search') { // Saved Searches page
        // Get list of visited images
        const visitedIDs = JSON.parse(localStorage.getItem('visitedIDs')) || [];
        // Mark visited links
        if (visitedIDs.length > 0) {
            const galleryLinks = document.querySelectorAll('.container-fluid > .thumb a');
            galleryLinks.forEach(galleryLink => markIfVisited(galleryLink, visitedIDs));
        }

        // Apply borders
        const css = document.createElement('style');
        css.innerHTML = `
            .container-fluid > .thumb a .thumbnail-preview {
                outline: 3px solid ` + imgUnvistedColor + `;
            }
            .container-fluid > .thumb a:visited .thumbnail-preview,
            .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"],
            .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"]),
            .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);
    } else if (page === 'wiki' && s === 'view') { // Wiki entry page
        // Get list of visited images
        const visitedIDs = JSON.parse(localStorage.getItem('visitedIDs')) || [];
        // Mark visited links
        if (visitedIDs.length > 0) {
            const galleryLinks = document.querySelectorAll('tr > td:nth-child(2) a[href*="s=view"]');
            galleryLinks.forEach(galleryLink => markIfVisited(galleryLink, visitedIDs));
        }

        // Apply borders
        const css = document.createElement('style');
        css.innerHTML = `
            a .thumbnail-preview img {
                padding: 5px;
                outline: 3px solid ` + imgUnvistedColor + `;
            }
            a:visited .thumbnail-preview img,
            a.visited .thumbnail-preview img {
                outline-color: ` + imgVisitedColor + `;
            }
            a .thumbnail-preview img[alt*="animated_gif"],
            a .thumbnail-preview img[alt*="animated_png"],
            a .thumbnail-preview img[alt*="animated "]:not([alt*=" webm"]) {
                outline: 3px solid ` + gifUnvisitedColor + `;
            }
            a .thumbnail-preview img[alt*=" webm"] {
                outline: 5px solid ` + webmUnvistedColor + `;
                margin: 5px 7px;
            }
            a:visited .thumbnail-preview img[alt*=" webm"],
            a.visited .thumbnail-preview img[alt*=" webm"] {
                outline-color: ` + webmVisitedColor + `;
            }
            a:visited .thumbnail-preview img[alt*="animated_gif"],
            a:visited .thumbnail-preview img[alt*="animated_png"],
            a:visited .thumbnail-preview img[alt*="animated "]:not([alt*=" webm"]),
            a.visited .thumbnail-preview img[alt*="animated_gif"],
            a.visited .thumbnail-preview img[alt*="animated_png"],
            a.visited .thumbnail-preview img[alt*="animated "]:not([alt*=" webm"]) {
                outline-color: ` + gifVisitedColor + `;
            }
        `;
        document.head.appendChild(css);
    } else if (page === 'account' && s === 'profile') { // Profile page
        // Get list of visited images
        const visitedIDs = JSON.parse(localStorage.getItem('visitedIDs')) || [];
        // Mark visited links
        if (visitedIDs.length > 0) {
            const galleryLinks = document.querySelectorAll('a[href*="s=view"]');
            galleryLinks.forEach(galleryLink => markIfVisited(galleryLink, visitedIDs));
        }

        // Apply borders
        const css = document.createElement('style');
        css.innerHTML = `
            .profileThumbnailPadding {
                max-width: none !important;
            }
            #statistics > span:last-child {
                display: none;
            }
            a[href*="s=view"] img {
                padding: 5px;
                outline: 3px solid ` + imgUnvistedColor + `;
                max-height: 190px;
                object-fit: scale-down;
            }
            a[href*="s=view"]:visited img,
            a[href*="s=view"].visited img {
                outline-color: ` + imgVisitedColor + `;
            }
            a[href*="s=view"] img[alt*="animated_gif"],
            a[href*="s=view"] img[alt*="animated_png"],
            a[href*="s=view"] img[alt*="animated "]:not([alt*=" webm"]) {
                outline: 3px solid ` + gifUnvisitedColor + `;
            }
            a[href*="s=view"] img[alt*=" webm"] {
                outline: 5px solid ` + webmUnvistedColor + `;
                margin: 5px 7px;
            }
            a[href*="s=view"]:visited img[alt*=" webm"],
            a[href*="s=view"].visited img[alt*=" webm"] {
                outline-color: ` + webmVisitedColor + `;
            }
            a[href*="s=view"]:visited img[alt*="animated_gif"],
            a[href*="s=view"]:visited img[alt*="animated_png"],
            a[href*="s=view"]:visited img[alt*="animated "]:not([alt*=" webm"]),
            a[href*="s=view"].visited img[alt*="animated_gif"],
            a[href*="s=view"].visited img[alt*="animated_png"],
            a[href*="s=view"].visited img[alt*="animated "]:not([alt*=" webm"]) {
                outline-color: ` + gifVisitedColor + `;
            }
        `;
        document.head.appendChild(css);
    }
})();