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.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

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