// ==UserScript==
// @name 链接新标签页打开❓
// @namespace http://tampermonkey.net/
// @version 1.45
// @description 为同域名和跨域名链接分别设置打开行为
// @author Grey333
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// @grant GM_registerMenuCommand
// ==/UserScript==
(function() {
'use strict';
const currentDomain = window.location.hostname;
const getTopLevelDomain = (domain) => {
const parts = domain.split('.');
return parts.length > 2 ? parts.slice(-2).join('.') : domain;
};
const currentTopLevelDomain = getTopLevelDomain(currentDomain);
// 获取和设置行为
const getSameDomainBehavior = () => GM_getValue(currentDomain + '_sameDomain_behavior', 'default');
const setSameDomainBehavior = (value) => GM_setValue(currentDomain + '_sameDomain_behavior', value);
const getSubDomainBehavior = () => GM_getValue(currentDomain + '_subDomain_behavior', 'default');
const setSubDomainBehavior = (value) => GM_setValue(currentDomain + '_subDomain_behavior', value);
const getCrossDomainBehavior = () => GM_getValue(currentDomain + '_crossDomain_behavior', 'default');
const setCrossDomainBehavior = (value) => GM_setValue(currentDomain + '_crossDomain_behavior', value);
// 创建图形界面
function createGUI() {
if (document.getElementById('linkControlPanel')) return;
const panel = document.createElement('div');
panel.id = 'linkControlPanel';
panel.style.cssText = `
position: fixed; top: 20px; right: 20px; background: #f9f9f9;
border: 1px solid #ddd; border-radius: 8px; padding: 15px;
z-index: 10000; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
font-family: Arial, sans-serif; transition: opacity 0.3s ease-in-out;
opacity: 0; max-width: 350px; min-width: 250px;
`;
const title = document.createElement('h3');
title.textContent = `链接控制 - ${currentDomain}`;
title.style.margin = '0 0 10px';
panel.appendChild(title);
const options = [
{ group: '同域名链接', key: 'sameDomain', get: getSameDomainBehavior, set: setSameDomainBehavior },
{ group: '子域名链接', key: 'subDomain', get: getSubDomainBehavior, set: setSubDomainBehavior },
{ group: '跨域名链接', key: 'crossDomain', get: getCrossDomainBehavior, set: setCrossDomainBehavior }
];
options.forEach(({ group, key, get, set }) => {
const groupTitle = document.createElement('h4');
groupTitle.textContent = group;
groupTitle.style.margin = '10px 0 5px';
panel.appendChild(groupTitle);
const behaviorOptions = [
{ label: '默认(不干预)', value: 'default' },
{ label: '强制新标签页', value: 'forceNewTab' },
{ label: '禁止新标签页', value: 'forceSameTab' }
];
behaviorOptions.forEach(opt => {
const label = document.createElement('label');
label.style.display = 'block';
label.style.margin = '5px 0';
const radio = document.createElement('input');
radio.type = 'radio';
radio.name = `${key}_behavior`;
radio.value = opt.value;
if (get() === opt.value) radio.checked = true;
radio.addEventListener('change', () => {
set(opt.value);
alert(`已为 ${group} 设置: ${opt.label}`);
});
label.appendChild(radio);
label.appendChild(document.createTextNode(` ${opt.label}`));
panel.appendChild(label);
});
});
const closeBtn = document.createElement('button');
closeBtn.textContent = '关闭';
closeBtn.style.cssText = `
margin-top: 15px; padding: 5px 10px; background: #007bff;
color: #fff; border: none; border-radius: 4px; cursor: pointer;
`;
closeBtn.addEventListener('click', () => {
panel.style.opacity = '0';
setTimeout(() => panel.remove(), 300);
});
panel.appendChild(closeBtn);
document.body.appendChild(panel);
setTimeout(() => panel.style.opacity = '1', 10);
}
GM_registerMenuCommand('设置链接行为', createGUI);
// 拦截 window.open
const originalOpen = unsafeWindow.open;
unsafeWindow.open = function(url, name, features) {
const absoluteURL = new URL(url, window.location.href);
const linkDomain = absoluteURL.hostname;
const linkTopLevelDomain = getTopLevelDomain(linkDomain);
let behavior;
if (linkDomain === currentDomain) {
behavior = getSameDomainBehavior();
} else if (linkTopLevelDomain === currentTopLevelDomain && linkDomain !== currentDomain) {
behavior = getSubDomainBehavior();
} else {
behavior = getCrossDomainBehavior();
}
if (behavior === 'forceSameTab') {
window.location.href = absoluteURL.href;
return null;
} else if (behavior === 'forceNewTab') {
return originalOpen.call(this, url, name, features);
}
return originalOpen.call(this, url, name, features);
};
// 应用链接行为
function applyLinkBehavior() {
const sameDomainBehavior = getSameDomainBehavior();
const subDomainBehavior = getSubDomainBehavior();
const crossDomainBehavior = getCrossDomainBehavior();
// 单次绑定点击事件
document.removeEventListener('click', clickHandler, { capture: true });
document.addEventListener('click', clickHandler, { capture: true });
function clickHandler(e) {
const link = e.target.closest('a');
if (!link || !link.href) return;
const absoluteURL = new URL(link.href, window.location.href);
const linkDomain = absoluteURL.hostname;
const linkTopLevelDomain = getTopLevelDomain(linkDomain);
let behavior;
if (linkDomain === currentDomain) {
behavior = sameDomainBehavior;
} else if (linkTopLevelDomain === currentTopLevelDomain && linkDomain !== currentDomain) {
behavior = subDomainBehavior;
} else {
behavior = crossDomainBehavior;
}
if (behavior === 'default') return;
e.preventDefault();
e.stopImmediatePropagation();
if (behavior === 'forceNewTab') {
window.open(absoluteURL.href, '_blank');
} else if (behavior === 'forceSameTab') {
window.location.href = absoluteURL.href;
}
}
// 优化 MutationObserver
let debounceTimer;
const observer = new MutationObserver(() => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
document.querySelectorAll('a[href]').forEach(link => {
const absoluteURL = new URL(link.href, window.location.href);
const linkDomain = absoluteURL.hostname;
const linkTopLevelDomain = getTopLevelDomain(linkDomain);
let behavior;
if (linkDomain === currentDomain) {
behavior = sameDomainBehavior;
} else if (linkTopLevelDomain === currentTopLevelDomain && linkDomain !== currentDomain) {
behavior = subDomainBehavior;
} else {
behavior = crossDomainBehavior;
}
if (behavior === 'forceSameTab' && link.getAttribute('target') === '_blank') {
link.removeAttribute('target');
}
});
}, 100); // 防抖 100ms
});
observer.observe(document.body, { childList: true, subtree: true, attributes: true });
}
applyLinkBehavior();
})();