Greasy Fork

Greasy Fork is available in English.

anti-rickroll

申诉

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         anti-rickroll
// @version      12.6
// @description  申诉
// @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 queue = [];
    let activeRequests = 0;
    const MAX_CONCURRENT = 3;

    // --- 0. 免死金牌判定 ---
    if (window.location.hash.includes('force-pass') || window.location.search.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.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-safe:hover { background:#ddd; }
        .btn-danger { background:#ff4444; color:#fff; font-size:11px; opacity:0.5; }
        .btn-danger:hover { opacity:1; }
        .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; }
        a.rickroll-danger { outline: 3px dashed #ff4444 !important; outline-offset: 2px; background: rgba(255,0,0,0.1) !important; }
    `);

    // --- 1. 工具函数 ---
    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 isInternal(url) {
        try {
            const t = new URL(url);
            const curr = window.location.hostname;
            if (curr.includes('bing.com') && t.pathname.includes('/ck/a')) return false;
            return t.hostname === curr;
        } catch (e) { return true; }
    }

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

    function smartExit(isIncoming) {
        if (!isIncoming) { document.getElementById('rick-breaker-overlay')?.remove(); return; }
        if (document.referrer && !document.referrer.includes(window.location.hostname)) {
            window.location.replace(document.referrer);
        } else if (window.history.length > 1) {
            window.history.back();
        } else {
            window.close(); setTimeout(() => { window.location.href = "about:blank"; }, 150);
        }
    }

    // --- 2. 申诉逻辑 ---
    function appealRickroll(url, btnElement) {
        const originalText = btnElement.innerText;
        btnElement.innerText = "正在提交申诉...";
        btnElement.disabled = true;

        GM_xmlhttpRequest({
            method: "POST",
            url: API_ENDPOINT,
            data: JSON.stringify({
                url: url,
                isAppeal: true,
                title: document.title,
                snippet: document.body ? document.body.innerText.substring(0, 500) : ""
            }),
            headers: { "Content-Type": "application/json" },
            onload: (res) => {
                const d = JSON.parse(res.responseText);
                if (!d.isRickroll) {
                    btnElement.innerText = "✅ 申诉成功!";
                    setTimeout(() => {
                        window.location.href = url + (url.includes('#') ? '&' : '#') + 'force-pass';
                    }, 1000);
                } else {
                    btnElement.innerText = "❌ AI 依然认为是 Rickroll";
                    setTimeout(() => { btnElement.innerText = originalText; btnElement.disabled = false; }, 2000);
                }
            }
        });
    }

    // --- 3. 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; line-height:1.5;">此链接已被云端标记为 <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 || document.documentElement).appendChild(overlay);

        document.getElementById('r-back').onclick = () => { clearInterval(silencer); smartExit(isIncoming); };
        document.getElementById('r-appeal').onclick = (e) => appealRickroll(url, e.target);
        document.getElementById('r-go').onclick = () => {
            clearInterval(silencer); overlay.remove();
            const jumpUrl = url + (url.includes('#') ? '&' : '#') + 'force-pass';
            if (isIncoming) window.location.replace(jumpUrl);
            else { const a = document.createElement('a'); a.href = jumpUrl; a.target = "_blank"; a.click(); }
        };
    }

    // --- 4. 扫描与监听 ---
    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 d = JSON.parse(res.responseText);
                    cache.set(url, { isRickroll: !!d.isRickroll, status: 'done' });
                    if (d.isRickroll) document.querySelectorAll(`a`).forEach(a => { if(getCleanUrl(a.href) === url) a.classList.add('rickroll-danger'); });
                } catch (e) {}
                activeRequests--; processQueue();
            },
            onerror: () => { activeRequests--; processQueue(); }
        });
    }

    function scan() {
        document.querySelectorAll('a[href]').forEach(a => {
            const url = getCleanUrl(a.href);
            if (!url.startsWith('http') || isInternal(url) || cache.has(url)) return;
            cache.set(url, { isRickroll: false, status: 'pending' });
            queue.push(url);
        });
        processQueue();
    }

    window.addEventListener('click', (e) => {
        const a = e.target.closest('a');
        if (!a || !a.href.startsWith('http')) return;
        const url = getCleanUrl(a.href);
        if (isInternal(url) || a.dataset.rickSafe === '1' || url.includes('force-pass')) return;

        const res = cache.get(url);
        if (res && res.status === 'done' && res.isRickroll) {
            e.preventDefault(); e.stopPropagation();
            renderOverlay("链路风险拦截", url, false);
            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);
                cache.set(url, { isRickroll: !!d.isRickroll, status: 'done' });
                if (d.isRickroll) renderOverlay("实时风险拦截", url, false);
                else { a.dataset.rickSafe = '1'; a.click(); }
            },
            onerror: () => { a.dataset.rickSafe = '1'; a.click(); }
        });
    }, true);

    function createReportButton() {
        if (document.getElementById('rick-report-btn') || sessionStorage.getItem('rick-btn-hidden') === '1') return;
        const btn = document.createElement('div');
        btn.id = 'rick-report-btn';
        btn.innerHTML = `<span id="rick-do-report">🛡️ 我被骗了!</span><span id="rick-close-report" style="margin-left:8px;opacity:0.5">×</span>`;
        document.body.appendChild(btn);
        document.getElementById('rick-do-report').onclick = () => {
            const b = document.getElementById('rick-report-btn');
            b.innerText = "🔍 AI 复审中...";
            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, 800) }),
                headers: { "Content-Type": "application/json" },
                onload: (res) => {
                    if (JSON.parse(res.responseText).isRickroll) { b.innerText = "✅ 举报成功!"; renderOverlay("举报生效拦截", window.location.href, true); }
                    else { b.innerText = "❌ AI 判定安全"; setTimeout(() => b.remove(), 2000); }
                }
            });
        };
        document.getElementById('rick-close-report').onclick = (e) => { e.stopPropagation(); btn.remove(); sessionStorage.setItem('rick-btn-hidden', '1'); };
    }

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

    window.addEventListener('load', () => { createReportButton(); setInterval(scan, 4000); });
})();