Gelbooru Animation Highlighter

Marks gifs as well similar to the built-in webm highlighting and makes webm highlighting work in more places.

От 03.01.2021. Виж последната версия.

// ==UserScript==
// @name         Gelbooru Animation Highlighter
// @namespace    http://tampermonkey.net/
// @version      14.4.1
// @description  Marks gifs as well similar to the built-in webm highlighting and makes webm highlighting work in more places.
// @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*
// @include      https://gelbooru.com/index.php*page=comment*s=list*
// @grant        none
// @icon         https://gelbooru.com/favicon.png
// ==/UserScript==
/* jshint esversion: 6 */

/*   configuration   */

// You can modify these from the "Highlighter Settings" item on the right side of the header on any page this runs on.

// Highlight colors
// Values can be hexadecimal, rgb, hsl, color name, or whatever CSS color definitions your browser supports
const webmColor = localStorage.getItem('webmUnvisitedColor') || '#1565C0'; // Color for WebMs
const gifColor = localStorage.getItem('gifUnvisitedColor') || '#FFD600'; // Color for animated gifs/pngs

// Whether to use the experimental workaround to display gif/webm highlighting on "More Like This" links on image pages
// NOTICE:   This is experimental and is not guaranteed to be perfect
const displayMoreLikeThisAnimatedTypes = JSON.parse(localStorage.getItem('displayMoreLikeThisAnimatedTypes') || 'true');

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

// Messy workaround for finding the type of "More Like This" links on image pages
function getAnimatedType(galleryLinks) {
    function checkURL(url, link, extension) {
        const request = new XMLHttpRequest();
        request.open('GET', url, true);
        request.onreadystatechange = () => {
            if (request.readyState === 4 && request.status !== 404) {
                link.classList.add(extension);
            }
        };
        request.send();
    }

    function checkImage(link) {
        const image = link.querySelector('img');
        if (!image) {
            return;
        }
        let imageURL = image.src;
        // Convert thumbnail to original url ==> gif
        imageURL = imageURL.replace('thumbs.gelbooru.com/', 'gelbooru.com/images').replace('thumbnail_', '').replace('.jpg', '.gif').replace('.jpeg', '.gif');

        // Check if gif version exists
        checkURL(imageURL, image, 'gif');

        // Convert to webm
        imageURL = imageURL.replace('.gif', '.webm');

        // Check if webm version exists
        checkURL(imageURL, image, 'webm');
    }

    // Check all images
    galleryLinks.forEach(galleryLink => checkImage(galleryLink));
}

