您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Marks previously visited images on Gelbooru search pages, and extends animation highlighting from webm to also include gifs.
当前为
// ==UserScript== // @name Gelbooru Visited and Type Highlighter // @namespace http://tampermonkey.net/ // @version 11.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 imgUnvisitedColor = '#E1F5FE'; // Color for unvisted images const imgVisitedColor = '#2E7D32'; // Color for visited images const webmUnvisitedColor = '#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; /*-------------------*/ // Tests whether value is in items function inSortedList(items, value) { 'use strict'; function binarySearch(array, value, first, last) { if (first > last) { return false; } const middle = (last + first) >> 1; if (array[middle] === value) { return true; } if (array[middle] > value) { return binarySearch(array, value, first, middle - 1); } else { return binarySearch(array, value, middle + 1, last); } } return binarySearch(items, value, 0, items.length - 1); } // Inserts value in items if not already present, returns whether insertion took place function insertIntoSortedList(items, value) { 'use strict'; let first = 0, last = items.length - 1, middle; while (first <= last) { middle = (last + first) >> 1; if (items[middle] > value) { last = middle - 1; continue; } first = middle + 1; if (items[middle] === value) { return false; } } items.splice(first, 0, value); return true; } // Check if link is in visited list function markIfVisited(galleryLink, visitedIDs) { 'use strict'; const linkURL = new URL(galleryLink.getAttribute('href'), window.location.href); const linkSearchParams = new URLSearchParams(linkURL.search); const id = parseInt(linkSearchParams.get('id')); if (inSortedList(visitedIDs, id)) { galleryLink.classList.add('visited'); } } // Checks all provided links and marks visited if in list function markVisitedLinks(galleryLinks) { 'use strict'; const links = galleryLinks; function applyVisitedToAllLinksInList() { const visitedIDs = JSON.parse(localStorage.getItem('visitedIDs')) || []; links.forEach(link => markIfVisited(link, visitedIDs)); } applyVisitedToAllLinksInList(); // Also mark visited images opened in new tab/windows from this page, or by any other means while this page is open window.addEventListener('storage', applyVisitedToAllLinksInList); } // 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; } // Create interface for history backups function createBackupInterface() { 'use strict'; // Get header const header = document.getElementById('navbar') || document.querySelector('.header .center'); if (!header) { return; } // Create element for header const headerWrapper = document.createElement('ul'); headerWrapper.classList.add('flat-list'); headerWrapper.classList.add('navbar-nav'); headerWrapper.classList.add('nav'); headerWrapper.style = 'float: right'; // Create button const openDialogButtonContainer = document.createElement('li'); const openDialogButton = document.createElement('a'); openDialogButton.textContent = 'Visited History Backups'; openDialogButton.setAttribute('role', 'button'); openDialogButton.href = 'javascript:void(0)'; openDialogButton.onclick = () => { let visitedIDs = localStorage.getItem('visitedIDs') || '[]'; visitedIDs = visitedIDs.slice(0, visitedIDs.length - 1).slice(1); const textArea = document.getElementById('dialog-data-field'); textArea.value = visitedIDs; textArea.select(); document.getElementById('backup-dialog').classList.add('open'); }; openDialogButtonContainer.appendChild(openDialogButton); headerWrapper.appendChild(openDialogButtonContainer); // Create dialog const dialog = document.createElement('div'); dialog.id = 'backup-dialog'; const dialogHeader = document.createElement('h2'); dialogHeader.id = 'dialog-header'; dialogHeader.textContent = 'Back Up Visited Image History'; const dialogText = document.createElement('label'); dialogText.id = 'dialog-text'; dialogText.setAttribute('for', 'dialog-data-field'); dialogText.textContent = 'Copy the content of the text field and save it somewhere. To import a backup, paste in the text field and click "Import" to overwrite the current history or "Merge" to combine them.'; const dialogDataField = document.createElement('textarea'); dialogDataField.id = 'dialog-data-field'; dialogDataField.setAttribute('autocomplete', 'off'); dialogDataField.setAttribute('name', 'dialog-data-field'); dialogDataField.setAttribute('rows', '3'); // Create the buttons const dialogButtons = document.createElement('div'); dialogButtons.id = 'dialog-buttons'; const copyButton = document.createElement('button'); const importButton = document.createElement('button'); const mergeButton = document.createElement('button'); const closeButton = document.createElement('button'); copyButton.id = 'dialog-copy-button'; copyButton.textContent = 'Copy to Clipboard'; copyButton.onclick = async () => { const backupText = document.getElementById('dialog-data-field').value; try { await navigator.clipboard.writeText(backupText); } catch (e) { console.error('Failed to copy', e); } }; importButton.id = 'dialog-import-button'; importButton.textContent = 'Import'; importButton.onclick = () => { const textareaContents = document.getElementById('dialog-data-field').value; if (!(/(^$)|(^[0-9]+(,[0-9]+)*$)/.test(textareaContents))) { document.getElementById('dialog-data-field').value = 'Invalid input'; return false; } const importedIDs = JSON.parse('[' + textareaContents + ']'); importedIDs.sort((a, b) => a - b); localStorage.setItem('visitedIDs', JSON.stringify(importedIDs)); document.getElementById('backup-dialog').classList.remove('open'); }; mergeButton.id = 'dialog-merge-button'; mergeButton.textContent = 'Merge'; mergeButton.onclick = () => { const textareaContents = document.getElementById('dialog-data-field').value; if (!(/(^$)|(^[0-9]+(,[0-9]+)*$)/.test(textareaContents))) { document.getElementById('dialog-data-field').value = 'Invalid input'; return false; } const importedIDs = JSON.parse('[' + textareaContents + ']'); const visitedIDs = JSON.parse(localStorage.getItem('visitedIDs')) || []; const combinedIDs = [...importedIDs, ...visitedIDs]; const mergedIDs = [...new Set(combinedIDs)]; mergedIDs.sort((a, b) => a - b); localStorage.setItem('visitedIDs', JSON.stringify(mergedIDs)); document.getElementById('backup-dialog').classList.remove('open'); }; closeButton.id = 'dialog-close-button'; closeButton.textContent = 'Close'; closeButton.onclick = () => { document.getElementById('backup-dialog').classList.remove('open'); }; if (!!navigator.clipboard) { dialogButtons.appendChild(copyButton); } dialogButtons.appendChild(importButton); dialogButtons.appendChild(mergeButton); dialogButtons.appendChild(closeButton); dialog.appendChild(dialogHeader); dialog.appendChild(dialogText); dialog.appendChild(dialogDataField); dialog.appendChild(dialogButtons); // Style everything const css = document.createElement('style'); css.innerHTML = ` #backup-dialog { position: fixed; top: 0; right: -400px; 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); } #backup-dialog.open { right: 0; transition: right 0.25s cubic-bezier(0,0,0.3,1); } #backup-dialog * { box-sizing: border-box; font-family: verdana, sans-serif; line-height: inherit; } #dialog-header { all: revert; } #dialog-text { display: inline-block; max-width: 100%; margin-bottom: 5px; white-space: unset; } #dialog-data-field { width: 100%; resize: vertical; font-size: inherit; padding: revert; display: revert; } #dialog-data-field:focus { background-color: unset; } #dialog-buttons button { margin-right: 6px; cursor: pointer; font-size: inherit; } `; document.head.appendChild(css); // Attach button to header header.appendChild(headerWrapper); // Attach dialog to page document.body.appendChild(dialog); } (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')); // Add to list of visited images function updateVisitedIDs(event) { const visitedIDs = JSON.parse(localStorage.getItem('visitedIDs')) || []; if (insertIntoSortedList(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) { markVisitedLinks(mltLinks); } const css = document.createElement('style'); css.innerHTML = ` a img.mltThumbs { padding: 5px; margin: 5px !important; outline: 3px solid ` + imgUnvisitedColor + `; background-color: #FFFFFF; } a:visited img.mltThumbs, a.visited img.mltThumbs { outline-color: ` + imgVisitedColor + `; } a:not(.visited):visited img.mltThumbs { background-color: #9E9E9E; } `; document.head.appendChild(css); } else if (s === 'list') { // Search page // 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) { markVisitedLinks(galleryLinks); } } // Apply borders const css = document.createElement('style'); css.innerHTML = ` div.thumbnail-preview { background-color: transparent; } div.thumbnail-preview a img.thumbnail-preview { background-color: #FFFFFF; } div.thumbnail-preview a:not(.visited):visited img.thumbnail-preview { background-color: #9E9E9E; } div.thumbnail-preview a img.thumbnail-preview:not(.webm) { outline: 3px solid ` + imgUnvisitedColor + `; } 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: ` + webmUnvisitedColor + ` !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 image thumbnails area const galleryContainer = document.querySelector('.thumbnail-container'); if (!!galleryContainer) { // Get all image thumbnail links const galleryLinks = galleryContainer.querySelectorAll('span a'); if (!!galleryLinks) { markVisitedLinks(galleryLinks); } } // Apply borders const css = document.createElement('style'); css.innerHTML = ` div.thumbnail-container a img { outline: 3px solid ` + imgUnvisitedColor + `; background-color: #FFFFFF; } div.thumbnail-container a:visited img, div.thumbnail-container a.visited img { outline-color: ` + imgVisitedColor + `; } div.thumbnail-container a:not(.visited):visited img { background-color: #9E9E9E; } div.thumbnail-container a img[title*=" webm "] { outline: 5px solid ` + webmUnvisitedColor + ` !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 ` + webmUnvisitedColor + `; } `; 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"]'); markVisitedLinks(galleryLinks); } css.innerHTML += ` .thumb a img { outline: 3px solid ` + imgUnvisitedColor + `; background-color: #FFFFFF; } .thumb a:visited img, .thumb a.visited img { outline-color: ` + imgVisitedColor + `; } .thumb a:not(.visited):visited img { background-color: #9E9E9E; } .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 /// Mark visited links const galleryLinks = document.querySelectorAll('.container-fluid > .thumb a'); markVisitedLinks(galleryLinks); // Apply borders const css = document.createElement('style'); css.innerHTML = ` .container-fluid > .thumb a .thumbnail-preview { outline: 3px solid ` + imgUnvisitedColor + `; background-color: #FFFFFF; } .container-fluid > .thumb a:visited .thumbnail-preview, .container-fluid > .thumb a.visited .thumbnail-preview { outline-color: ` + imgVisitedColor + `; } .container-fluid > .thumb a:not(.visited):visited .thumbnail-preview { background-color: #9E9E9E; } .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 ` + webmUnvisitedColor + `; 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 // Mark visited links const galleryLinks = document.querySelectorAll('tr > td:nth-child(2) a[href*="s=view"]'); markVisitedLinks(galleryLinks); // Apply borders const css = document.createElement('style'); css.innerHTML = ` a .thumbnail-preview img { padding: 5px; outline: 3px solid ` + imgUnvisitedColor + `; background-color: #FFFFFF; } a:visited .thumbnail-preview img, a.visited .thumbnail-preview img { outline-color: ` + imgVisitedColor + `; } a:not(.visited):visited .thumbnail-preview img { background-color: #9E9E9E; } 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 ` + webmUnvisitedColor + `; 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 // Mark visited links const galleryLinks = document.querySelectorAll('a[href*="s=view"]'); markVisitedLinks(galleryLinks); // 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 ` + imgUnvisitedColor + `; max-height: 190px; object-fit: scale-down; background-color: #FFFFFF; } a[href*="s=view"]:visited img, a[href*="s=view"].visited img { outline-color: ` + imgVisitedColor + `; } a[href*="s=view"]:not(.visited):visited img { background-color: #9E9E9E; } 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 ` + webmUnvisitedColor + `; 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); } createBackupInterface(); })();