Greasy Fork

Greasy Fork is available in English.

anti-rickroll

fuck-rickroll

当前为 2026-02-07 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         anti-rickroll
// @namespace    http://tampermonkey.net/
// @version      11.3
// @description  fuck-rickroll
// @author       dext
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      anti-rickroll.dext.top
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    const API_ENDPOINT = "https://anti-rickroll.dext.top";
    const cache = new Map();
    const queue = [];
    let activeRequests = 0;
    const MAX_CONCURRENT = 3;

    // 如果 URL 包含强制放行标记,彻底退出
    if (window.location.hash.includes('force-pass')) return;

    GM_addStyle(`
        #rick-breaker-overlay { position: fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.9); z-index:2147483647; display:flex; justify-content:center; align-items:center; backdrop-filter:blur(20px); }
        .rick-card { background:#fff; padding:40px; border-radius:24px; width:350px; text-align:center; border:5px solid #ff4444; box-shadow: 0 20px 50px rgba(0,0,0,0.5); font-family: sans-serif; }
        .rick-btn { padding:14px; border-radius:12px; cursor:pointer; border:none; font-weight:bold; width:100%; margin-top:12px; font-size:15px; }
        .btn-safe { background:#f0f0f0; color:#333; }
        .btn-danger { background:#ff4444; color:#fff; font-size:11px; opacity:0.6; }
        a.rickroll-danger { outline: 2px dashed #ff4444 !important; background: rgba(255,0,0,0.1) !important; }
    `);

    // 核心判定:是否为真正的内链
    function isInternalLink(url) {
        try {
            const target = new URL(url);
            const current = window.location;
            // 只有协议和域名完全一致,才判定为内链
            return target.protocol === current.protocol && target.hostname === current.hostname;
        } catch (e) {
            return true; // 无法解析的通常是相对路径,视为内链
        }
    }

    // 脱壳逻辑
    function getRealUrl(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');
        } catch (e) {}
        return url;
    }

    function silencePage() {
        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(silencePage, 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:#666; font-size:14px;">已拦截 Rickroll 风险链接。</p>
                <button class="rick-btn btn-safe" id="r-back">立刻返回</button>
                <button class="rick-btn btn-danger" id="r-go">我非要看 (强制放行)</button>
            </div>`;

        (document.body || document.documentElement).appendChild(overlay);

        document.getElementById('r-back').onclick = () => {
            if (isIncoming && window.history.length > 1) {
                window.history.back();
            } else {
                overlay.remove();
                clearInterval(silencer);
                // 如果是本页强行跳过来的,点返回要尝试跳回
                if (!isIncoming) window.stop();
            }
        };

        document.getElementById('r-go').onclick = () => {
            clearInterval(silencer);
            overlay.remove();
            // 拼接强制放行 Hash
            const jumpUrl = url + (url.includes('#') ? '&' : '#') + 'force-pass';
            window.location.href = jumpUrl;
        };
    }

    // 入境自检
    function checkIncoming() {
        const url = window.location.href;
        if (url.length < 25 || /google|baidu|bing/.test(window.location.hostname)) return;

        GM_xmlhttpRequest({
            method: "POST",
            url: API_ENDPOINT,
            data: JSON.stringify({ url }),
            headers: { "Content-Type": "application/json" },
            onload: (res) => {
                try {
                    const data = JSON.parse(res.responseText);
                    if (data.isRickroll) {
                        silencePage();
                        renderOverlay("入境风险拦截", url, true);
                    }
                } catch (e) {}
            }
        });
    }

    // 并发预检
    function processQueue() {
        if (queue.length === 0 || activeRequests >= MAX_CONCURRENT) return;
        const url = queue.shift();
        activeRequests++;
        GM_xmlhttpRequest({
            method: "POST",
            url: API_ENDPOINT,
            data: JSON.stringify({ url }),
            headers: { "Content-Type": "application/json" },
            onload: (res) => {
                try {
                    const data = JSON.parse(res.responseText);
                    cache.set(url, data.isRickroll);
                    if (data.isRickroll) {
                        document.querySelectorAll('a').forEach(a => {
                            if(getRealUrl(a.href) === url) a.classList.add('rickroll-danger');
                        });
                    }
                } catch (e) {}
                activeRequests--; processQueue();
            },
            onerror: () => { activeRequests--; processQueue(); }
        });
    }

    function scanPage() {
        const EXCLUDE = /\.(jpg|jpeg|png|gif|css|js|woff|zip|pdf|mp4)($|\?)/i;
        document.querySelectorAll('a[href]').forEach(a => {
            const rawUrl = a.href;
            const realUrl = getRealUrl(rawUrl);

            // 严格跳过:1. 必须是 http 2. 必须不是内链 3. 排除静态资源 4. 没扫过
            if (!realUrl.startsWith('http') || isInternalLink(realUrl) || EXCLUDE.test(realUrl) || cache.has(realUrl)) return;

            cache.set(realUrl, 'pending');
            queue.push(realUrl);
        });
        processQueue();
    }

    // 点击接管 (核心:拦截本标签页点击)
    window.addEventListener('click', function(e) {
        const a = e.target.closest('a');
        if (!a || !a.href || !a.href.startsWith('http')) return;

        const realUrl = getRealUrl(a.href);
        // 如果是内链,直接放行,不进入拦截逻辑
        if (isInternalLink(realUrl)) return;
        // 如果已经带了标记,放行
        if (realUrl.includes('force-pass') || a.dataset.rickSafe === '1') return;

        e.preventDefault();
        e.stopPropagation();

        GM_xmlhttpRequest({
            method: "POST",
            url: API_ENDPOINT,
            data: JSON.stringify({ url: realUrl }),
            headers: { "Content-Type": "application/json" },
            onload: (res) => {
                try {
                    const data = JSON.parse(res.responseText);
                    if (data.isRickroll) {
                        renderOverlay("重定向拦截", realUrl, false);
                    } else {
                        a.dataset.rickSafe = '1';
                        // 判定是否需要新开窗口
                        if (a.target === '_blank' || e.ctrlKey || e.metaKey) {
                            window.open(realUrl, '_blank');
                        } else {
                            window.location.href = realUrl;
                        }
                    }
                } catch (e) { window.location.href = realUrl; }
            }
        });
    }, true);

    checkIncoming();
    window.addEventListener('DOMContentLoaded', scanPage);
    setInterval(scanPage, 5000);
})();