// ==UserScript==
// @name south-plus域名自动替换 (v2.0)
// @namespace https://github.com/qgdyyg/automatically-replace-southplus-domain
// @version 2.0
// @description 自动替换域名并支持自定义替换规则
// @author qgdyyg
// @match *://*/*
// @grant GM_openInTab
// @grant GM_registerMenuCommand
// @grant GM_webRequest
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @license MIT
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
// 初始化存储的替换规则
const DEFAULT_RULES = [
{ source: 'south-plus.net', target: 'bbs.imoutolove.me' },
{ source: 'north-plus.net', target: 'bbs.imoutolove.me' },
{ source: 'white-plus.net', target: 'bbs.imoutolove.me' },
{ source: 'blue-plus.net', target: 'bbs.imoutolove.me' },
{ source: 'snow-plus.net', target: 'bbs.imoutolove.me' },
{ source: 'spring-plus.net', target: 'bbs.imoutolove.me' }
];
// 从存储加载用户规则,如果没有则使用默认规则
let REPLACEMENT_RULES = GM_getValue('replacementRules', DEFAULT_RULES);
// 配置区域
const CONFIG = {
debug: false,
autoRefresh: true,
refreshDelay: 1500
};
// 保存规则到存储
const saveRules = () => {
GM_setValue('replacementRules', REPLACEMENT_RULES);
if (CONFIG.debug) {
console.log('[规则保存] 替换规则已保存', REPLACEMENT_RULES);
}
};
// 重置为默认规则
const resetToDefault = () => {
REPLACEMENT_RULES = [...DEFAULT_RULES];
saveRules();
alert('已重置为默认替换规则');
};
// 添加新规则
const addRule = () => {
const source = prompt('请输入要替换的源域名 (例如: example.com)', '');
if (!source) return;
const target = prompt('请输入替换后的目标域名 (例如: newdomain.com)', 'bbs.imoutolove.me');
if (!target) return;
// 确保域名格式正确
const cleanSource = source.replace(/^https?:\/\//, '').replace(/\/.*$/, '').trim();
const cleanTarget = target.replace(/^https?:\/\//, '').replace(/\/.*$/, '').trim();
if (!cleanSource || !cleanTarget) {
alert('域名格式不正确,请重新输入');
return;
}
// 检查是否已存在相同源域名的规则
const exists = REPLACEMENT_RULES.some(rule => rule.source === cleanSource);
if (exists) {
if (!confirm(`已存在源域名为 "${cleanSource}" 的规则,是否覆盖?`)) {
return;
}
REPLACEMENT_RULES = REPLACEMENT_RULES.filter(rule => rule.source !== cleanSource);
}
REPLACEMENT_RULES.push({ source: cleanSource, target: cleanTarget });
saveRules();
alert(`已添加新规则:\n${cleanSource} → ${cleanTarget}`);
};
// 编辑现有规则
const editRule = () => {
if (REPLACEMENT_RULES.length === 0) {
alert('当前没有可用的替换规则');
return;
}
const ruleList = REPLACEMENT_RULES.map((rule, i) =>
`${i + 1}. ${rule.source} → ${rule.target}`
).join('\n');
const index = prompt(
'请输入要编辑的规则编号:\n\n' + ruleList,
'1'
);
if (!index) return;
const ruleIndex = parseInt(index) - 1;
if (isNaN(ruleIndex) || ruleIndex < 0 || ruleIndex >= REPLACEMENT_RULES.length) {
alert('无效的规则编号');
return;
}
const currentRule = REPLACEMENT_RULES[ruleIndex];
const newSource = prompt('修改源域名:', currentRule.source);
if (newSource === null) return; // 用户点击了取消
const newTarget = prompt('修改目标域名:', currentRule.target);
if (newTarget === null) return; // 用户点击了取消
// 确保域名格式正确
const cleanSource = newSource.replace(/^https?:\/\//, '').replace(/\/.*$/, '').trim();
const cleanTarget = newTarget.replace(/^https?:\/\//, '').replace(/\/.*$/, '').trim();
if (!cleanSource || !cleanTarget) {
alert('域名格式不正确,请重新输入');
return;
}
// 检查是否已存在相同源域名的规则(除了当前规则)
const exists = REPLACEMENT_RULES.some((rule, i) =>
i !== ruleIndex && rule.source === cleanSource
);
if (exists) {
if (!confirm(`已存在源域名为 "${cleanSource}" 的规则,是否覆盖?`)) {
return;
}
REPLACEMENT_RULES = REPLACEMENT_RULES.filter((_, i) => i !== ruleIndex);
REPLACEMENT_RULES.push({ source: cleanSource, target: cleanTarget });
} else {
REPLACEMENT_RULES[ruleIndex] = {
source: cleanSource,
target: cleanTarget
};
}
saveRules();
alert(`规则已更新:\n${cleanSource} → ${cleanTarget}`);
};
// 删除规则
const deleteRule = () => {
if (REPLACEMENT_RULES.length === 0) {
alert('当前没有可用的替换规则');
return;
}
const ruleList = REPLACEMENT_RULES.map((rule, i) =>
`${i + 1}. ${rule.source} → ${rule.target}`
).join('\n');
const index = prompt(
'请输入要删除的规则编号:\n\n' + ruleList,
'1'
);
if (!index) return;
const ruleIndex = parseInt(index) - 1;
if (isNaN(ruleIndex) || ruleIndex < 0 || ruleIndex >= REPLACEMENT_RULES.length) {
alert('无效的规则编号');
return;
}
const rule = REPLACEMENT_RULES[ruleIndex];
if (confirm(`确定要删除规则吗?\n${rule.source} → ${rule.target}`)) {
REPLACEMENT_RULES = REPLACEMENT_RULES.filter((_, i) => i !== ruleIndex);
saveRules();
alert('规则已删除');
}
};
// 显示当前规则
const showCurrentRules = () => {
if (REPLACEMENT_RULES.length === 0) {
alert('当前没有配置替换规则');
return;
}
const ruleList = REPLACEMENT_RULES.map(rule =>
`• ${rule.source} → ${rule.target}`
).join('\n');
alert(
`当前替换规则 (${REPLACEMENT_RULES.length}条):\n\n` +
ruleList +
'\n\n点击 plus.net 域名链接将自动替换为目标域名'
);
};
// 注册右键菜单
GM_registerMenuCommand('🛠️ 配置自动替换规则', () => {
const options = [
'1. 查看当前规则',
'2. 添加新规则',
'3. 编辑现有规则',
'4. 删除规则',
'5. 重置为默认规则',
'6. 退出'
].join('\n');
const choice = prompt(
`请选择操作:\n\n${options}`,
'1'
);
switch (choice) {
case '1':
showCurrentRules();
break;
case '2':
addRule();
break;
case '3':
editRule();
break;
case '4':
deleteRule();
break;
case '5':
if (confirm('确定要重置为默认规则吗?当前自定义规则将丢失')) {
resetToDefault();
}
break;
default:
// 任何其他选择或取消都视为退出
break;
}
});
// 1. 点击链接时替换域名
document.addEventListener('click', function (e) {
if (e.target.tagName.toLowerCase() === 'a') {
try {
const url = new URL(e.target.href);
const matchedRule = REPLACEMENT_RULES.find(rule =>
url.hostname.endsWith(rule.source)
);
if (matchedRule) {
const oldUrl = e.target.href;
url.hostname = matchedRule.target;
const newUrl = url.toString();
if (CONFIG.debug) {
console.log(`[自动替换] ${oldUrl} → ${newUrl}`);
}
e.preventDefault();
GM_openInTab(newUrl, {
active: true,
insert: true,
setParent: true
});
}
} catch (error) {
console.warn('[链接解析失败]', e.target.href, error);
}
}
});
// 2. 使用 webRequest API 重定向请求
const rules = REPLACEMENT_RULES.map(rule => ({
selector: `*://*.${rule.source}/*`,
action: {
type: "redirect",
redirectUrl: (details) => {
const url = new URL(details.url);
url.hostname = rule.target;
const newUrl = url.toString();
if (CONFIG.debug) {
console.log(`[请求重定向] ${details.url} → ${newUrl}`);
}
return newUrl;
}
}
}));
GM_webRequest(rules);
// 3. 自动刷新失败页面功能
if (CONFIG.autoRefresh) {
const checkPageStatus = () => {
const isEmpty = !document.body ||
(document.body.textContent || '').trim() === '' ||
document.body.innerHTML === '';
const hasError = document.body && (
document.body.textContent.includes('404') ||
document.body.textContent.includes('Not Found') ||
document.body.textContent.includes('Connection refused') ||
document.body.textContent.includes('Unable to connect') ||
document.body.textContent.includes('This site can’t be reached')
);
const currentDomain = window.location.hostname;
const isTargetDomain = REPLACEMENT_RULES.some(rule =>
currentDomain.endsWith(rule.source)
);
if (isTargetDomain && (isEmpty || hasError)) {
if (CONFIG.debug) {
console.log('[自动刷新] 检测到加载失败页面,尝试刷新...');
}
try {
const url = new URL(window.location.href);
const matchedRule = REPLACEMENT_RULES.find(rule =>
currentDomain.endsWith(rule.source)
);
if (matchedRule) {
url.hostname = matchedRule.target;
const newUrl = url.toString();
if (CONFIG.debug) {
console.log(`[自动刷新] 尝试重定向: ${window.location.href} → ${newUrl}`);
}
setTimeout(() => {
window.location.replace(newUrl);
}, CONFIG.refreshDelay);
}
} catch (error) {
console.error('[自动刷新] 重定向失败:', error);
}
}
};
window.addEventListener('load', checkPageStatus);
setInterval(checkPageStatus, 3000);
}
// 4. 添加视觉指示器(脚本运行状态提示)
GM_addStyle(`
.vm-plusnet-indicator {
position: fixed;
bottom: 20px;
right: 20px;
background: #4CAF50;
color: white;
padding: 8px 12px;
border-radius: 4px;
font-size: 12px;
z-index: 9999;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
cursor: pointer;
animation: vm-pulse 2s infinite;
user-select: none;
}
@keyframes vm-pulse {
0% { opacity: 0.7; }
50% { opacity: 1; }
100% { opacity: 0.7; }
}
`);
const indicator = document.createElement('div');
indicator.className = 'vm-plusnet-indicator';
indicator.textContent = `自动替换已启用 (${REPLACEMENT_RULES.length}条规则) ✅`;
indicator.title = '点击显示当前规则';
indicator.addEventListener('click', showCurrentRules);
document.body.appendChild(indicator);
})();