您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Blur images on Komica
// ==UserScript== // @name Komica Blur // @description Blur images on Komica // @namespace https://github.com/usausausausak // @match https://gita.komica1.org/00b/* // @version 0.2a // @require https://cdn.jsdelivr.net/gh/usausausausak/pixelmatch@6abc46852cdfe64e8b7005d6e01b91d0451620b9/index.js // @grant GM.setValue // @grant GM.getValue // @grant GM.addStyle // ==/UserScript== const Komica = {}; (function komicaDialog(exports) { 'use strict' const TAG = '[Komica_Dialog]'; function insertDialog(name, id, namespace) { // WORKAROUND: GM4 double insert if (document.querySelector(`#${id}`)) { return; } const tabBox = createTabBox(namespace); function toggleDialog() { dialog.classList.toggle(`${namespace}-dialog-show`); if (dialog.classList.contains(`${namespace}-dialog-show`)) { tabBox.currentSelected = 0; } } const dialog = document.createElement('div'); dialog.id = id; dialog.className = `${namespace}-dialog`; tabBox.appendTo(dialog); const footer = document.createElement('div'); footer.className = `${namespace}-dialog-footer`; dialog.appendChild(footer); const closeBut = document.createElement('button'); closeBut.className = `${namespace}-dialog-close-button`; closeBut.innerHTML = '關閉'; closeBut.addEventListener('click', toggleDialog, false); dialog.appendChild(closeBut); document.body.insertBefore(dialog, document.body.firstChild); // Insert toggle button to top links area. const toggleButton = document.createElement('a'); toggleButton.className = 'text-button'; toggleButton.innerHTML = name; toggleButton.addEventListener('click', toggleDialog, false); const anchor = document.querySelector('#toplink a:last-of-type'); const parent = anchor.parentElement; const insertPoint = anchor.nextSibling; parent.insertBefore(document.createTextNode('] ['), insertPoint); parent.insertBefore(toggleButton, insertPoint); return { tabBox, footer }; } function createTabBox(namespace) { const eventListener = { onswitch: [] }; function addEventListener(name, cb) { if (!eventListener[name]) { // ignore unknown event return; } if (typeof cb === 'function') { eventListener[name].push(cb); } else { console.warn(TAG, 'event listener not a function'); } } function emitEvent(name, ...args) { try { eventListener[name].forEach(cb => cb(...args)); } catch (e) { console.error(TAG, e); } } const tabBox = document.createElement('div'); tabBox.className = `${namespace}-tabbox-header`; const pageBox = document.createElement('div'); pageBox.className = `${namespace}-tabbox-container`; const groups = new Map(); const pageInfos = []; let currentSelected = -1; function addPage(title = null, groupTitle = null) { const index = pageInfos.length; const page = document.createElement('div'); page.className = `${namespace}-tabbox-page`; pageBox.appendChild(page); function getOrAddGroup(groupTitle) { let group = groups.get(groupTitle); if (!group) { const header = document.createElement('div'); header.className = `${namespace}-tabbox-group-title`; header.innerHTML = groupTitle; tabBox.appendChild(header); group = document.createElement('div'); group.className = `${namespace}-tabbox-group`; tabBox.appendChild(group); groups.set(groupTitle, group); } return group; } function addTab(title, parent) { const tab = document.createElement('div'); tab.className = `${namespace}-tabbox-tab`; tab.innerHTML = title; tab.addEventListener('click', () => switchTab(index), false); parent.appendChild(tab); return tab; } const group = (groupTitle == null) ? null : getOrAddGroup(groupTitle); const tab = (title == null) ? null : addTab(title, group ?? tabBox); const newInfo = { index, page, tab, group }; pageInfos.push(newInfo); return newInfo; } function getPage(index) { if ((index < 0) || (index >= pageInfos.length)) { console.error(TAG, `invalid tab index: ${index}`); return null; } return pageInfos[index].page; } function switchTab(index) { if ((index < 0) || (index >= pageInfos.length)) { console.error(TAG, `invalid tab index: ${index}`); return; } else if (currentSelected == index) { return; } const prevIndex = currentSelected; const { page, tab } = pageInfos[index]; // emit before show to make time to render currentSelected = index; emitEvent('onswitch', index, page); // hide current tab if (prevIndex >= 0) { // hide current tab const { page, tab } = pageInfos[prevIndex]; if (tab) { tab.classList.remove(`${namespace}-tabbox-selected`); } page.classList.remove(`${namespace}-tabbox-selected`); } if (tab) { tab.classList.add(`${namespace}-tabbox-selected`); } page.classList.add(`${namespace}-tabbox-selected`); } function getCurrentPage() { if ((currentSelected < 0) || (currentSelected >= pageInfos.length)) { return null; } else { return pageInfos[currentSelected].page; } } return { get currentSelected() { return currentSelected; }, set currentSelected(index) { switchTab(index); }, getCurrentPage, addPage, getPage, appendTo(parent) { parent.appendChild(tabBox); parent.appendChild(pageBox); }, on(eventName, cb) { addEventListener(`on${eventName}`, cb); }, }; } exports.insertDialog = insertDialog; })(Komica); // from https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js if (typeof GM == 'undefined') { this.GM = {}; } if (typeof GM_addStyle == 'undefined') { this.GM_addStyle = (aCss) => { 'use strict'; let head = document.getElementsByTagName('head')[0]; if (head) { let style = document.createElement('style'); style.setAttribute('type', 'text/css'); style.textContent = aCss; head.appendChild(style); return style; } return null; }; } if (typeof GM['addStyle'] == 'undefined') { GM['addStyle'] = function(...args) { return new Promise((resolve, reject) => { try { resolve(GM_addStyle.apply(this, args)); } catch (e) { reject(e); } }); }; } (async function () { 'use strict'; class Uint8StorageMap { #storageId; #storage; #cache; #bytes; maxLength = 50; onadd = () => {}; onremove = () => {}; constructor(storageId, list, bytes, storage = localStorage) { this.#storageId = storageId; this.#storage = storage; this.#cache = list; this.#bytes = bytes; } static async fromLocalStorage(storageId) { const value = localStorage.getItem(storageId); if (!value) { return new Uint8StorageMap(storageId, [], 0); } const bytes = value.length; const entries = value.split(/;/); return new Uint8StorageMap(storageId, entries.map(e => { const [key, value] = e.split(/=/); const uint8 = Uint8ClampedArray.from(value.split(/,/), i => parseInt(i, 10)); return [key, uint8]; }), bytes); } async add(key, data) { const has = this.#cache.some(([k, v]) => k == key); if (!has) { if (this.#cache.length >= this.maxLength) { const remove = this.#cache.length - this.maxLength + 1; this.#cache.splice(0, remove); } this.#cache.push([key, data]); await this.#serialize(); this.onadd(key, data); } } async #serialize() { let value = ''; let semicolon = ''; for (const [key, data] of this.#cache) { value += `${semicolon}${key}=${data}`; semicolon = ';'; } this.#storage.setItem(this.#storageId, value); this.#bytes = value.length; } async remove(key) { const i = this.#cache.findIndex(([k, v]) => k == key); if (i != -1) { const [data] = this.#cache.splice(i, 1); await this.#serialize(); this.onremove(key, data); } } async clear() { this.#cache = []; await this.#serialize(); this.onremove(); } entries() { return this.#cache.values(); } get length() { return this.#cache.length; } get bytes() { return this.#bytes; } } class StorageSet { #storageId; #storage; #cache; maxLength = 100; constructor(storageId, list, storage = localStorage) { this.#storageId = storageId; this.#storage = storage; this.#cache = list; } static async fromLocalStorage(storageId) { const value = localStorage.getItem(storageId); if (value) { return new StorageSet(storageId, value.split(/,/)); } else { return new StorageSet(storageId, []); } } async add(key) { console.log(TAG, this.#storageId, 'add', key); console.log(TAG, this.#storageId, 'add', this.#cache); if (!this.has(key)) { if (this.#cache.length >= this.maxLength) { const remove = this.#cache.length - this.maxLength + 1; this.#cache.splice(0, remove); } this.#cache.push(key); await this.#serialize(); } } async #serialize() { this.#storage.setItem(this.#storageId, this.#cache); } async remove(key) { console.log(TAG, this.#storageId, 'remove', key); const i = this.#cache.indexOf(key); if (i != -1) { this.#cache.splice(i, 1); await this.#serialize(); } } has(key) { return this.#cache.includes(key); } get length() { return this.#cache.length; } } const TAG = '[Komica_Blur]'; const DEFAULT_STLYE_VARS = ` :root { --blur-primary-background-color: #FFFFEE; --blur-secondary-background-color: #F0E0D6; --blur-highlight-background-color: #EEAA88; --blur-highlight-color: #800000; --blur-text-button-color: #00E; --blur-text-button-hover-color: #D00; --blur-separator-color: #000; --blur-primary-shadow-color: #5f5059; --blur-warning-color: #D00; } `; const DIALOG_STYLE = ` .blur-options-page { display: grid; grid-template-columns: [i-start] auto auto [i-end]; grid-auto-rows: min-content; } .blur-blacklist-page { display: grid; grid-template-columns: [i-start] auto max-content max-content [i-end]; grid-auto-rows: min-content; } .blur-blacklist-page button { place-self: center; } .blur-blacklist-page span { margin: 0 6px; place-self: center start; } .blur-blacklist-page canvas { margin: 3px 0; } .blur-separtor { grid-column: i; } .blur-listitem-description { color: var(--blur-highlight-color); grid-column: i; } .blur-listitem-description::before { content: "・"; grid-column: i; } .blur-dialog { visibility: hidden; position: fixed; top: -10px; z-index: 1; opacity: 0; display: grid; grid-template: "h h" min-content "c c" auto "f b" min-content / max-content 1fr; width: 40%; height: 50%; margin: 0 30%; overflow: hidden; border-radius: 5px; box-shadow: 0 0 15px 5px var(--blur-primary-shadow-color); background-color: var(--blur-primary-background-color); transition: top 100ms, visibility 100ms, opacity 100ms; } .blur-dialog-show { visibility: visible; opacity: 1; top: 30px; } .blur-dialog-footer { grid-area: f; align-self: center; margin: 10px 20px; } .blur-dialog-close-button { place-self: center end; margin: 10px 20px; } .blur-tabbox-header { grid-area: h; display: flex; justify-content: start; background-color: var(--blur-secondary-background-color); } .blur-tabbox-tab { cursor: pointer; flex: 1; padding: 3px 12px; font-weight: bold; text-align: center; } .blur-tabbox-tab:hover { background-color: var(--blur-highlight-background-color); color: var(--blur-highlight-color); } .blur-tabbox-tab.blur-tabbox-selected { background-color: var(--blur-highlight-background-color); color: var(--blur-highlight-color); } .blur-tabbox-container { grid-area: c; display: flex; overflow-y: auto; } .blur-tabbox-page { width: 0; opacity: 0; overflow-y: scroll; overflow-x: hidden; transition: opacity 200ms; } .blur-tabbox-page.blur-tabbox-selected { width: 100%; opacity: 1; padding: 10px; } @media screen and (max-device-width: 600px) { .blur-dialog { width: calc(100vw - 20px); margin: 0 10px; } .blur-tabbox-container { width: calc(100vw - 20px); } } `; const BLUR_STYLE = ` .file-thumb .img.blur-safe-img { filter: unset; } .blur-button::before { content: " "; } @media only screen and (max-device-width: 480px) { div.file-text { display: block; font-size: 0; } .file-text .qlink { font-size: 0.8rem; } } `; const HOST_SETTINGS = { 'komica': { darkStyleVars: ` :root { --blur-primary-background-color: #1D1F21; --blur-secondary-background-color: rgb(40, 42, 46); --blur-highlight-background-color: rgb(0, 0, 0); --blur-highlight-color: rgb(178, 148, 187); --blur-text-button-color: #81A2BE; --blur-text-button-hover-color: #FFC685; --blur-separator-color: gray; --blur-primary-shadow-color: rgb(40, 42, 46); --blur-warning-color: #D00; } `, getStyleVars: function () { const [themeCookie] = document.cookie.split(/;\s*/) .map(c => c.split(/=/,2)) .filter(([k, v]) => k == 'theme'); if ((themeCookie) && (themeCookie[1] == 'dark.css')) { return this.darkStyleVars; } else { return DEFAULT_STLYE_VARS; } }, }, }; const hostId = 'komica'; const hostSettings = HOST_SETTINGS[hostId]; async function readSettings() { const saved = await settingsFromGM(); blacklist.maxLength = saved.blacklistMax; whitelist.maxLength = saved.whitelistMax; imageSampler.matchThreshold = saved.matchThreshold; imageSampler.sampleSize = saved.sampleSize; console.log(TAG, saved); saved.onchange = async function onSettingsChanged(optionId, value) { console.log(TAG, optionId, value); switch (optionId) { case 'blacklistMax': blacklist.maxLength = value; break; case 'whitelistMax': whitelist.maxLength = value; break; case 'matchThreshold': imageSampler.matchThreshold = value; break; case 'sampleSize': imageSampler.sampleSize = value; await blacklist.clear(); break; } await GM.setValue(optionId, value); }; return saved; } async function addStyle() { const styleVars = ((hostSettings) && (hostSettings.getStyleVars)) ? hostSettings.getStyleVars() : DEFAULT_STLYE_VARS; await GM.addStyle(styleVars); const blurSettingsStyle = `.file-thumb .img { filter: blur(${settings.blurRadius}); }`; await GM.addStyle(`${blurSettingsStyle}\n${BLUR_STYLE}`); await GM.addStyle(DIALOG_STYLE); } function onLoad(settings) { insertSettingDialog(settings, { blacklist, imageSampler }); const imgs = document.querySelectorAll('.file-thumb .img'); for (const img of imgs) { if (img.complete) { markImage(img); } else { img.addEventListener('load', onLoadImg); } } } const OPTIONS = { 'blurRadius': { choose: ['12px', '15px', '21px'], default: '12px', title: '霧化半徑', afterSepartor: true, }, 'matchThreshold': { range: { min: 0.1, max: 0.5, step: 0.1 }, default: 0.3, isFloat: true, title: '相似閾值', description: '相異像素低於閾值視為相似圖片', }, 'sampleSize': { choose: [32, 64, 96], default: 64, title: 'sampleSize', description: '會增加容量和處理時間,更改時清空黑名單', afterSepartor: true, }, 'blacklistMax': { range: { min: 10, max: 50, step: 10 }, default: 10, title: '黑名單數量', description: '超過會從舊的刪除', }, 'whitelistMax': { choose: [50, 100], default: 50, title: '白名單數量', description: '超過會從舊的刪除', }, }; function typeofOption(descriptor) { return (descriptor.isFloat) ? 'float' : typeof descriptor.default; } function parseValue(type, value) { switch (type) { case 'number': return parseInt(value, 10); case 'float': return parseFloat(value); default: return value; } } async function settingsFromGM() { const settings = { }; for (const [optionId, descriptor] of Object.entries(OPTIONS)) { const value = await GM.getValue(optionId, descriptor.default); settings[optionId] = parseValue(typeofOption(descriptor), value); } return settings; } function insertSettingDialog(settings, { blacklist, imageSampler }) { const { tabBox, footer } = Komica.insertDialog('BLUR', 'blur-settings-dialog', 'blur'); footer.textContent = "※更改需要F5後才套用"; const optionsPageInfo = tabBox.addPage('設定'); const blacklistPageInfo = tabBox.addPage('黑名單'); optionsPageInfo.page.classList.add('blur-options-page'); blacklistPageInfo.page.classList.add('blur-blacklist-page'); function switchTab(pageIdx, root) { switch (pageIdx) { case blacklistPageInfo.index: renderBlacklist(root); break; default: renderOptions(root); break; } } tabBox.on('switch', switchTab); function onBlacklistChanged() { const currentSelected = tabBox.currentSelected; if (currentSelected == blacklistPageInfo.index) { const root = tabBox.getCurrentPage(); renderBlacklist(root); } } blacklist.onadd = onBlacklistChanged; blacklist.onremove = onBlacklistChanged; function renderBlacklist(root) { root.innerHTML = ''; for (const [key, data] of blacklist.entries()) { root.prepend(...createSampleView(key, data, blacklist)); } const bytesTitle = createTextView('黑名單使用容量'); const clearButton = document.createElement('button'); clearButton.textContent = "清空"; clearButton.addEventListener('click', async () => { await blacklist.clear(); }); root.prepend( bytesTitle, createTextView(`${blacklist.bytes / 1000}KiB`), clearButton, createGridSepartor(), ); } function createSampleView(key, data, blacklist) { const [canvas, ctx] = imageSampler.newCanvas(); const imgData = new ImageData(data, canvas.width); ctx.putImageData(imgData, 0, 0); const delButton = document.createElement('button'); delButton.textContent = "移除"; delButton.addEventListener('click', async () => { await blacklist.remove(key); }); return [canvas, createTextView(key), delButton]; } function renderOptions(root) { root.innerHTML = ''; for (const [optionId, descriptor] of Object.entries(OPTIONS)) { const details = { ...descriptor, value: settings[optionId], onchange: settings.onchange }; let views; if (details.range) { views = createRange('blur', optionId, details); } else if (details.choose) { views = createChoose('blur', optionId, details); } root.append(...views); if (details.description) { root.append(createTextView(details.description, 'blur-listitem-description')); } if (details.afterSepartor) { root.append(createGridSepartor()); } } } function createRange(namespace, optionId, details) { const value = details.value ?? details.default; const type = typeofOption(details.default); const onchange = details.onchange; const view = document.createElement('span'); const input = document.createElement('input'); input.type = 'range'; for (const attr of ['min', 'max', 'step']) { input[attr] = details.range[attr]; } input.id = `${namespace}-${optionId}`; input.value = value; const display = document.createElement('span'); display.textContent = value; input.addEventListener('change', async () => { const value = parseValue(type, input.value); display.textContent = value; onchange(optionId, value); }); view.appendChild(input); view.appendChild(display); return [createTextView(details.title, `${namespace}-listitem-title`), view]; } function createChoose(namespace, optionId, details) { const value = details.value ?? details.default; const type = typeofOption(details.default); const onchange = details.onchange; const select = document.createElement('select'); for (const value of details.choose) { select.add(new Option(value)); } select.id = `${namespace}-${optionId}`; select.value = value; select.addEventListener('change', async () => { const value = parseValue(type, select.value); onchange(optionId, value); }); return [createTextView(details.title, `${namespace}-listitem-title`), select]; } function createTextView(textContent, className) { const view = document.createElement('span'); view.textContent = textContent; if (className) { view.className = className; } return view; } function createGridSepartor() { const view = document.createElement('div'); view.className = 'blur-separtor'; view.append(document.createElement('hr')); return view; } } class ImageSampler { #matchThreshold = 0.3; #maxDiff = 64 * 64 * 0.3; #sampleSize = { width: 64, height: 64, length: 64 * 64 }; #canvas = document.createElement('canvas'); #ctx = null; constructor() { this.#canvas.width = this.#sampleSize.width; this.#canvas.height = this.#sampleSize.height; this.#ctx = this.#canvas.getContext("2d"); } set sampleSize(value) { this.#sampleSize = { width: value, height: value, length: value * value }; const [canvas, ctx] = this.newCanvas(); this.#canvas = canvas; this.#ctx = ctx; this.#maxDiff = value * value * this.#matchThreshold; } set matchThreshold(value) { this.#matchThreshold = value; this.#maxDiff = this.#sampleSize.width * this.#sampleSize.height * value; } toGrayData(img) { this.#ctx.drawImage(img, 0, 0, this.#sampleSize.width, this.#sampleSize.height); const imgData = this.#ctx.getImageData(0, 0, this.#sampleSize.width, this.#sampleSize.height); return ImageSampler.canvasToGray(this.#ctx, imgData); } static canvasToGray(ctx, imgData) { const pixels = imgData.data; for (let i = 0; i < pixels.length; i += 4) { const lightness = 0.2126 * pixels[i] + 0.715 * pixels[i+1] + 0.0722 * pixels[i+2]; pixels[i] = lightness; pixels[i + 1] = lightness; pixels[i + 2] = lightness; } ctx.putImageData(imgData, 0, 0); return pixels; } matchData(key1, img1, key2, img2) { const diff = pixelmatch(img1, img2, null, this.#sampleSize.width, this.#sampleSize.height, {maxDiff: this.#maxDiff, threshold: 0.1}); const n = diff / this.#sampleSize.length; const payload = { n, key: key2 }; //console.log(TAG, key1, key2, n); return (n < this.#matchThreshold) ? payload : null; } newCanvas() { const canvas = document.createElement('canvas'); canvas.width = this.#sampleSize.width; canvas.height = this.#sampleSize.height; const ctx = canvas.getContext("2d"); return [canvas, ctx]; } } class Blacklist { #storage; constructor(storage) { this.#storage = storage; } static async fromLocalStorage() { return new Blacklist(await Uint8StorageMap.fromLocalStorage('blur-blacklist')); } set maxLength(value) { this.#storage.maxLength = value; } set onadd(value) { this.#storage.onadd = value; } set onremove(value) { this.#storage.onremove = value; } get length() { return this.#storage.length; } get bytes() { return this.#storage.bytes; } async add(key, data) { console.log(TAG, 'add blacklist', key); this.#storage.add(key, data); } async remove(key, data) { console.log(TAG, 'remove blacklist', key); this.#storage.remove(key); } async clear() { console.log(TAG, 'clear blacklist'); this.#storage.clear(); } entries() { return this.#storage.entries(); } match(callback) { for (const [key, data] of this.#storage.entries()) { const matches = callback(key, data); if (matches) { return matches; } } return null; } } const imageSampler = new ImageSampler(); const blacklist = await Blacklist.fromLocalStorage(); const whitelist = await StorageSet.fromLocalStorage('blur-whitelist'); class WorkList { #list = []; #completed = 0; #timer = null; push(work) { const shouldStart = this.finished; this.#list.push(work); return shouldStart; } next() { const current = this.#completed; if (current == this.#list.length) { return null; } else { this.#completed++; return this.#list[current]; } } get finished() { return (this.#completed == this.#list.length); } } const workList = new WorkList(); function onLoadImg(ev) { const img = ev.target; markImage(img); } function markImage(img, key2, img2) { if (workList.push({ img, key2, img2 })) { setTimeout(doImageWork, 0); console.time(TAG, 'work'); } } async function doImageWork() { const work = workList.next(); if (!work) { console.timeEnd(TAG, 'work'); return; } const { img, key2, img2 } = work; const key1 = imageKey(img); //console.log(TAG, 'doImageWork', key1); if (whitelist.has(key1)) { img.classList.add('blur-safe-img'); img.dataset.imageInList = 'whitelist'; } else { const img1 = imageSampler.toGrayData(img); let matches = null; if ((key2) && (img2)) { matches = imageSampler.matchData(key1, img1, key2, img2); } else { matches = blacklist.match((key2, img2) => imageSampler.matchData(key1, img1, key2, img2)); } if (!matches) { img.classList.add('blur-safe-img'); } else if (matches.key == key1) { img.classList.remove('blur-safe-img'); img.dataset.imageInList = 'blacklist'; } else { console.log(TAG, key1, 'matches', matches); img.classList.remove('blur-safe-img'); img.dataset.imageMatchBlacklist = matches.key; } } renderContextMenu(img); setTimeout(doImageWork, 0); } function imageKey(img) { return img.src.replace(/^.*\//, ''); } function renderContextMenu(img) { const parent = img.parentElement.parentElement.querySelector('.file-text'); let blurButton = parent.querySelector('.blur-button'); if (!blurButton) { blurButton = document.createElement('span'); blurButton.classList.add('qlink', 'blur-button'); blurButton.addEventListener('click', () => toggleBlur(img)); parent.appendChild(blurButton); } if (img.dataset.imageInList == 'whitelist') { blurButton.textContent = "[remove whitelist]"; } else if (img.dataset.imageMatchBlacklist) { blurButton.textContent = "[whitelist]"; } else if (img.dataset.imageInList == 'blacklist') { blurButton.textContent = "[remove blacklist]"; } else { blurButton.textContent = "[blacklist]"; } } async function toggleBlur(img) { const key = imageKey(img); if (img.dataset.imageInList == 'whitelist') { await whitelist.remove(key) onRemovedWhitelist(img); } else if (img.dataset.imageMatchBlacklist) { await whitelist.add(key) onAddedWhitelist(img); } else if (img.dataset.imageInList == 'blacklist') { await blacklist.remove(key); onRemovedBlacklist(key); } else { const imgData = imageSampler.toGrayData(img); await blacklist.add(key, imgData); onAddedBlacklist(key, imgData); } } function onRemovedWhitelist(img) { img.classList.remove('blur-safe-img'); delete img.dataset.imageInList; delete img.dataset.imageMatchBlacklist; markImage(img); renderContextMenu(img); } function onAddedWhitelist(img) { img.classList.add('blur-safe-img'); img.dataset.imageInList = 'whitelist'; renderContextMenu(img); } function onRemovedBlacklist(key) { const imgs = document.querySelectorAll('.file-thumb .img'); for (const img of imgs) { const key1 = imageKey(img); if (img.dataset.imageMatchBlacklist == key) { img.classList.add('blur-safe-img'); delete img.dataset.imageMatchBlacklist; renderContextMenu(img); } else if (key1 == key) { img.classList.add('blur-safe-img'); delete img.dataset.imageInList; renderContextMenu(img); } } } function onAddedBlacklist(key2, img2) { const imgs = document.querySelectorAll('.file-thumb .img'); for (const img of imgs) { if (!img.dataset.imageMatchBlacklist) { const key1 = imageKey(img); if (key1 == key2) { img.classList.remove('blur-safe-img'); img.dataset.imageInList = 'blacklist'; renderContextMenu(img); } else if (!img.dataset.imageInList) { markImage(img, key2, img2); } } } } const settings = await readSettings(); await addStyle(settings); onLoad(settings); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址