// ==UserScript==
// @license MIT
// @name 网站跳转管理器(终极可拖拽版)
// @namespace https://greasyfork.org/users/123456
// @version 2.0
// @description 网页跳转 从A跳转到B 可以实现国内伪装垃圾网站跳转到真正的官网 网页跳转自定义 可保存规则 小球悬浮可拖动
// @match *://*/*
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==
(function() {
'use strict';
//---------------- 数据存储 ----------------//
let rules = GM_getValue("rules", []);
let currentRuleIndex = GM_getValue("currentRuleIndex", -1);
let autoRedirect = GM_getValue("autoRedirect", true);
let windowStates = GM_getValue("windowStates", {jumpWindow:false, rulesWindow:false});
let positions = GM_getValue("positions", {jumpBall:{top:"50%", left:"95%"}, jumpWindow:{top:"50%", left:"35%"}, rulesWindow:{top:"50%", left:"65%"}});
function saveRules() {
GM_setValue("rules", rules);
GM_setValue("currentRuleIndex", currentRuleIndex);
GM_setValue("autoRedirect", autoRedirect);
}
function saveWindowStates() {
GM_setValue("windowStates", windowStates);
}
function savePositions() {
GM_setValue("positions", positions);
}
//---------------- 样式 ----------------//
const style = document.createElement("style");
style.textContent = `
#jumpBall {
position: fixed; width: 40px; height: 40px; background: #cce7ff;
border-radius: 50%; cursor: move; z-index: 10000;
display: flex; align-items: center; justify-content: center;
font-weight: bold; color: #333; box-shadow: 0 0 8px rgba(0,0,0,0.3);
transition: transform 0.2s;
}
#jumpBall:hover { transform: scale(1.1); }
#jumpWindow, #rulesWindow {
position: fixed; transform: translate(-50%, -50%);
background: #f9f9fb; border-radius: 12px;
box-shadow: 0 0 15px rgba(0,0,0,0.2);
padding: 16px; z-index: 10001; display: none;
width: 500px; max-height: 80%; overflow-y: auto;
font-family: "Segoe UI", Arial, sans-serif;
cursor: move;
}
#jumpWindow h2, #rulesWindow h2 { margin: 0 0 10px 0; font-size: 18px; color: #333; }
.fieldRow { display: flex; gap: 8px; margin-bottom: 8px; }
.fieldRow input[type="text"] {
flex: 1; padding: 6px 8px; border: 1px solid #ccc; border-radius: 6px;
min-width: 0;
}
.btn {
flex: 1; text-align: center;
background: #4cafef; color: #fff; border: none; border-radius: 6px;
padding: 6px 10px; cursor: pointer; transition: 0.2s; font-size: 14px;
}
.btn:hover { background: #42a5e5; }
.btn.danger { background: #f44336; }
.ruleItem {
padding: 6px 8px; margin-bottom: 6px;
border-radius: 6px; background: #fff; border: 1px solid #ddd;
font-size: 14px;
display: flex; justify-content: space-between; align-items: center;
}
.ruleItem span {
flex: 1;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ruleItem button { margin-left: 4px; }
.topBar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; cursor: default; }
.closeBtn { background: transparent; border: none; font-size: 16px; cursor: pointer; color: #666; }
.closeBtn:hover { color: #000; }
.btnRow { display: flex; gap: 8px; margin-top: 10px; }
`;
document.head.appendChild(style);
//---------------- 创建元素 ----------------//
const ball = document.createElement("div");
ball.id = "jumpBall";
ball.textContent = "⇆";
document.body.appendChild(ball);
const win = document.createElement("div");
win.id = "jumpWindow";
document.body.appendChild(win);
const rulesWin = document.createElement("div");
rulesWin.id = "rulesWindow";
document.body.appendChild(rulesWin);
//---------------- 工具函数 ----------------//
function setPosition(el, pos) {
el.style.top = pos.top;
el.style.left = pos.left;
}
function makeDraggable(el, key) {
let isDragging = false, offsetX=0, offsetY=0;
el.addEventListener("mousedown", (e) => {
if(e.target.classList.contains("closeBtn") || e.target.tagName==="INPUT" || e.target.tagName==="BUTTON") return;
isDragging=true;
const rect = el.getBoundingClientRect();
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
e.preventDefault();
});
document.addEventListener("mousemove", (e)=>{
if(!isDragging) return;
let x = e.clientX - offsetX;
let y = e.clientY - offsetY;
el.style.left = x + "px";
el.style.top = y + "px";
});
document.addEventListener("mouseup", ()=>{
if(isDragging){
isDragging=false;
positions[key]={top:el.style.top,left:el.style.left};
savePositions();
}
});
}
//---------------- 渲染主窗口 ----------------//
function renderWindow() {
win.innerHTML = "";
const topBar = document.createElement("div");
topBar.className = "topBar";
topBar.innerHTML = `
跳转管理
`;
const closeBtn = document.createElement("button");
closeBtn.className = "closeBtn";
closeBtn.textContent = "×";
closeBtn.onclick = () => { win.style.display = "none"; windowStates.jumpWindow=false; saveWindowStates(); };
topBar.appendChild(closeBtn);
win.appendChild(topBar);
const toggleRow = document.createElement("div");
toggleRow.className = "fieldRow";
const toggle = document.createElement("input");
toggle.type = "checkbox";
toggle.checked = autoRedirect;
toggle.onchange = () => { autoRedirect = toggle.checked; saveRules(); };
const toggleLabel = document.createElement("label");
toggleLabel.textContent = "启用自动跳转";
toggleLabel.style.marginLeft = "8px";
toggleRow.appendChild(toggle);
toggleRow.appendChild(toggleLabel);
win.appendChild(toggleRow);
if(currentRuleIndex < 0){
rules.push({ real: "", fakes: [""] });
currentRuleIndex = rules.length - 1;
saveRules();
}
let rule = rules[currentRuleIndex];
const realRow = document.createElement("div");
realRow.className = "fieldRow";
const realInput = document.createElement("input");
realInput.type = "text";
realInput.placeholder = "真网站 URL";
realInput.value = rule.real || "";
realRow.appendChild(realInput);
win.appendChild(realRow);
const fakeContainer = document.createElement("div");
fakeContainer.style.display = "flex";
fakeContainer.style.flexDirection = "column";
win.appendChild(fakeContainer);
function renderFakes() {
fakeContainer.innerHTML = "";
rule.fakes.forEach((f, idx) => {
const row = document.createElement("div");
row.className = "fieldRow";
const input = document.createElement("input");
input.type = "text";
input.placeholder = "假网站 URL";
input.value = f;
row.appendChild(input);
const delBtn = document.createElement("button");
delBtn.textContent = "-";
delBtn.className = "btn";
delBtn.onclick = () => {
if(rule.fakes.length > 1){
rule.fakes.splice(idx,1);
renderFakes();
}
};
row.appendChild(delBtn);
fakeContainer.appendChild(row);
});
const addBtn = document.createElement("button");
addBtn.textContent = "+假网站";
addBtn.className = "btn";
addBtn.onclick = () => { rule.fakes.push(""); renderFakes(); };
fakeContainer.appendChild(addBtn);
}
renderFakes();
const btnRow = document.createElement("div");
btnRow.className = "btnRow";
const saveBtn = document.createElement("button");
saveBtn.textContent = "保存规则";
saveBtn.className = "btn";
saveBtn.onclick = () => {
rule.real = realInput.value;
const fakeInputs = fakeContainer.querySelectorAll("input");
rule.fakes = Array.from(fakeInputs).map(inp => inp.value);
rules[currentRuleIndex] = rule;
saveRules();
};
btnRow.appendChild(saveBtn);
const addRuleBtn = document.createElement("button");
addRuleBtn.textContent = "+新增规则";
addRuleBtn.className = "btn";
addRuleBtn.onclick = () => {
rules.push({ real: "", fakes: [""] });
currentRuleIndex = rules.length - 1;
saveRules();
renderWindow();
};
btnRow.appendChild(addRuleBtn);
const manageBtn = document.createElement("button");
manageBtn.textContent = "管理规则";
manageBtn.className = "btn";
manageBtn.onclick = () => { renderRulesWindow(); rulesWin.style.display = "block"; windowStates.rulesWindow=true; saveWindowStates(); adjustWindows(); };
btnRow.appendChild(manageBtn);
win.appendChild(btnRow);
}
//---------------- 渲染规则窗口 ----------------//
function renderRulesWindow(){
rulesWin.innerHTML = "";
const topBar = document.createElement("div");
topBar.className = "topBar";
topBar.innerHTML = `规则管理
`;
const closeBtn = document.createElement("button");
closeBtn.className = "closeBtn";
closeBtn.textContent = "×";
closeBtn.onclick = () => { rulesWin.style.display = "none"; windowStates.rulesWindow=false; saveWindowStates(); };
topBar.appendChild(closeBtn);
rulesWin.appendChild(topBar);
rules.forEach((r, idx) => {
const item = document.createElement("div");
item.className = "ruleItem";
const text = document.createElement("span");
text.textContent = `真:${r.real} 假:${r.fakes.join(",")}`;
item.appendChild(text);
const editBtn = document.createElement("button");
editBtn.textContent = "编辑";
editBtn.className = "btn";
editBtn.onclick = () => { currentRuleIndex = idx; renderWindow(); win.style.display = "block"; windowStates.jumpWindow=true; saveWindowStates(); adjustWindows(); };
const delBtn = document.createElement("button");
delBtn.textContent = "删除";
delBtn.className = "btn danger";
delBtn.onclick = () => {
rules.splice(idx, 1);
if(currentRuleIndex >= rules.length) currentRuleIndex = rules.length - 1;
saveRules();
renderRulesWindow();
};
item.appendChild(editBtn);
item.appendChild(delBtn);
rulesWin.appendChild(item);
});
const btnRow = document.createElement("div");
btnRow.className = "btnRow";
const addRuleBtn = document.createElement("button");
addRuleBtn.textContent = "+新增规则";
addRuleBtn.className = "btn";
addRuleBtn.onclick = () => {
rules.push({ real: "", fakes: [""] });
currentRuleIndex = rules.length - 1;
saveRules();
renderRulesWindow();
};
btnRow.appendChild(addRuleBtn);
const clearAllBtn = document.createElement("button");
clearAllBtn.textContent = "⚠ 清空规则";
clearAllBtn.className = "btn danger";
clearAllBtn.onclick = () => {
if(confirm("确定要清空所有规则吗?此操作不可撤销!")){
rules = [];
currentRuleIndex = -1;
saveRules();
renderRulesWindow();
}
};
btnRow.appendChild(clearAllBtn);
rulesWin.appendChild(btnRow);
}
//---------------- 逻辑 ----------------//
ball.onclick = () => {
windowStates.jumpWindow=true;
saveWindowStates();
renderWindow();
win.style.display="block";
adjustWindows();
};
//---------------- 自动避开窗口 ----------------//
function adjustWindows(){
const margin = 10;
if(windowStates.jumpWindow && windowStates.rulesWindow){
const winRect = win.getBoundingClientRect();
const rulesRect = rulesWin.getBoundingClientRect();
if(Math.abs(winRect.left - rulesRect.left) f && location.href.includes(f))){
if(confirm(`检测到假网址,是否跳转到真实网址?\n${r.real}`)){
location.href = r.real;
}
break;
}
}
}
checkRedirect();
})();