// Create interface for settings
function createSettingsInterface() {
    'use strict';

    // Get subheader
    const header = document.getElementById('myTopnav') || document.querySelector('#navbar ul') || document.querySelector('.header .center .flat-list');
    if (!header) {
        return;
    }

    // Create button
    const openSettingsButtonContainer = document.createElement('li');
    openSettingsButtonContainer.classList.add('open-highlighter-dialog');
    const openSettingsButton = document.createElement('a');
    openSettingsButton.appendChild(document.createTextNode('Highlighter Settings'));
    openSettingsButton.setAttribute('role', 'button');
    openSettingsButton.href = 'javascript:void(0)';
    openSettingsButton.onclick = () => dialog.classList.add('open');
    openSettingsButtonContainer.appendChild(openSettingsButton);

    // Create dialog
    const dialog = document.createElement('div');
    dialog.id = 'settings-dialog';
    dialog.classList.add('highlighter-dialog');
    const dialogHeader = document.createElement('h3');
    dialogHeader.classList.add('highlighter-dialog-header');
    dialogHeader.appendChild(document.createTextNode('Type Highlighter Settings'));
    dialog.appendChild(dialogHeader);

    const webmColorSetting = document.createElement('div');
    webmColorSetting.classList.add('highlighter-setting-single');
    const webmColorInput = document.createElement('input');
    webmColorInput.id = 'webm-input';
    webmColorInput.setAttribute('name', 'webm-input');
    webmColorInput.setAttribute('type', 'color');
    webmColorInput.value = webmColor;
    const webmColorLabel = document.createElement('label');
    webmColorLabel.classList.add('highlighter-dialog-text');
    webmColorLabel.setAttribute('for', 'webm-input');
    webmColorLabel.appendChild(document.createTextNode('WebM Color'));
    webmColorSetting.appendChild(webmColorInput);
    webmColorSetting.appendChild(webmColorLabel);

    const gifColorSetting = document.createElement('div');
    gifColorSetting.classList.add('highlighter-setting-single');
    const gifColorInput = document.createElement('input');
    gifColorInput.id = 'gif-input';
    gifColorInput.setAttribute('name', 'gif-input');
    gifColorInput.setAttribute('type', 'color');
    gifColorInput.value = gifColor;
    const gifColorLabel = document.createElement('label');
    gifColorLabel.classList.add('highlighter-dialog-text');
    gifColorLabel.setAttribute('for', 'gif-input');
    gifColorLabel.appendChild(document.createTextNode('GIF Color'));
    gifColorSetting.appendChild(gifColorInput);
    gifColorSetting.appendChild(gifColorLabel);

    // Create the buttons
    const dialogButtons = document.createElement('div');
    dialogButtons.classList.add('highlighter-dialog-buttons');
    const applyButton = document.createElement('button');
    const defaultButton = document.createElement('button');
    const closeButton = document.createElement('button');
    applyButton.appendChild(document.createTextNode('Save'));
    applyButton.onclick = () => {
        localStorage.setItem('webmUnvisitedColor', webmColorInput.value);
        localStorage.setItem('gifUnvisitedColor', gifColorInput.value);
        if (confirm('The page must be reloaded for changes to take effect.\n\nReload now?')) {
            location.reload();
        }
        dialog.classList.remove('open');
    };
    defaultButton.appendChild(document.createTextNode('Restore Defaults'));
    defaultButton.onclick = () => {
        localStorage.removeItem('webmUnvisitedColor');
        localStorage.removeItem('gifUnvisitedColor');
        if (confirm('The page must be reloaded for changes to take effect.\n\nReload now?')) {
            location.reload();
        }
        dialog.classList.remove('open');
    };
    closeButton.appendChild(document.createTextNode('Cancel'));
    closeButton.onclick = () => dialog.classList.remove('open');
    dialogButtons.appendChild(applyButton);
    dialogButtons.appendChild(defaultButton);
    dialogButtons.appendChild(closeButton);

    dialog.appendChild(dialogHeader);
    dialog.appendChild(webmColorSetting);
    dialog.appendChild(gifColorSetting);
    dialog.appendChild(dialogButtons);

    // Attach button to header
    header.appendChild(openSettingsButtonContainer);
    // Attach dialog to page
    document.body.appendChild(dialog);
}

