Greasy Fork is available in English.

Anti Rickroll (Universal)

A lightweight, robust userscript designed to protect users from known Rickrolls on YouTube. Optimized specifically for mobile environments (iOS Userscripts) and modern browser security policies.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name            Anti Rickroll (Universal)
// @version         1.7.5
// @description     A lightweight, robust userscript designed to protect users from known Rickrolls on YouTube. Optimized specifically for mobile environments (iOS Userscripts) and modern browser security policies.
// @author          JMcrafter26
// @match           https://*.youtube.com/watch?*
// @match           https://youtube.com/watch?*
// @match           https://*.youtube.com/embed/*
// @match           https://youtube.com/embed/*
// @match           https://*.youtu.be/*
// @match           https://youtu.be/*
// @run-at          document-start
// @grant           none
// @namespace http://github.com/sepehr
// ==/UserScript==

(function() {
    'use strict';

    const config = {
        storageKey: 'antiRickroll',
        bypassPrefix: 'rr_bypass_',
        icon: "https://files.catbox.moe/ylj5xb.png",
        apiUrl: "https://api.jm26.net/rickroll-db/?type=get&api=userscript",
        statsUrl: "https://api.jm26.net/extensions/anti-rickroll/stats.php",
        updateInterval: 86400000,
        fallbackDatabase: ["dQw4w9WgXcQ", "oHg5SJYRHA0", "cvh0nX08nRw", "xfr64zoBTAQ", "iik25wqIuFo"]
    };

    function generateTraceId() {
        const hex = () => Math.floor((1 + Math.random()) * 0x100000000).toString(16).substring(1);
        return `${hex()}${hex()}`;
    }

    // Using fetch with keepalive: true to survive window.stop()
    function reportStats(id, type, trace) {
        try {
            const formData = new URLSearchParams();
            formData.append('id', id);
            formData.append('type', type);
            formData.append('trace', trace);
            formData.append('version', '1.7.5');

            fetch(config.statsUrl, {
                method: 'POST',
                body: formData,
                keepalive: true, // CRITICAL: Ensures request survives page unloading
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                }
            }).catch(e => console.error("Stats Error:", e));
        } catch (e) {
            console.error("Stats Build Error:", e);
        }
    }

    function getFullDatabase() {
        let localIds = [];
        try {
            const data = localStorage.getItem(config.storageKey);
            if (data) {
                const parsed = JSON.parse(data);
                localIds = Array.isArray(parsed) ? parsed : (parsed.database || []);
            }
        } catch (e) {}
        return [...new Set([...config.fallbackDatabase, ...localIds])];
    }

    async function updateDatabase() {
        const now = Date.now();
        let store = {};
        try { store = JSON.parse(localStorage.getItem(config.storageKey) || "{}"); } catch(e) {}
        if (store.lastUpdate && (now - store.lastUpdate < config.updateInterval)) return;

        try {
            const response = await fetch(config.apiUrl);
            const json = await response.json();
            if (json && json.ids) {
                const existing = Array.isArray(store) ? store : (store.database || []);
                localStorage.setItem(config.storageKey, JSON.stringify({
                    database: [...new Set([...existing, ...json.ids])],
                    lastUpdate: now
                }));
            }
        } catch (e) {}
    }

    function checkVideo() {
        let videoId = null;
        let contentType = "video";
        const url = window.location.href;

        if (url.includes('v=')) {
            videoId = new URLSearchParams(window.location.search).get('v');
        } else if (url.includes('/embed/')) {
            videoId = url.split('/embed/')[1].split(/[?#]/)[0];
            contentType = "embed";
        } else if (url.includes('youtu.be/')) {
            videoId = url.split('youtu.be/')[1].split(/[?#]/)[0];
        }

        if (!videoId || sessionStorage.getItem(config.bypassPrefix + videoId) === 'true') {
            const blocker = document.getElementById('antirickroll-wrapper');
            if (blocker) {
                blocker.remove();
                document.documentElement.style.overflow = '';
            }
            return;
        }

        if (getFullDatabase().includes(videoId)) {
            const traceId = generateTraceId();
            
            // 1. Send stats FIRST (asynchronously)
            reportStats(videoId, contentType, traceId);

            // 2. Mute audio
            const video = document.querySelector('video');
            if (video) { video.muted = true; video.pause(); }

            // 3. Render block
            document.title = "⚠️ Rickroll Blocked!";
            renderBlocker(videoId, contentType, traceId);
        }
    }

    function renderBlocker(id, type, trace) {
        if (document.getElementById('antirickroll-wrapper')) return;
        
        // 4. Stop window execution AFTER firing the request
        window.stop();

        const blocker = document.createElement('div');
        blocker.id = 'antirickroll-wrapper';
        blocker.setAttribute('style', 'position:fixed !important; top:0 !important; left:0 !important; width:100vw !important; height:100vh !important; z-index:2147483647 !important; background:#fff; overflow:auto !important; display:block !important;');

        blocker.innerHTML = `
          <style>
              #antirickroll-wrapper { --google-gray-700: rgb(95, 99, 104); color: var(--google-gray-700); word-wrap: break-word; font-family: 'Segoe UI', Tahoma, sans-serif; font-size: 16px; line-height: 1.4; }
              .nav-wrapper .secondary-button{ background: #fff; border: 1px solid rgb(154, 160, 166); color: var(--google-gray-700); padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 14px;}
              .ssl button{ border: 0; border-radius: 4px; color: #fff; cursor: pointer; float: right; padding: 6px 12px; font-family: 'Segoe UI', Tahoma, sans-serif;}
              .ssl .primary-button { background: rgb(26, 115, 232); }
              .details-code{ color: var(--google-gray-700); font-size: .75em; margin-top: 15px; border-top: 1px solid #eee; padding-top: 10px; line-height: 1.8;}
              h1{ color: rgb(32, 33, 36); font-size: 1.4em; font-weight: normal; margin-bottom: 12px; margin-top: 0;}
              .interstitial-wrapper{ box-sizing: border-box; margin: 10vh auto 0; max-width: 90% !important; width: 500px; padding: 20px;}
              .nav-wrapper{ margin-top: 25px; display: flex; justify-content: flex-end; gap: 8px; }
              .icon{ height: 60px; margin: 0 0 20px; width: 60px; background-image: url("${config.icon}"); background-size: contain; background-repeat: no-repeat;}
              @media (prefers-color-scheme: dark){
                #antirickroll-wrapper { background: #202124 !important; color: rgb(155, 160, 165);}
                .nav-wrapper .secondary-button{ background: #202124; border: 1px solid rgb(154, 160, 166); color: rgb(138, 180, 248);}
                .ssl .primary-button { background: rgb(129, 162, 208); color: #202124; }
                .details-code { border-top-color: #3c4043; color: #9aa0a6; }
                h1 { color: rgb(155, 160, 165); }
              }
          </style>
          <div class="ssl">
              <div class="interstitial-wrapper">
                  <div class="icon"></div>
                  <h1>You are about to get Rickrolled!</h1>
                  <p>This ${type} is a known Rickroll and has been intercepted.</p>
                  <div class="nav-wrapper">
                      <button id="rr-continue" class="secondary-button">Continue</button>
                      <button class="primary-button" onclick="history.length>1?history.back():window.close();">Back to safety</button>
                  </div>
                  <div class="details-code">
                      Trace ID: <b>${trace}</b><br>
                      Video ID: <b>${id}</b><br>
                      Status: <b>BLOCKED_${type.toUpperCase()}</b>
                  </div>
              </div>
          </div>`;

        (document.documentElement || document.body).appendChild(blocker);
        document.documentElement.style.overflow = 'hidden';

        document.getElementById('rr-continue').onclick = function() {
            sessionStorage.setItem(config.bypassPrefix + id, 'true');
            location.reload();
        };
    }

    window.addEventListener('yt-navigate-finish', checkVideo);
    let lastUrl = location.href;
    new MutationObserver(() => {
        if (location.href !== lastUrl) {
            lastUrl = location.href;
            checkVideo();
        }
    }).observe(document, {subtree: true, childList: true});

    checkVideo();
    updateDatabase();
})();