网站跳转管理器(终极可拖拽版)

网页跳转 从A跳转到B 可以实现国内伪装垃圾网站跳转到真正的官网 网页跳转自定义 可保存规则 小球悬浮可拖动

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @license MIT
// @name         网站跳转管理器(终极可拖拽版)
// @namespace    https://greasyfork.org/users/123456
// @version      2.0
// @author       五香肉
// @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 = `<h2>跳转管理</h2>`;
        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 = `<h2>规则管理</h2>`;
        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)<margin && Math.abs(winRect.top - rulesRect.top)<margin){
                // 自动错开
                rulesWin.style.left = parseInt(win.style.left)+winRect.width+margin+"px";
                rulesWin.style.top = win.style.top;
                positions.rulesWindow={top:rulesWin.style.top,left:rulesWin.style.left};
                savePositions();
            }
        }
    }

    //---------------- 拖拽 ----------------//
    makeDraggable(ball,"jumpBall");
    makeDraggable(win,"jumpWindow");
    makeDraggable(rulesWin,"rulesWindow");

    //---------------- 设置位置 ----------------//
    setPosition(ball, positions.jumpBall);
    setPosition(win, positions.jumpWindow);
    setPosition(rulesWin, positions.rulesWindow);

    //---------------- 刷新/跳页保持浮窗显示 ----------------//
    if(windowStates.jumpWindow) { renderWindow(); win.style.display="block"; }
    if(windowStates.rulesWindow) { renderRulesWindow(); rulesWin.style.display="block"; }

    //---------------- 自动跳转 ----------------//
    function checkRedirect(){
        if(!autoRedirect) return;
        for(const r of rules){
            if(r.real && r.fakes.some(f => f && location.href.includes(f))){
                if(confirm(`检测到假网址,是否跳转到真实网址?\n${r.real}`)){
                    location.href = r.real;
                }
                break;
            }
        }
    }
    checkRedirect();

})();