// ==UserScript==
// @name 前缀转发
// @namespace http://example.com/
// @license GPL3
// @version 1
// @description 使用 JSON 格式配置规则,支持多条规则、独立开关
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @run-at document-start
// @include http://example.com/
// ==/UserScript==
(function () {
'use strict';
/* ------------ 默认配置 ------------ */
const DEFAULT_RULES = [
{
"name": "测试规则",
"enabled": true,
"prefix": "https://dev.com/api/",
"replacement": "http://localhost:8080/"
},
{
"name": "测试规则2",
"enabled": false,
"prefix": "https://dev.com/api2/",
"replacement": "http://localhost:8082/"
}
];
/* ------------ 读取和保存规则 ------------ */
function getRules() {
const savedRules = GM_getValue('rules');
if (savedRules && typeof savedRules === 'string') {
try {
return JSON.parse(savedRules);
} catch (e) {
console.error('加载保存的规则出错:', e);
GM_setValue('rules', JSON.stringify(DEFAULT_RULES));
return DEFAULT_RULES;
}
}
return DEFAULT_RULES;
}
function saveRules(rules) {
if (Array.isArray(rules)) {
GM_setValue('rules', JSON.stringify(rules));
} else {
console.error('无效的规则格式。规则必须是数组格式。');
throw new Error('无效的规则格式。规则必须是数组格式。');
}
}
/* ------------ 核心替换逻辑 ------------ */
function redirect(url) {
if (typeof url !== 'string') return url;
const rules = getRules();
const normalizedUrl = url.trim();
for (const rule of rules) {
if (rule.enabled) {
const normalizedPrefix = rule.prefix.trim();
if (normalizedUrl.startsWith(normalizedPrefix)) {
return rule.replacement + normalizedUrl.slice(normalizedPrefix.length);
}
}
}
return url;
}
/* ------------ 拦截 XHR ------------ */
const XHROPEN = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (method, url, ...rest) {
url = redirect(url);
return XHROPEN.call(this, method, url, ...rest);
};
/* ------------ 拦截 fetch ------------ */
const ORIG_FETCH = window.fetch;
window.fetch = function (input, init) {
if (typeof input === 'string') {
input = redirect(input);
} else if (input instanceof Request) {
const newUrl = redirect(input.url);
if (newUrl !== input.url) {
input = new Request(newUrl, input);
}
} else if (typeof input === 'object' && input !== null && 'url' in input) {
input.url = redirect(input.url);
}
return ORIG_FETCH.call(this, input, init);
};
/* ------------ 菜单管理 ------------ */
let menuCommands = [];
function buildMenu() {
menuCommands.forEach(id => GM_unregisterMenuCommand(id));
menuCommands = [];
const configCommand = GM_registerMenuCommand('#️⃣ 配置规则', () => {
const currentRules = getRules();
const prettyJson = JSON.stringify(currentRules, null, 2);
const updatedJson = prompt(
'输入 JSON 格式的规则:\n[\n {\n "name": "规则名称",\n "enabled": true,\n "prefix": "原前缀",\n "replacement": "新前缀"\n }\n]',
prettyJson
);
if (updatedJson !== null) {
try {
const newRules = JSON.parse(updatedJson);
if (Array.isArray(newRules)) {
saveRules(newRules);
showNotification('已保存,即时生效', 'success');
buildMenu();
} else {
alert('规则必须是数组格式,请检查后重试');
}
} catch (e) {
console.error('解析输入的 JSON 出错:', e);
alert('JSON 格式错误,请检查后重试');
}
}
});
menuCommands.push(configCommand);
const rules = getRules();
rules.forEach((rule, index) => {
const toggleCommand = GM_registerMenuCommand(
`${rule.enabled ? '✅' : '❌'} ${rule.name}`,
() => {
const newRules = getRules();
if (index >= 0 && index < newRules.length) {
newRules[index].enabled = !newRules[index].enabled;
saveRules(newRules);
showNotification(`${rule.name} 已${newRules[index].enabled ? '启用' : '禁用'},即时生效`, newRules[index].enabled ? 'success' : 'info');
buildMenu();
}
}
);
menuCommands.push(toggleCommand);
});
}
buildMenu();
/* ------------ 通知功能增强 ------------ */
function showNotification(message, type = 'info') {
if (!document.body) {
setTimeout(() => showNotification(message, type), 100);
return;
}
const notification = document.createElement('div');
notification.className = `tm-notification ${type}`; // 添加类型类名
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('show');
}, 10);
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 3000);
}
// 添加样式
const style = document.createElement('style');
style.textContent = `
.tm-notification {
position: fixed;
top: 20px;
right: 20px;
padding: 10px 20px;
border-radius: 4px;
font-family: Arial, sans-serif;
font-size: 14px;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
transform: translateY(-100%);
opacity: 0;
transition: transform 0.3s ease, opacity 0.3s ease;
z-index: 9999;
pointer-events: none;
}
.tm-notification.success {
background-color: #4CAF50; /* 启用的绿色背景 */
color: white;
}
.tm-notification.info {
background-color: #9E9E9E; /* 禁用的灰色背景 */
color: white;
}
.tm-notification.show {
transform: translateY(0);
opacity: 1;
}
`;
document.head.appendChild(style);
/* ------------ 页面加载时显示启用的规则(3秒后消失)------------ */
function showEnabledRules() {
const rules = getRules();
const enabledRules = rules.filter(rule => rule.enabled);
if (enabledRules.length > 0) {
const enabledRuleNames = enabledRules.map(rule => rule.name).join(',');
showNotification(`当前启用的规则:${enabledRuleNames}`, 'success');
}
}
// 页面加载完成后显示启用规则提示
window.addEventListener('load', showEnabledRules);
})();