Greasy Fork

Greasy Fork is available in English.

阅图标记 (边框标记版)

可配合【阅图标记 (Visited Image Marker)】使用

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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">&times;</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...
})();