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.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

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