// ==UserScript==
// @name Gelbooru Visited and Type Highlighter
// @namespace http://tampermonkey.net/
// @version 9.1.1
// @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 = false;
/*-------------------*/
// 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 + '")');
}
// Check if link is in visited list
function markIfVisited(galleryLink, visitedIDs) {
let linkURL = new URL(galleryLink.getAttribute('href'), window.location.href);
let linkSearchParams = new URLSearchParams(linkURL.search);
let id = parseInt(linkSearchParams.get('id'));
if (visitedIDs.includes(id)) {
galleryLink.classList.add('visited');
}
}
// 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 ? parseInt(userID) : -1;
}
(function() {
'use strict';
// Create visited image database if doesn't exist
if (localStorage.getItem('visitedIDs') === null) {
localStorage.setItem('visitedIDs', JSON.stringify([]));
}
// Get list of visited images
let visitedIDs = JSON.parse(localStorage.getItem('visitedIDs'));
// Find out what kind of page we're on
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);
if (visitedIDs.length > 0) {
markIfVisited(galleryLink, visitedIDs);
}
});
}
}
// 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,
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 (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);
// Add to visited database
let currentURLSearchParams = new URLSearchParams(url.search);
let id = parseInt(currentURLSearchParams.get('id'));
if (!visitedIDs.includes(id)) {
visitedIDs.push(id);
localStorage.setItem('visitedIDs', JSON.stringify(visitedIDs));
}
// Causes the visited highlighting to update immediately in the original page this was opened from (or any other page) if opened in new tab or window
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 => markIfVisited(mltLink, visitedIDs));
}
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,
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);
if (visitedIDs.length > 0) {
markIfVisited(galleryLink, visitedIDs);
}
});
}
}
// Apply borders
let 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 (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') && parseInt(searchParams.get('id')) != userID) {
// Mark visited links
if (visitedIDs.length > 0) {
let galleryLinks = document.querySelectorAll('.thumb a');
galleryLinks.forEach(galleryLink => markIfVisited(galleryLink, visitedIDs));
}
css.innerHTML = 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 (searchParams.get('s') === 'saved_search' && searchParams.get('page') === 'tags') { // Saved Searches page
// Mark visited links
if (visitedIDs.length > 0) {
let galleryLinks = document.querySelectorAll('.container-fluid > .thumb a');
galleryLinks.forEach(galleryLink => markIfVisited(galleryLink, visitedIDs));
}
// 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,
.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 (searchParams.get('s') === 'view' && searchParams.get('page') === 'wiki') { // Wiki entry page
// Mark visited links
if (visitedIDs.length > 0) {
let galleryLinks = document.querySelectorAll('tr > td:nth-child(2) a[href*="s=view"]');
galleryLinks.forEach(galleryLink => markIfVisited(galleryLink, visitedIDs));
}
// Apply borders
let 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);
}
})();