// ==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);
}
})();