anti-rickroll

修复了bug

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         anti-rickroll
// @version      13.0
// @description  修复了bug
// @author       dext
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      anti-rickroll.dext.top
// @run-at       document-start
// @namespace http://tampermonkey.net/
// ==/UserScript==

(function() {
    'use strict';

    const API_ENDPOINT = "https://anti-rickroll.dext.top";
    const cache = new Map();
    const host = window.location.hostname;

    // 状态判定
    const isForcePass = window.location.hash.includes('force-pass') || window.location.search.includes('force-pass');
    const isInfra = ["cloudflare.com", "workers.dev", "tampermonkey.net", "github.com", "greasyfork.org"].some(d => host.includes(d));

    GM_addStyle(`
        #rick-breaker-overlay { position: fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.98); z-index:2147483647; display:flex; justify-content:center; align-items:center; backdrop-filter:blur(25px); }
        .rick-card { background:#fff; padding:40px; border-radius:24px; width:360px; text-align:center; border:6px solid #ff4444; box-shadow: 0 20px 60px rgba(0,0,0,0.8); font-family: sans-serif; }
        .rick-btn { padding:15px; border-radius:12px; cursor:pointer; border:none; font-weight:bold; width:100%; margin-top:15px; font-size:16px; transition: 0.2s; }
        .btn-safe { background:#eee; color:#333; }
        .btn-danger { background:#ff4444; color:#fff; font-size:11px; opacity:0.5; }
        .btn-appeal { background: transparent; color: #888; font-size: 12px; margin-top: 10px; text-decoration: underline; cursor: pointer; border: none; }
        #rick-report-btn { position: fixed; bottom: 20px; left: 20px; z-index: 2147483646; background: rgba(0,0,0,0.7); color: white; padding: 8px 15px; border-radius: 20px; cursor: pointer; font-size: 13px; backdrop-filter: blur(5px); border: 1px solid rgba(255,255,255,0.2); display: flex; align-items: center; gap: 8px; }
    `);

    // --- 工具函数 ---
    function getCleanUrl(url) {
        try {
            const u = new URL(url);
            if (u.hostname.includes('google.com') && u.searchParams.has('q')) return u.searchParams.get('q');
            if (u.hostname.includes('baidu.com') && u.searchParams.has('url')) return u.searchParams.get('url');
            if (u.hostname.includes('bing.com') && u.pathname.includes('/ck/a')) {
                let rawU = u.searchParams.get('u');
                if (rawU) { try { return atob(rawU.substring(2).replace(/-/g, '+').replace(/_/g, '/')); } catch (e) {} }
            }
            return u.origin + u.pathname + u.search;
        } catch (e) { return url; }
    }

    function silenceMedia() {
        document.querySelectorAll('video, audio').forEach(m => { m.pause(); m.muted = true; });
    }

    // --- UI 渲染 ---
    function renderOverlay(title, url, isIncoming = false) {
        if (document.getElementById('rick-breaker-overlay')) return;
        const silencer = setInterval(silenceMedia, 200);
        const overlay = document.createElement('div');
        overlay.id = 'rick-breaker-overlay';
        overlay.innerHTML = `<div class="rick-card">
            <div style="font-size:50px">🛑</div>
            <h2 style="color:#ff4444; margin:10px 0;">${title}</h2>
            <p style="color:#444; font-size:13px;">此路径被识别为 <b>Rickroll</b>。</p>
            <button class="rick-btn btn-safe" id="r-back">${isIncoming ? '离开' : '关闭'}</button>
            <button class="rick-btn btn-danger" id="r-go">我非要看</button>
            <button class="btn-appeal" id="r-appeal">这是误报?点我申诉</button>
        </div>`;
        document.body.appendChild(overlay);

        document.getElementById('r-back').onclick = () => {
            clearInterval(silencer);
            if(isIncoming) { window.history.length > 1 ? window.history.back() : window.close(); }
            else { overlay.remove(); }
        };
        document.getElementById('r-appeal').onclick = (e) => {
            e.target.innerText = "正在复审...";
            GM_xmlhttpRequest({
                method: "POST", url: API_ENDPOINT,
                data: JSON.stringify({ url, isAppeal: true, title: document.title }),
                headers: { "Content-Type": "application/json" },
                onload: (res) => {
                    const d = JSON.parse(res.responseText);
                    if (!d.isRickroll) { e.target.innerText = "✅ 申诉成功"; window.location.replace(url + (url.includes('#') ? '&' : '#') + 'force-pass'); }
                    else { e.target.innerText = "❌ 申诉失败"; setTimeout(()=>e.target.innerText="这是误报?点我申诉", 2000); }
                }
            });
        };
        document.getElementById('r-go').onclick = () => {
            clearInterval(silencer); overlay.remove();
            window.location.replace(url + (url.includes('#') ? '&' : '#') + 'force-pass');
        };
    }

    // --- 核心监听:点击拦截 (始终运行) ---
    window.addEventListener('click', (e) => {
        const a = e.target.closest('a');
        if (!a || !a.href.startsWith('http')) return;
        const url = getCleanUrl(a.href);
        // 如果点击的是内链,或者是已经放行的链接,不拦截
        if (new URL(url).hostname === host || url.includes('force-pass')) return;

        e.preventDefault(); e.stopPropagation();
        GM_xmlhttpRequest({
            method: "POST", url: API_ENDPOINT, data: JSON.stringify({ url }),
            headers: { "Content-Type": "application/json" },
            onload: (res) => {
                const d = JSON.parse(res.responseText);
                if (d.isRickroll) renderOverlay("实时风险拦截", url, false);
                else { window.open(a.href, a.target || '_self'); }
            },
            onerror: () => { window.open(a.href, a.target || '_self'); }
        });
    }, true);

    // --- 举报按钮 (仅在非放行、非白名单页显示) ---
    function createReportButton() {
        if (isForcePass || isInfra || document.getElementById('rick-report-btn')) return;
        const btn = document.createElement('div');
        btn.id = 'rick-report-btn';
        btn.innerHTML = `<span id="rick-do-report">🛡️ 我被骗了!</span>`;
        document.body.appendChild(btn);
        btn.onclick = () => {
            btn.innerText = "🔍 复审中...";
            GM_xmlhttpRequest({
                method: "POST", url: API_ENDPOINT,
                data: JSON.stringify({ url: window.location.href, isFeedback: true, title: document.title, snippet: document.body.innerText.substring(0, 500) }),
                headers: { "Content-Type": "application/json" },
                onload: (res) => {
                    if (JSON.parse(res.responseText).isRickroll) { btn.innerText = "✅ 举报成功"; renderOverlay("举报生效拦截", window.location.href, true); }
                    else { btn.innerText = "❌ 判定安全"; setTimeout(() => btn.remove(), 2000); }
                }
            });
        };
    }

    // --- 入境自检 (放行页和白名单页跳过) ---
    if (!isForcePass && !isInfra && !/google|baidu|bing/.test(host)) {
        GM_xmlhttpRequest({
            method: "POST", url: API_ENDPOINT, data: JSON.stringify({ url: window.location.href }),
            headers: { "Content-Type": "application/json" },
            onload: (res) => {
                try { if (JSON.parse(res.responseText).isRickroll) { window.stop(); renderOverlay("入境风险拦截", window.location.href, true); } } catch(e) {}
            }
        });
    }

    window.addEventListener('load', createReportButton);
})();