您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
可配合【阅图标记 (Visited Image Marker)】使用
// ==UserScript== // @name 阅图标记 (边框标记版) // @namespace RANRAN // @version 1.0 // @description 可配合【阅图标记 (Visited Image Marker)】使用 // @author Gemini // @match http://*/* // @match https://*/* // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_deleteValue // ==/UserScript== (function() { 'use strict'; // --- 默认设置与常量 --- const STORAGE_KEY_VISITED = 'visitedLinks'; const STORAGE_KEY_SETTINGS = 'readimage_settings'; const READ_STATE_CLASS = 'readimage-visited-link'; const DEFAULTS = { unreadWidth: '5px', unreadColor: 'rgba(211, 211, 211, 0.7)', readWidth: '5px', readColor: 'tomato', matchingMode: 'blacklist', matchingList: [ 'google.com', 'bing.com', 'baidu.com' ] }; // --- 加载设置 --- let settings = { ...DEFAULTS, ...JSON.parse(GM_getValue(STORAGE_KEY_SETTINGS, '{}')) }; if (!Array.isArray(settings.matchingList)) { settings.matchingList = DEFAULTS.matchingList; } // --- 核心逻辑:检查黑白名单 --- function shouldScriptRun() { const currentUrl = window.location.href; const { matchingMode, matchingList } = settings; // v5.1 优化的匹配逻辑:不再使用严格的正则表达式,而是使用更宽容的字符串包含检查 // 这样用户输入 "example.com" 就能匹配 "https://www.example.com/page" const isMatch = matchingList.some(pattern => { if (!pattern) return false; // 忽略空行 // 将 ".*" 形式的简单通配符转为真正的通配符,其他则直接检查是否包含 if (pattern.includes('*')) { const regex = new RegExp(pattern.replace(/\./g, '\\.').replace(/\*/g, '.*'), 'i'); return regex.test(currentUrl); } return currentUrl.includes(pattern); }); if (matchingMode === 'whitelist') { return isMatch; } else { return !isMatch; } } if (!shouldScriptRun()) { return; // 如果不应运行,则停止脚本 } // --- 脚本主要功能 (与之前版本相同) --- let visitedLinks = JSON.parse(GM_getValue(STORAGE_KEY_VISITED, '{}')); function applyStyles() { const styleId = 'readimage-dynamic-styles'; let styleElement = document.getElementById(styleId); if (!styleElement) { styleElement = document.createElement('style'); styleElement.id = styleId; document.head.appendChild(styleElement); } styleElement.textContent = ` a img { border: ${settings.unreadWidth} solid ${settings.unreadColor} !important; box-sizing: border-box; } a.${READ_STATE_CLASS} img { border-width: ${settings.readWidth} !important; border-color: ${settings.readColor} !important; } `; } function markVisitedLinks() { document.querySelectorAll('a:has(img)').forEach(link => { if (link.href && visitedLinks[link.href]) { link.classList.add(READ_STATE_CLASS); } }); } document.body.addEventListener('click', (event) => { const link = event.target.closest('a'); if (link && link.href && link.querySelector('img')) { if (!visitedLinks[link.href]) { visitedLinks[link.href] = true; link.classList.add(READ_STATE_CLASS); GM_setValue(STORAGE_KEY_VISITED, JSON.stringify(visitedLinks)); } } }, true); // --- 可视化UI模块 (与之前版本相同) --- const UI = { init() { GM_addStyle(` #readimage-settings-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 99999; background: #f0f0f0; border: 1px solid #ccc; border-radius: 8px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); font-family: Arial, sans-serif; font-size: 14px; color: #333; width: 420px; } #readimage-settings-panel .ri-header { padding: 10px 15px; background: #e0e0e0; font-weight: bold; border-bottom: 1px solid #ccc; border-radius: 8px 8px 0 0; cursor: move; position: relative; } #readimage-settings-panel .ri-close-btn { position: absolute; top: 5px; right: 10px; font-size: 20px; font-weight: bold; cursor: pointer; color: #888; } #readimage-settings-panel .ri-close-btn:hover { color: #000; } #readimage-settings-panel .ri-body { padding: 15px; max-height: 70vh; overflow-y: auto; } #readimage-settings-panel fieldset { border: 1px solid #ccc; border-radius: 4px; padding: 10px; margin-bottom: 15px; } #readimage-settings-panel legend { font-weight: bold; padding: 0 5px; } #readimage-settings-panel .ri-row { display: flex; align-items: center; margin-bottom: 8px; } #readimage-settings-panel .ri-row label { width: 50px; } #readimage-settings-panel .ri-row input[type="text"] { flex-grow: 1; border: 1px solid #ccc; border-radius: 4px; padding: 5px; } #readimage-settings-panel .ri-row input[type="color"] { margin-left: 10px; border: 1px solid #ccc; padding: 2px; border-radius: 4px; width: 40px; height: 30px; cursor: pointer; } #readimage-settings-panel .ri-footer { padding: 10px 15px; background: #e0e0e0; text-align: right; border-top: 1px solid #ccc; border-radius: 0 0 8px 8px; } #readimage-settings-panel .ri-footer button { margin-left: 10px; padding: 5px 15px; border: 1px solid #999; border-radius: 4px; cursor: pointer; background: #fff; } #readimage-settings-panel .ri-footer button#ri-save-btn { background: #4CAF50; color: white; border-color: #4CAF50; font-weight: bold; } #readimage-settings-panel .ri-note { font-size: 12px; color: #666; margin: 5px 0 10px 0; } #readimage-settings-panel textarea { width: 95%; min-height: 80px; resize: vertical; padding: 5px; border: 1px solid #ccc; border-radius: 4px; font-family: monospace; } `); }, create() { if (document.getElementById('readimage-settings-panel')) return; const panel = document.createElement('div'); panel.id = 'readimage-settings-panel'; panel.innerHTML = ` <div class="ri-header">脚本设置</div> <div class="ri-body"> <fieldset> <legend>生效网站设置</legend> <div class="ri-row"> <input type="radio" name="ri-mode" id="ri-mode-blacklist" value="blacklist" style="margin-right: 5px;"> <label for="ri-mode-blacklist" style="width: auto;">黑名单模式 (在下列网站<strong style="color:red">不</strong>运行)</label> </div> <div class="ri-row"> <input type="radio" name="ri-mode" id="ri-mode-whitelist" value="whitelist" style="margin-right: 5px;"> <label for="ri-mode-whitelist" style="width: auto;">白名单模式 (<strong>只在</strong>下列网站运行)</label> </div> <p class="ri-note">每行一个域名/网址,* 为通配符。例如: example.com</p> <textarea id="ri-matching-list"></textarea> </fieldset> <fieldset> <legend>边框样式设置</legend> <div class="ri-row"> <label for="ri-unread-width">粗细:</label> <input type="text" id="ri-unread-width"> </div> <div class="ri-row"> <label for="ri-unread-color">颜色:</label> <input type="text" id="ri-unread-color"> <input type="color" id="ri-unread-color-picker"> </div> <hr style="border: none; border-top: 1px dashed #ccc; margin: 10px 0;"> <div class="ri-row"> <label for="ri-read-width">粗细:</label> <input type="text" id="ri-read-width"> </div> <div class="ri-row"> <label for="ri-read-color">颜色:</label> <input type="text" id="ri-read-color"> <input type="color" id="ri-read-color-picker"> </div> </fieldset> </div> <div class="ri-footer"> <button id="ri-defaults-btn">恢复默认</button> <button id="ri-cancel-btn">取消</button> <button id="ri-save-btn">保存并刷新</button> </div> <span class="ri-close-btn">×</span> `; document.body.appendChild(panel); this.addListeners(panel); }, show() { let panel = document.getElementById('readimage-settings-panel'); if (!panel) { this.create(); panel = document.getElementById('readimage-settings-panel'); } panel.querySelector('#ri-unread-width').value = settings.unreadWidth; panel.querySelector('#ri-unread-color').value = settings.unreadColor; panel.querySelector('#ri-unread-color-picker').value = this.toHex(settings.unreadColor); panel.querySelector('#ri-read-width').value = settings.readWidth; panel.querySelector('#ri-read-color').value = settings.readColor; panel.querySelector('#ri-read-color-picker').value = this.toHex(settings.readColor); panel.querySelector(`#ri-mode-${settings.matchingMode}`).checked = true; panel.querySelector('#ri-matching-list').value = settings.matchingList.join('\n'); panel.style.display = 'block'; }, hide() { const panel = document.getElementById('readimage-settings-panel'); if (panel) panel.style.display = 'none'; }, addListeners(panel) { panel.querySelector('.ri-close-btn').addEventListener('click', () => this.hide()); panel.querySelector('#ri-cancel-btn').addEventListener('click', () => this.hide()); panel.querySelector('#ri-unread-color-picker').addEventListener('input', (e) => { panel.querySelector('#ri-unread-color').value = e.target.value; }); panel.querySelector('#ri-read-color-picker').addEventListener('input', (e) => { panel.querySelector('#ri-read-color').value = e.target.value; }); panel.querySelector('#ri-save-btn').addEventListener('click', () => { const newSettings = { unreadWidth: panel.querySelector('#ri-unread-width').value, unreadColor: panel.querySelector('#ri-unread-color').value, readWidth: panel.querySelector('#ri-read-width').value, readColor: panel.querySelector('#ri-read-color').value, matchingMode: panel.querySelector('input[name="ri-mode"]:checked').value, matchingList: panel.querySelector('#ri-matching-list').value.split('\n').map(line => line.trim()).filter(line => line) }; GM_setValue(STORAGE_KEY_SETTINGS, JSON.stringify(newSettings)); this.hide(); alert('设置已保存!页面将刷新以应用新规则。'); window.location.reload(); }); panel.querySelector('#ri-defaults-btn').addEventListener('click', () => { if (confirm('确定要恢复所有默认设置吗?')) { panel.querySelector('#ri-unread-width').value = DEFAULTS.unreadWidth; panel.querySelector('#ri-unread-color').value = DEFAULTS.unreadColor; panel.querySelector('#ri-unread-color-picker').value = this.toHex(DEFAULTS.unreadColor); panel.querySelector('#ri-read-width').value = DEFAULTS.readWidth; panel.querySelector('#ri-read-color').value = DEFAULTS.readColor; panel.querySelector('#ri-read-color-picker').value = this.toHex(DEFAULTS.readColor); panel.querySelector(`#ri-mode-${DEFAULTS.matchingMode}`).checked = true; panel.querySelector('#ri-matching-list').value = DEFAULTS.matchingList.join('\n'); } }); this.makeDraggable(panel.querySelector('.ri-header'), panel); }, toHex(colorStr) { try { const ctx = document.createElement('canvas').getContext('2d'); ctx.fillStyle = colorStr; return ctx.fillStyle; } catch (e) { return '#000000'; } }, makeDraggable(header, panel) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; header.onmousedown = (e) => { e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = () => { document.onmouseup = null; document.onmousemove = null; }; document.onmousemove = (e) => { e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; panel.style.top = (panel.offsetTop - pos2) + "px"; panel.style.left = (panel.offsetLeft - pos1) + "px"; }; }; } }; // --- 脚本启动与菜单注册 --- function initialize() { applyStyles(); markVisitedLinks(); const observer = new MutationObserver(markVisitedLinks); observer.observe(document.body, { childList: true, subtree: true }); UI.init(); GM_registerMenuCommand('打开设置面板', () => UI.show()); GM_registerMenuCommand('清除所有已读记录', () => { if (confirm('您确定要清除所有图片的已读记录吗?此操作不可撤销。')) { GM_deleteValue(STORAGE_KEY_VISITED); visitedLinks = {}; alert('所有已读记录已被清除。请刷新页面。'); window.location.reload(); } }); } initialize(); })();// ==UserScript== // @name New Userscript // @namespace http://tampermonkey.net/ // @version 2025-07-27 // @description try to take over the world! // @author You // @match http://*/* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant none // ==/UserScript== (function() { 'use strict'; // Your code here... })();