// Create user interface for settings and backups
function createUserInterface() {
    'use strict';

    createSettingsInterface();

    // Style everything
    const css = document.createElement('style');
    css.appendChild(document.createTextNode(`
        div.center {
            padding: 0 3em;
            margin: 0;
        }
        .header .center {
            box-sizing: border-box;
            width: 100%;
        }
        h2.siteName {
            height: 48px;
            width: 100px;
            display: inline-flex;
            align-items: center;
        }
        ul.flat-list {
            width: calc(100% - 117px);
        }
        #navbar ul.navbar-nav {
            width: calc(100% - 75px);
        }
        .open-highlighter-dialog {
            display: block;
        }
        @media (min-width: 1225px) {
            .open-highlighter-dialog {
                float: right !important;
            }
        }
        .highlighter-dialog {
            position: fixed;
            top: 0;
            right: -400px;
            z-index: 1003;
            background-color: white;
            box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 3px 1px -2px rgba(0,0,0,0.12), 0 1px 5px 0 rgba(0,0,0,0.2);
            width: 400px;
            max-width: 90vw;
            max-height: 90vh;
            padding: 12px;
            font-size: 12px;
            line-height: 1.42857143;
            box-sizing: border-box;
            transition: right 0.2s cubic-bezier(0,0,0.3,1);
        }
        .highlighter-dialog.open {
            right: 0;
            transition: right 0.25s cubic-bezier(0,0,0.3,1);
        }
        .highlighter-dialog * {
            box-sizing: border-box;
            font-family: verdana, sans-serif;
            line-height: inherit;
        }
        .highlighter-dialog-header {
            all: revert;
        }
        .highlighter-setting-single * {
            cursor: pointer;
        }
        .highlighter-setting-single input {
            padding: revert;
            margin: revert;
        }
        .highlighter-setting-single .highlighter-dialog-text {
            padding-left: 6px;
        }
        .highlighter-setting-single abbr {
            border: none;
            cursor: help;
            color: white;
            font-size: 14px;
        }
        .highlighter-setting-single abbr:after {
            content: '?';
            display: inline-block;
            font-family: sans-serif;
            font-weight: bold;
            text-align: center;
            font-size: 0.8em;
            line-height: 0.8em;
            border-radius: 50%;
            margin-left: 6px;
            padding: 0.13em 0.2em 0.09em 0.2em;
            color: inherit;
            border: 1px solid;
            text-decoration: none;
            background-color: rgb(33, 131, 252);
        }
        .highlighter-setting-single abbr sup {
            cursor: help;
        }
        .highlighter-setting-boolean {
            margin: 8px;
        }
        .highlighter-setting-boolean input[type='checkbox'] {
            appearance: revert;
        }
        .highlighter-dialog-text {
            display: inline-block;
            max-width: 100%;
            margin-bottom: 5px;
            white-space: unset;
        }
        .highlighter-dialog-buttons {
            padding-top: 6px;
        }
        .highlighter-dialog-buttons button {
            margin-right: 6px;
            cursor: pointer;
            font-size: inherit;
            padding: revert;
            appearance: revert;
        }
    `));
    document.head.appendChild(css);
}

