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