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.

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==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();
})();