(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
            // "More Like This" results, currently in beta, could break, but this code should hypothetically never break the page from this end
            const mltContainer = document.getElementsByClassName('contain-push')[0];
            // Get all image thumbnail links
            const mltLinks = mltContainer && mltContainer.querySelectorAll('a[href*="page=post&s=view"]');
            if (mltLinks) {
                if (displayMoreLikeThisAnimatedTypes) {
                    getAnimatedType(mltLinks);
                }
            }

            const css = document.createElement('style');
            css.appendChild(document.createTextNode(`
                a img.mltThumbs.webm:not(.gif) {
                    padding: 5px;
                    margin: 5px !important;
                    border: 3px solid ` + webmColor + ` !important;
                }
                a img.mltThumbs.gif {
                    padding: 5px;
                    margin: 5px !important;
                    border: 3px solid ` + gifColor + `;
                }
            `));
            document.head.appendChild(css);
        } else if (s === 'list') { // Search page
            // Get search results area
            const galleryContainer = document.querySelector('.thumbnail-container');
            // Get all image thumbnail links
            const galleryLinks = galleryContainer && galleryContainer.querySelectorAll('article.thumbnail-preview a[href*="page=post&s=view"]');

            // Apply borders
            const css = document.createElement('style');
            css.appendChild(document.createTextNode(`
                article.thumbnail-preview {
                    background-color: transparent;
                }
                article.thumbnail-preview a img.webm {
                    padding: 5px;
                    border: 3px solid ` + webmColor + ` !important;
                }
                article.thumbnail-preview a img[title*="animated_gif"],
                article.thumbnail-preview a img[title*="animated_png"],
                article.thumbnail-preview a img[title*="animated "]:not(.webm) {
                    padding: 5px;
                    outline: 3px solid ` + gifColor + `;
                }
            `));
            document.head.appendChild(css);
        }
    } else if (page === 'pool' && s === 'show') { // Pool page
        // Get image thumbnails area
        const galleryContainer = document.querySelector('.thumbnail-container');
        // Get all image thumbnail links
        const galleryLinks = galleryContainer && galleryContainer.querySelectorAll('span a[href*="page=post&s=view"]');
        // Apply borders
        const css = document.createElement('style');
        css.appendChild(document.createTextNode(`
            div.thumbnail-container a img[title*=" webm "] {
                outline: 3px solid ` + webmColor + ` !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: 3px solid ` + gifColor + `;
            }
        `));
        document.head.appendChild(css);
    } else if (page === 'favorites' && s === 'view') { // Favorites page
        // Apply type borders
        const css = document.createElement('style');
        css.appendChild(document.createTextNode(`
            .thumb {
                margin: 5px;
            }
            .thumb a img[title*="animated_gif"],
            .thumb a img[title*="animated_png"],
            .thumb a img[title*="animated "]:not([title*=" webm"]) {
                padding: 5px;
                margin: 5px;
                outline: 3px solid ` + gifColor + `;
            }
            .thumb a img[title*=" webm"] {
                padding: 5px;
                margin: 5px;
                outline: 3px solid ` + webmColor + `;
            }
        `));
        document.head.appendChild(css);
    } else if (page === 'tags' && s === 'saved_search') { // Saved Searches page
        /// Mark visited links
        const galleryLinks = document.querySelectorAll('.container-fluid > .thumb a[href*="page=post&s=view"]');

        // Apply borders
        const css = document.createElement('style');
        css.appendChild(document.createTextNode(`
            .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 ` + gifColor + `;
            }
            .container-fluid > .thumb a .thumbnail-preview[alt*=" webm"] {
                outline: 3px solid ` + webmColor + `;
                margin: 5px 7px;
            }
        `));
        document.head.appendChild(css);
    } else if (page === 'wiki' && !(s === 'list')) { // Wiki entry page
        // Mark visited links
        const galleryLinks = document.querySelectorAll('tr > td:nth-child(2) a[href*="page=post&s=view"]');

        // Apply borders
        const css = document.createElement('style');
        css.appendChild(document.createTextNode(`
            a .thumbnail-preview img[alt*="animated_gif"],
            a .thumbnail-preview img[alt*="animated_png"],
            a .thumbnail-preview img[alt*="animated "]:not([alt*=" webm"]) {
                padding: 5px;
                outline: 3px solid ` + gifColor + `;
            }
            a .thumbnail-preview img[alt*=" webm"] {
                padding: 5px;
                outline: 3px solid ` + webmColor + `;
                margin: 5px 7px;
            }
        `));
        document.head.appendChild(css);
    } else if (page === 'account' && s === 'profile') { // Profile page
        // Apply borders
        const css = document.createElement('style');
        css.appendChild(document.createTextNode(`
            .profileThumbnailPadding {
                max-width: none !important;
            }
            .profileStatisticsBoxContainer {
                z-index: auto;
            }
            a[href*="s=view"] img {
                padding: 5px;
                max-height: 190px;
                width: auto !important;
                max-width: 150px;
                object-fit: scale-down;
            }
            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 ` + gifColor + `;
            }
            a[href*="s=view"] img[alt*=" webm"] {
                outline: 3px solid ` + webmColor + `;
                margin: 5px 7px;
            }
        `));
        document.head.appendChild(css);
    } else if (page === 'comment' && s === 'list') { // Comments page
        // Mark visited links
        const galleryLinks = document.querySelectorAll('#comment-list .post .col1 a[href*="page=post&s=view"]');

        // Apply borders
        const css = document.createElement('style');
        css.appendChild(document.createTextNode(`
            a[href*="s=view"] img {
                position: relative;
                top: -5px;
                left: -5px;
                padding: 5px;
            }
            a[href*="s=view"] img.flagged {
                background-color: #FF0000 !important;
                border: none !important;
            }
            a[href*="s=view"] img[title*="animated_gif"],
            a[href*="s=view"] img[title*="animated_png"],
            a[href*="s=view"] img[title*="animated "]:not([title*=" webm"]) {
                outline: 3px solid ` + gifColor + `;
            }
            a[href*="s=view"] img[title*=" webm"] {
                outline: 3px solid ` + webmColor + `;
            }
        `));
        document.head.appendChild(css);
    }

    createUserInterface();
})();