您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Display some soyjak.party images differently according to file name (MD5)
// ==UserScript== // @name Third Eye - 'Sharty Port // @description Display some soyjak.party images differently according to file name (MD5) // @author Originally by Cunny Software Solutions // @include https://*.soyjak*.party/* // @match https://soyjak.party/* // @namespace ThirdEyeSharty // @match https://www.soyjak.party/* // @license Public Domain // @version 22 // @grant GM.xmlHttpRequest // @grant GM_xmlHttpRequest // @grant unsafeWindow // ==/UserScript== // /* PLEASE REPORT BUGS & ISSUES @ https://git.coom.tech/cunnysoft/third-eye/issues */ /* You can also ask for help at /cumg/: https://boards.4channel.org/g/catalog#s=cumg */ // /*eslint curly:0*/ (function() { "use strict" // /* Added in v8, used to clear the cache on updates. Only bump when the cache needs to be cleared */ // const THIRD_EYE_VERSION = 10 // /* Enable or disable sources here */ // const SOURCE_LIST = [ {name: "gelbooru", url: "https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1&tags=md5:", width: "sample_width", height: "sample_height"}, {name: "yande.re", url: "https://yande.re/post.json?tags=md5%3A", width: "sample_width", height: "sample_height"}, {name: "sankaku", url: "https://capi-v2.sankakucomplex.com/posts/keyset?tags=md5:", width: "sample_width", height: "sample_height"}, {name: "rule34", url: "https://api.rule34.xxx/index.php?page=dapi&s=post&q=index&json=1&tags=md5:", width: "sample_width", height: "sample_height"}, ] // /* Set to true to color Third Eye links */ // const COLOR_THIRD_EYE_LINKS = false // /* Any color */ // const THIRD_EYE_LINK_COLOR = "deeppink" // /* Set to false to disallow images from git.coom.tech/cunnysoft/oekaki */ // const ENABLE_OEKAKI = true // /* Set to false to disallow explicit images */ // const ALLOW_EXPLICIT = true // /* BLACKLIST: Uncomment one or add your own tags */ //const BLACKLIST = [] const BLACKLIST = ["scat"] //const BLACKLIST = ["male_focus", "scat", "penis", "cum"] // /* THUMBNAIL SIZE */ // const THUMBNAIL_SIZE = "125px" const BIG_THUMBNAIL_SIZE = "250px" // /* Change at your own peril */ // const PRESERVE_THUMBNAIL_SIZE = false // /* Options for when to use the source's preview image instead of the full image */ /* Some of these settings may interfere with PRESERVE_THUMBNAIL_SIZE */ // const USE_PREVIEW_IMAGE_IN_OP = false // preview is often less than 250px, threatening loss of quality const USE_PREVIEW_IMAGE_IN_REPLIES = true // preview is usually the right size // /* Avoid touching things below this line */ // const XMLHttpRequest = (GM && GM.xmlHttpRequest) ?? GM_xmlHttpRequest const THIRD_EYE_CACHE_PREFIX = "__thirdeye" const THIRD_EYE_MD5_CACHE_PREFIX = "__thirdeye_md5__" const THIRD_EYE_MISC_CACHE_PREFIX = "__thirdeye_misc__" const must = (map, key) => { if (map.has(key)) return map.get(key) else console.error("must() failed:", key) } const quote = (string) => { return "'" + string + "'" } { const clear_cache = () => { for (const x in localStorage) { if (x.startsWith(THIRD_EYE_CACHE_PREFIX)) localStorage.removeItem(x) } } const _THIRD_EYE_VERSION = localStorage.getItem(THIRD_EYE_MISC_CACHE_PREFIX+"version") if (!_THIRD_EYE_VERSION) { // _THIRD_EYE_VERSION === null console.warn("third eye: No version in cache, so clearing cache.") clear_cache() } else if (_THIRD_EYE_VERSION < THIRD_EYE_VERSION) { // _THIRD_EYE_VERSION is a string like "8" console.warn("third eye: Version in cache is outdated (" + _THIRD_EYE_VERSION + " < " + THIRD_EYE_VERSION + "), so clearing cache.") clear_cache() } localStorage.setItem(THIRD_EYE_MISC_CACHE_PREFIX+"version", THIRD_EYE_VERSION) } for (const element of document.getElementsByClassName("fileThumb")) element.__thirdeye_prepared = undefined const whitelisted = (json) => { // sankaku if (typeof(json.tags) === "object") { for (const tag of BLACKLIST) { for (const tag2 in json.tags) { if (tag2.name_en === tag) return false } } } else { for (const tag of BLACKLIST) { if ((json.tag_string_general || json.tags).split(" ").includes(tag)) return false else if ((json.tag_string_character || "").split(" ").includes(tag)) return false } } return ALLOW_EXPLICIT || json.rating !== "e" } const whitelisted_log = (json, md5sum) => { const res = whitelisted(json) if (!res) console.info("third eye: skipped md5 " + md5sum + " due to one or more blacklisted tags") return res } const _4cdn_image = (url) => { return url.startsWith("https://i.4cdn.org/") || url.startsWith("https://s.4cdn.org/") } const _4cdn_full_image_cache = new Map() const with_4cdn_full_image = (url, func) => { if (_4cdn_full_image_cache.has(url)) { func(_4cdn_full_image_cache.get(url)) } else { const img = document.createElement("img") img.src = url.replace("s.jpg", ".jpg") img.onerror = () => { img.src = url.replace("s.jpg", ".png") img.onerror = () => { img.src = url.replace("s.jpg", ".gif") img.onerror = () => { img.src = url.replace("s.jpg", ".webm") } } } img.onload = () => { _4cdn_full_image_cache.set(url, img.src) func(img.src) } } } const blob_cache = new Map() const r_blob_cache = new Map() const with_blob = (image, func) => { if (blob_cache.has(image)) { func(blob_cache.get(image)) } else XMLHttpRequest({ method: "GET", url: image, responseType: "blob", onload: (response) => { const bimg = URL.createObjectURL(response.response) blob_cache.set(image, bimg) r_blob_cache.set(bimg, image) func(bimg) } }) } const add_events = (element, image) => { const href0 = element.href const href1 = () => { if (_4cdn_image(href0)) return href0 else return element.href } element.href = image // TODO remove | try to fix race condition setTimeout(() => { element.href = image }, 700) // event handling if (element.__thirdeye_observer) { console.debug("third eye: disconnecting observer") element.__thirdeye_observer.disconnect() element.__thirdeye_observer = null // futureproofing } const config = { attributes: false, childList: true, subtree: false } const callback = function(mutationsList, observer) { if (element.children[1]?.className === "full-image") element.children[1].style.visibility = "hidden" } const observer = new MutationObserver(callback) observer.observe(element, config) element.__thirdeye_observer = observer element.addEventListener("click", (e) => { setTimeout(() => { const image2 = (element.parentElement.querySelector(".third-eye-swap-button")?.__thirdeye_show_original) ? href1() : image with_blob(image2, (bimg) => { element.href = image2 if (element.children[1]) element.children[1].style.visibility = "visible" if (element.children[1]?.className === "full-image") { element.children[1].src = bimg } }) }, 4) }) // video thumbnails don't load and break the hoverUI if (!element.href.endsWith(".mp4") && !element.href.endsWith(".webm")) { element.children[0].addEventListener("mouseover", (e) => { const handle_4chanX_hoverUI = (currentCB) => { const hoverUI = document.getElementById("hoverUI") if (!hoverUI || hoverUI.children.length === 0) { if (currentCB < 125) setTimeout(() => handle_4chanX_hoverUI(currentCB+1), 16) // DO NOT set this to below 16, it breaks else { if (hoverUI) hoverUI.style.visibility = "visible" console.warn("third eye: intercepting 4chanX HoverUI took too long. If 4chanX is not installed or hover is turned off, ignore this warning.") } } else { const child = hoverUI.children[0] const image2 = (element.parentElement.querySelector(".third-eye-swap-button")?.__thirdeye_show_original) ? href1() : image with_blob(image2, (bimg) => { child.src = bimg const img = document.createElement("img") img.src = bimg img.onerror = () => { console.error("Hover: Error loading img with src", quote(img.src)) hoverUI.style.visibility = "visible" } img.onload = () => { const doc = document.documentElement let width = img.width let height = img.height const maxWidth = doc.clientWidth const maxHeight = doc.clientHeight - 25 const scale = Math.min(1, maxWidth / width, maxHeight / height) width *= scale height *= scale width = Math.floor(width) height = Math.floor(height) child.src = bimg child.style.width = width + "px" child.style.height = height + "px" child.style.maxWidth = width + "px" child.style.maxHeight = height + "px" child.style.top = Math.max(0, e.clientY * (doc.clientHeight - height) / doc.clientHeight) + "px" hoverUI.style.visibility = "visible" // event handling if (child.__thirdeye_observer) { //console.debug("third eye: disconnecting observer") child.__thirdeye_observer.disconnect() child.__thirdeye_observer = null // futureproofing } const config = { attributes: true, childList: false, subtree: false } const callback = function(mutationsList, observer) { // Use traditional 'for loops' for IE 11 for (const mutation of mutationsList) { child.style.top = Math.max(0, e.clientY * (doc.clientHeight - height) / doc.clientHeight) + "px" break } } const observer = new MutationObserver(callback) observer.observe(child, config) child.__thirdeye_observer = observer } }) } } const hoverUI = document.getElementById("hoverUI") if (hoverUI) hoverUI.style.visibility = "hidden" setTimeout(() => handle_4chanX_hoverUI(0), 4) }) } } const thumbnail_cache = new Map() const thumbnailize = (url, preview_url_or_undefined, md5sum, is_OP) => { const work = (thumbnail) => { thumbnail_cache.set(thumbnail, url) return thumbnail } if (!USE_PREVIEW_IMAGE_IN_REPLIES || (is_OP && !USE_PREVIEW_IMAGE_IN_OP)) return url else if (url.startsWith("https://img1.gelbooru.com/")) return work("https://img1.gelbooru.com/thumbnails/" + md5sum[0] + md5sum[1] + "/" + md5sum[2] + md5sum[3] + "/thumbnail_" + md5sum + ".jpg") else if (url.startsWith("https://img2.gelbooru.com/")) return work("https://img2.gelbooru.com/thumbnails/" + md5sum[0] + md5sum[1] + "/" + md5sum[2] + md5sum[3] + "/thumbnail_" + md5sum + ".jpg") else if (url.startsWith("https://img3.gelbooru.com/")) return work("https://img3.gelbooru.com/thumbnails/" + md5sum[0] + md5sum[1] + "/" + md5sum[2] + md5sum[3] + "/thumbnail_" + md5sum + ".jpg") else return preview_url_or_undefined ? work(preview_url_or_undefined) : url } // loading function const request_source = (element, md5sum, thumbnail_size, is_OP) => { const callback = (i) => { const source = SOURCE_LIST[i] XMLHttpRequest({ method: "GET", url: source.url+md5sum, responseType: "json", onload: (response) => { // NOTE response.response should always exist, but this doesn't hurt // https://git.coom.tech/cunnysoft/third-eye/pulls/1 let json = response?.response ?? JSON.parse(response?.responseText || "{}"); if (json && json.data !== undefined) // yande.re json = json.data // typeof(null) === "object" // danbooru: json[0] // gelbooru: json.post OR json.post[0]. Changed from json[0] on jan 2 2022 // yande.re: json[0] // sankaku: json.data[0] // rule34: json[0] // lolibooru: json[0] json = (json && typeof(json) == "object") ? (json.post || json[0]) : json json = (json && typeof(json) == "object") ? (json[0] || json) : json // gelbooru exception if (json && json.file_url && whitelisted_log(json, md5sum)) { const child = element.children[0] const thumbnail = {width: json[source.width], height: json[source.height]} localStorage.setItem(THIRD_EYE_MD5_CACHE_PREFIX+md5sum, JSON.stringify({url: json.file_url, preview_url: json.preview_url /* yande.re, sankaku, lolibooru, rule34 */ || json.preview_file_url, // danbooru (not always available?) width: thumbnail.width, height: thumbnail.height, rating: json.rating, tags: json.tags, tag_string_general: json.tag_string_general, tag_string_character: json.tag_string_character})) child.setAttribute("referrerpolicy", "no-referrer") cache_src(json.file_url, child.src) set_element_src(child, thumbnailize(json.file_url, json.preview_url, md5sum, is_OP)) if (PRESERVE_THUMBNAIL_SIZE) { child.style.width = thumbnail.width+"px" child.style.height = thumbnail.height+"px" } else if (thumbnail.width > thumbnail.height) { child.style.width = thumbnail_size child.style.height = "auto" } else { child.style.height = thumbnail_size child.style.width = "auto" } add_events(element, json.file_url) console.debug("third eye: registering a new file (" + source.name + ")") } else if (i < SOURCE_LIST.length-1) callback(i+1) else console.debug("third eye: couldn't find anything for md5 " + md5sum) } }) } callback(0) } const src_cache = new Map() const cache_src = (image1, image2) => { if (!src_cache.has(image1)) src_cache.set(image1, image2) if (!src_cache.has(image2)) src_cache.set(image2, image1) } const set_element_src = (element, image) => { cache_src(element.src, image) element.src = image } const swap_element_src = (element) => { element.src = must(src_cache, element.src) } const swap_element_src_then = (element, image, func) => { with_blob(image, (bimg) => { element.src = bimg func() }) } let time = performance.now() // main function const main = () => { // re-assign download href whenever it changes (4chanX) // also add the swap button for (const downloader of document.getElementsByClassName("download-button")) { const fileThumb = downloader.parentElement.parentElement.parentElement.querySelector(".fileThumb") if (downloader.__thirdeye_original_href === undefined) downloader.__thirdeye_original_href = downloader.href if (!_4cdn_image(fileThumb.href)) { downloader.href = fileThumb.href downloader.__thirdeye_replaced_href = downloader.href } if (!downloader.parentElement.querySelector(".third-eye-swap-button") && !_4cdn_image(fileThumb.children[0].src)) { let image1 = fileThumb.children[0].src const button = document.createElement("a") // swap image button button.className = "fa fa-eye third-eye-swap-button" button.innerHTML = "" button.style.marginLeft = "4px" button.style.textDecoration = "none" button.href = "javascript:void(null);" button.__thirdeye_show_original = false button.onclick = () => { if (button.className == "fa fa-eye third-eye-swap-button") button.className = "fa fa-eye third-eye-swap-button disabled" else button.className = "fa fa-eye third-eye-swap-button" image1 = must(src_cache, image1) const thumbnail = downloader.parentElement.parentElement.parentElement.querySelector(".fileThumb").children[0] swap_element_src_then(thumbnail, image1, () => { button.__thirdeye_show_original = !button.__thirdeye_show_original const swap = (a, b, c) => { if (a == b) return c else return b } downloader.href = swap(downloader.href, downloader.__thirdeye_original_href, downloader.__thirdeye_replaced_href) fileThumb.href = downloader.href // only option to get the original 4chan image in replies const img = document.createElement("img") img.src = thumbnail.src img.onerror = () => { console.error("Swap1: Error loading img with src", quote(img.src)) } img.onload = () => { // fix dimensions of alt images const child = fileThumb.children[0] if (img.width > img.height) { child.style.width = fileThumb.closest(".opContainer") ? BIG_THUMBNAIL_SIZE : THUMBNAIL_SIZE child.style.height = "auto" } else { child.style.height = fileThumb.closest(".opContainer") ? BIG_THUMBNAIL_SIZE : THUMBNAIL_SIZE child.style.width = "auto" } } }) } downloader.parentElement.insertBefore(button, downloader.nextSibling) } } // 4chanX (fnswitch does not exist otherwise) if (COLOR_THIRD_EYE_LINKS) { for (const element of document.getElementsByClassName("fnswitch")) { if (!_4cdn_image(element.parentElement.parentElement.parentElement.parentElement.querySelector(".fileThumb").href)) element.style.color = THIRD_EYE_LINK_COLOR } } // for replies for (const button of document.getElementsByClassName("third-eye-swap-button")) { if (button.__thirdeye_show_original === undefined) { button.__thirdeye_show_original = false // 4chanX copies the replaced image so make sure the icon is right button.className = "fa fa-eye third-eye-swap-button" } if (button.onclick === null) { let image1 = undefined // wait for thumbnail to be replaced button.onclick = () => { if (button.className == "fa fa-eye third-eye-swap-button") button.className = "fa fa-eye third-eye-swap-button disabled" else button.className = "fa fa-eye third-eye-swap-button" button.__thirdeye_show_original = !button.__thirdeye_show_original const downloader = button.parentElement.querySelector(".download-button") const fileThumb = downloader.parentElement.parentElement.parentElement.querySelector(".fileThumb") const thumbnail = fileThumb.children[0] if (image1 === undefined) { image1 = fileThumb.children[0].src image1 = r_blob_cache.get(image1) || image1 } image1 = must(src_cache, image1) swap_element_src_then(thumbnail, image1, () => { // only option to get the original 4chan image in replies const img = document.createElement("img") img.src = thumbnail.src img.onerror = () => { console.error("Swap2: Error loading img with src", quote(img.src)) } img.onload = () => { if (_4cdn_image(image1)) { with_4cdn_full_image(image1, (image2) => { downloader.href = image2 fileThumb.href = image2 }) } else { downloader.href = thumbnail_cache.get(image1) || image1 fileThumb.href = downloader.href } // fix dimensions of alt images const child = fileThumb.children[0] if (img.width > img.height) { child.style.width = fileThumb.closest(".opContainer") ? BIG_THUMBNAIL_SIZE : THUMBNAIL_SIZE child.style.height = "auto" } else { child.style.height = fileThumb.closest(".opContainer") ? BIG_THUMBNAIL_SIZE : THUMBNAIL_SIZE child.style.width = "auto" } } }) } } } let it = 0 for (const element of document.getElementsByClassName("fileThumb")) { // the last is necessary for those that don't have an extended filename const text_field = element.parentElement.children[0].children[0].title || // vanilla 4chan element.parentElement.children[0].children[0].text || // vanilla 4chan (truncated filename...) element.parentElement.children[0].children[0].children[0]?.children[0]?.children[1].innerText || // 4chanX element.parentElement.children[0].children[0].children[0]?.text /* 4chanX */ || "" const field_prefix = text_field.split(".")[0] const is_valid_MD5 = (x) => { const regexpr = /^[a-f0-9]{32}$/gi return regexpr.test(x) } const skip_MD5 = (element, maybe_md5sum) => { try { const md5 = Array.from(atob(element.children[0].dataset.md5), aChar => ('0' + aChar.charCodeAt(0).toString(16)).slice(-2)).join('') if (md5 === maybe_md5sum) { console.debug("third eye: Skipping md5sum " + md5 + " because it is the same as the image MD5") return true } else return false } catch (error) { console.error("third eye: skip_MD5 error: " + error + " (md5=" + maybe_md5sum + ")") return true } } // field_prefix !== "" for deleted files it++ if (element.__thirdeye_prepared === undefined && field_prefix !== "" && !skip_MD5(element, field_prefix) && (is_valid_MD5(field_prefix) || (ENABLE_OEKAKI && (field_prefix.startsWith("oekaki--") || field_prefix.startsWith("oekakijpg--"))))) { element.__thirdeye_prepared = true // TODO const is_OP = (it === 1 || element.closest(".opContainer")) const thumbnail_size = is_OP ? BIG_THUMBNAIL_SIZE : THUMBNAIL_SIZE let item = localStorage.getItem(THIRD_EYE_MD5_CACHE_PREFIX+field_prefix) if (item === null) { if (field_prefix.startsWith("oekaki--")) { console.info("third eye: using oekaki (git.coom.tech/cunnysoft/oekaki) for file prefix " + field_prefix) const child = element.children[0] child.setAttribute("referrerpolicy", "no-referrer") set_element_src(child, "https://git.coom.tech/cunnysoft/oekaki/raw/branch/%e3%82%bb%e3%82%af%e3%82%b7%e3%83%bc%e3%81%aa%e5%a5%b3%e3%81%ae%e5%ad%90/" + field_prefix.split("oekaki--")[1] + ".png") child.onerror = () => { console.error("Main1: Error loading img with src", quote(child.src)) } child.onload = () => { if (child.width > child.height) { child.style.width = thumbnail_size child.style.height = "auto" } else { child.style.height = thumbnail_size child.style.width = "auto" } } // we assume width:height ratio is the same for now add_events(element, child.src) // although .jpg files are not planned at the moment we'd like to make sure older clients support them in case they ever are } else if (field_prefix.startsWith("oekakijpg--")) { console.info("third eye: using oekaki jpg (git.coom.tech/cunnysoft/oekaki) for file prefix " + field_prefix) const child = element.children[0] child.setAttribute("referrerpolicy", "no-referrer") set_element_src(child, "https://git.coom.tech/cunnysoft/oekaki/raw/branch/%e3%82%bb%e3%82%af%e3%82%b7%e3%83%bc%e3%81%aa%e5%a5%b3%e3%81%ae%e5%ad%90/" + field_prefix.split("oekakijpg--")[1] + ".jpg") child.onerror = () => { console.error("Main2: Error loading img with src", quote(child.src)) } child.onload = () => { if (child.width > child.height) { child.style.width = thumbnail_size child.style.height = "auto" } else { child.style.height = thumbnail_size child.style.width = "auto" } } // we assume width:height ratio is the same for now add_events(element, child.src) } else { const md5sum = field_prefix console.info("third eye: found valid md5 " + md5sum + ", running query") request_source(element, md5sum, thumbnail_size, is_OP) } } else { const md5sum = field_prefix item = JSON.parse(item) if (whitelisted_log(item, md5sum)) { console.debug("third eye: reusing cached file for md5 " + md5sum) const child = element.children[0] child.setAttribute("referrerpolicy", "no-referrer") cache_src(item.url, child.src) set_element_src(child, thumbnailize(item.url, item.preview_url, md5sum, is_OP)) if (PRESERVE_THUMBNAIL_SIZE) { child.style.width = item.width+"px" child.style.height = item.height+"px" } else if (item.width > item.height) { child.style.width = thumbnail_size child.style.height = "auto" } else { child.style.height = thumbnail_size child.style.width = "auto" } add_events(element, item.url) } } } else { if (((performance.now() - time)/1000) >= 30) { time = performance.now() console.info("third eye: Skipping element with assigned 'prepared' field/invalid MD5 (delayed next log)") } } } } main() // required if no 4chanX // event handling const config = { attributes: false, childList: true, subtree: true } const callback = function(mutationsList, observer) { // Use traditional 'for loops' for IE 11 for (const mutation of mutationsList) { main() break } } const observer = new MutationObserver(callback) observer.observe(document.getElementsByClassName("thread")[0], config) })() if (typeof exportFunction === "function") { const THIRD_EYE_CACHE_PREFIX = "__thirdeye" const THIRD_EYE_MD5_CACHE_PREFIX = "__thirdeye_md5__" const THIRD_EYE_MISC_CACHE_PREFIX = "__thirdeye_misc__" // exports for the client const clear_third_eye_cache = () => { for (const x in localStorage) { if (x.startsWith(THIRD_EYE_CACHE_PREFIX)) localStorage.removeItem(x) } } const display_third_eye_cache = () => { for (const x in localStorage) { if (x.startsWith(THIRD_EYE_CACHE_PREFIX)) console.info(x + " = " + localStorage[x]) } } unsafeWindow.clear_third_eye_cache = exportFunction(clear_third_eye_cache, unsafeWindow) unsafeWindow.display_third_eye_cache = exportFunction(display_third_eye_cache, unsafeWindow) // more searchable unsafeWindow.third_eye_clear_cache = exportFunction(clear_third_eye_cache, unsafeWindow) unsafeWindow.third_eye_display_cache = exportFunction(display_third_eye_cache, unsafeWindow) unsafeWindow.third_eye_version = exportFunction(() => localStorage.getItem(THIRD_EYE_MISC_CACHE_PREFIX+"version")) } else { const THIRD_EYE_CACHE_PREFIX = "__thirdeye" const THIRD_EYE_MD5_CACHE_PREFIX = "__thirdeye_md5__" const THIRD_EYE_MISC_CACHE_PREFIX = "__thirdeye_misc__" // exports for the client clear_third_eye_cache = (() => { for (const x in localStorage) { if (x.startsWith(THIRD_EYE_CACHE_PREFIX)) localStorage.removeItem(x) } }) // more searchable third_eye_clear_cache = clear_third_eye_cache display_third_eye_cache = (() => { for (const x in localStorage) { if (x.startsWith(THIRD_EYE_CACHE_PREFIX)) console.info(x + " = " + localStorage[x]) } }) // more searchable third_eye_display_cache = display_third_eye_cache third_eye_version = (() => { return localStorage.getItem(THIRD_EYE_MISC_CACHE_PREFIX+"version") }) }