// ==UserScript==
// @name Gelbooru Animation Highlighter
// @namespace http://tampermonkey.net/
// @version 14.13.2
// @description Make webm highlighting work in many more places and mark animated gif as well.
// @author Xerodusk
// @homepage https://greasyfork.org/en/users/460331-xerodusk
// @license GPL-3.0-or-later
// @match https://gelbooru.com/index.php*page=post*s=list*
// @match https://gelbooru.com/index.php*page=post*s=view*
// @match https://gelbooru.com/index.php*page=pool*s=show*
// @match https://gelbooru.com/index.php*page=favorites*s=view*
// @match https://gelbooru.com/index.php*page=tags*s=saved_search*
// @match https://gelbooru.com/index.php*page=wiki*s=view*
// @match https://gelbooru.com/index.php*page=account*s=profile*
// @match 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) {
const event = new Event('tagsretrieve');
let retrieved = galleryLinks.length;
function checkImage(link) {
const linkURL = new URL(link.href, window.location.href).href;
fetch(linkURL).then(response => response.text()).then((responseText) => {
const parser = new DOMParser();
const htmlDoc = parser.parseFromString(responseText, 'text/html');
const sideLinks = htmlDoc.querySelectorAll('ul#tag-list > li:not([class^="tag-type"]) > a');
// Get tags for possible later use
const tagsEditField = htmlDoc.querySelector('textarea#tags.tagBox[name="tags"]');
const tags = tagsEditField.value;
// Get rating for possible later use
const ratingEditField = htmlDoc.querySelector('input[name="rating"][checked]');
const ratingAbbrv = ratingEditField.value;
let rating = '';
switch (ratingAbbrv) {
case 'e': rating = 'explicit'; break;
case 'q': rating = 'questionable'; break;
case 's': rating = 'sensitive'; break;
case 'g': rating = 'general'; break;
default: break;
}
// Get score for possible later use
const linkSearchParams = new URLSearchParams(new URL(linkURL).search);
const linkID = linkSearchParams.get('id');
const score = htmlDoc.getElementById('psc' + linkID).textContent;
const image = link.querySelector('img');
image.dataset.tags = tags + ' score:' + score + ' rating:' + rating;
retrieved--;
if (retrieved === 0) {
window.dispatchEvent(event);
}
for (let i = 0, len = sideLinks.length; i < len; i++) {
if (sideLinks[i].textContent === 'Original image') {
const imageURL = sideLinks[i].href;
if (imageURL.endsWith('.webm')) {
if (image) {
image.classList.add('webm');
}
} else if (imageURL.endsWith('.gif') || tags.split(' ').includes('animated')) {
if (image) {
image.classList.add('gif');
}
}
break;
}
}
});
}
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);
// 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
window.typeHighlighterInstalled = true;
// "More Like This" results, currently in beta, could break, but this code should hypothetically never break the page from this end
const mltLinks = document.querySelectorAll('.mainBodyPadding > div > a[href*="page=post&s=view"]');
if (mltLinks) {
if (displayMoreLikeThisAnimatedTypes) {
getAnimatedType(mltLinks);
}
}
const css = document.createElement('style');
css.appendChild(document.createTextNode(`
a[href*="page=post&s=view"] img.webm:not(.gif) {
padding: 3px;
border: 3px solid ` + webmColor + ` !important;
}
a[href*="page=post&s=view"] img.gif {
padding: 3px;
border: 3px solid ` + gifColor + `;
}
`));
document.head.appendChild(css);
} else if (s === 'list') { // Search page
// Apply borders
const css = document.createElement('style');
css.appendChild(document.createTextNode(`
article.thumbnail-preview {
background-color: transparent;
width: auto;
margin: 20px 10px 0 10px;
}
article.thumbnail-preview a img.webm {
box-sizing: content-box;
padding: 3px;
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) {
box-sizing: content-box;
padding: 3px;
outline: 3px solid ` + gifColor + `;
}
`));
document.head.appendChild(css);
}
} else if (page === 'pool' && s === 'show') { // Pool page
// Apply borders
const css = document.createElement('style');
css.appendChild(document.createTextNode(`
.thumbnail-preview {
padding: 3px;
}
div.thumbnail-container a img[title*=" video "] {
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*=" video "]) {
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 "] {
padding: 3px;
margin: 5px 0 3px 0;
outline: 3px solid ` + gifColor + `;
}
.thumb a img[title*=" video "],
.thumb a img[title$=" video"] {
padding: 3px;
margin: 5px 0 3px 0;
outline: 3px solid ` + webmColor + `;
}
`));
document.head.appendChild(css);
} else if (page === 'tags' && s === 'saved_search') { // Saved Searches page
// Apply borders
const css = document.createElement('style');
css.appendChild(document.createTextNode(`
.thumbnail-container > .thumbnail-preview a img {
padding: 3px;
}
.thumbnail-container > .thumbnail-preview a img[alt*="animated_gif"],
.thumbnail-container > .thumbnail-preview a img[alt*="animated_png"],
.thumbnail-container > .thumbnail-preview a img[alt*="animated "] {
outline: 3px solid ` + gifColor + `;
}
.thumbnail-container > .thumbnail-preview a img[alt*=" video "],
.thumbnail-container > .thumbnail-preview a img[alt$=" video"] {
outline: 3px solid ` + webmColor + `;
}
`));
document.head.appendChild(css);
} else if (page === 'wiki' && !(s === 'list')) { // Wiki entry page
// 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 "] {
padding: 3px;
outline: 3px solid ` + gifColor + `;
}
a .thumbnail-preview img[alt*=" video "],
a .thumbnail-preview img[alt$=" video"] {
padding: 3px;
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: 3px;
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 "] {
outline: 3px solid ` + gifColor + `;
}
a[href*="s=view"] img[alt*=" video "] {
outline: 3px solid ` + webmColor + `;
}
`));
document.head.appendChild(css);
} else if (page === 'comment' && s === 'list') { // Comments page
// Apply borders
const css = document.createElement('style');
css.appendChild(document.createTextNode(`
a[href*="s=view"] img {
position: relative;
top: -3px;
left: -3px;
padding: 3px;
}
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 "] {
outline: 3px solid ` + gifColor + `;
}
a[href*="s=view"] img[title*=" video "],
a[href*="s=view"] img[title$=" video"] {
outline: 3px solid ` + webmColor + `;
}
`));
document.head.appendChild(css);
}
createSettingsInterface();
})();