Greasy Fork is available in English.

4chan - Vanilla+

This script removes/bypasses the redirect URL from 4chan links (if you've the Linkify URLs option enabled on 4chan), loads all media in a thread as soon as you enter it, and shows catbox videos and images directly on the thread.

// ==UserScript==
// @name         4chan - Vanilla+
// @namespace    http://tampermonkey.net/
// @version      2.41
// @description  This script removes/bypasses the redirect URL from 4chan links (if you've the Linkify URLs option enabled on 4chan), loads all media in a thread as soon as you enter it, and shows catbox videos and images directly on the thread.
// @author       Airman
// @match        https://*.4chan.org/*
// @grant        none
// @run-at       document-end
// @license GNU GPLv3
// ==/UserScript==

(function() {
    'use strict';

    function bypassRedirects() {
        // Select all links on the page
        let links = document.querySelectorAll('a');
        // Loop through each link
        links.forEach(link => {
            // Check if the link URL matches the 4chan redirect URL
            if (link.href.startsWith('https://sys.4chan.org/derefer?url=')) {
                // Extract the target URL from the redirect URL by removing the prefix
                let targetUrl = link.href.replace('https://sys.4chan.org/derefer?url=', '');
                // Decode the target URL to get the actual URL
                targetUrl = decodeURIComponent(targetUrl);
                // If the target URL is found, update the link href to it
                if (targetUrl) {
                    link.href = targetUrl;
                    console.log(`Updated link: ${link.href}`);
                }
            }
        });
    }

    function renderCatboxMedia() {
        // Render all Catbox images and videos inline
        let catboxLinks = document.querySelectorAll('a[href*="catbox.moe"]');
        const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
        const videoExtensions = ['.mp4', '.webm'];

        catboxLinks.forEach(link => {
            let mediaUrl = link.href;

            // Check if the media has already been rendered for this link
            if (link.classList.contains('media-rendered')) {
                return;
            }

            // Mark the link as processed to avoid duplicate rendering
            link.classList.add('media-rendered');

            // Check if the URL is an image file
            if (imageExtensions.some(ext => mediaUrl.endsWith(ext))) {
                let thumbImg = new Image();
                thumbImg.src = mediaUrl;
                thumbImg.style.maxWidth = '150px';
                thumbImg.style.display = 'block';
                thumbImg.style.marginTop = '10px';
                thumbImg.style.cursor = 'pointer';
                thumbImg.classList.add('rendered-media');

                let fullImg = new Image();
                fullImg.src = mediaUrl;
                fullImg.style.maxWidth = '100%';
                fullImg.style.display = 'none';
                fullImg.style.marginTop = '10px';
                fullImg.style.cursor = 'pointer';
                fullImg.classList.add('rendered-media');

                thumbImg.addEventListener('click', () => {
                    thumbImg.style.display = 'none';
                    fullImg.style.display = 'block';
                });

                fullImg.addEventListener('click', () => {
                    fullImg.style.display = 'none';
                    thumbImg.style.display = 'block';
                });

                // Insert the thumbnail image after the link
                link.parentNode.insertBefore(thumbImg, link.nextSibling);
                link.parentNode.insertBefore(fullImg, thumbImg.nextSibling);
                console.log(`Rendered Catbox image: ${mediaUrl}`);

            } else if (videoExtensions.some(ext => mediaUrl.endsWith(ext))) {
                // Fetch the video thumbnail
                fetchVideoThumbnail(mediaUrl).then(thumbnailUrl => {
                    let thumbDiv = document.createElement('div');
                    let fullVideo = document.createElement('video');
                    let closeWrapper = document.createElement('span');
                    let closeLink = document.createElement('a');

                    closeWrapper.textContent = ' - [';
                    closeLink.textContent = 'Close';
                    closeLink.style.cursor = 'pointer';
                    closeLink.style.textDecoration = 'underline';
                    closeWrapper.style.display = 'none';

                    closeWrapper.appendChild(closeLink);
                    closeWrapper.appendChild(document.createTextNode(']'));

                    fullVideo.src = mediaUrl;
                    fullVideo.controls = true;
                    fullVideo.style.maxWidth = '100%';
                    fullVideo.style.display = 'none';
                    fullVideo.style.marginTop = '10px';
                    fullVideo.classList.add('rendered-media');

                    if (thumbnailUrl) {
                        thumbDiv.style.backgroundImage = `url(${thumbnailUrl})`;
                    } else {
                        thumbDiv.textContent = 'Click to view video';
                    }

                    thumbDiv.style.width = '150px';
                    thumbDiv.style.height = '150px';
                    thumbDiv.style.display = 'flex';
                    thumbDiv.style.alignItems = 'center';
                    thumbDiv.style.justifyContent = 'center';
                    thumbDiv.style.marginTop = '10px';
                    thumbDiv.style.cursor = 'pointer';
                    thumbDiv.style.backgroundSize = 'cover';
                    thumbDiv.style.backgroundColor = '#000';
                    thumbDiv.style.color = '#fff';
                    thumbDiv.classList.add('rendered-media');

                    thumbDiv.addEventListener('click', () => {
                         fullVideo.play();
                        thumbDiv.style.display = 'none';
                        fullVideo.style.display = 'block';
                        closeWrapper.style.display = 'inline';
                    });

                    closeLink.addEventListener('click', () => {
                        fullVideo.pause();
                        fullVideo.style.display = 'none';
                        thumbDiv.style.display = 'flex';
                        closeWrapper.style.display = 'none';
                    });

                    // Insert the elements after the link
                    link.parentNode.insertBefore(closeWrapper, link.nextSibling);
                    link.parentNode.insertBefore(thumbDiv, closeWrapper.nextSibling);
                    link.parentNode.insertBefore(fullVideo, thumbDiv.nextSibling);
                    console.log(`Rendered Catbox video: ${mediaUrl}`);
                });
            }
        });
    }

    function fetchVideoThumbnail(videoUrl) {
        return new Promise((resolve, reject) => {
            let video = document.createElement('video');
            video.src = videoUrl;
            video.crossOrigin = 'anonymous';
            video.addEventListener('loadeddata', () => {
                video.currentTime = 1; // Set the time to 1 second to get a frame
            });

            video.addEventListener('seeked', () => {
                let canvas = document.createElement('canvas');
                canvas.width = video.videoWidth;
                canvas.height = video.videoHeight;
                let ctx = canvas.getContext('2d');
                ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
                resolve(canvas.toDataURL('image/jpeg'));
            });

            video.addEventListener('error', () => {
                reject('Error loading video thumbnail');
            });
        });
    }

    function preloadImages() {
        // Preload thread images (thumbnails and full-sized images)
        let thumbImages = document.querySelectorAll('.fileThumb img');
        thumbImages.forEach(imgElement => {
            if (imgElement.classList.contains('media-rendered')) {
                return;
            }

            imgElement.classList.add('media-rendered');
            let thumbUrl = imgElement.src;
            let fullUrl = imgElement.parentNode.href;

            if (thumbUrl) {
                let thumbImg = new Image();
                thumbImg.src = thumbUrl;
                console.log(`Preloading thumbnail image: ${thumbUrl}`);
            }
            if (fullUrl) {
                let fullImg = new Image();
                fullImg.src = fullUrl;
                console.log(`Preloading full-sized image: ${fullUrl}`);
            }
        });
    }

    // Run the functions initially to bypass redirects and render images and videos inline on the existing page content
    bypassRedirects();
    renderCatboxMedia();
    preloadImages();

    // Intercept Fetch API requests
    (function() {
        const originalFetch = window.fetch;
        window.fetch = function() {
            return originalFetch.apply(this, arguments).then(response => {
                // Check if the request URL matches the pattern for thread updates
                if (response.url.includes('.json')) {
                    response.clone().json().then(data => {
                        console.log('Update request completed:', response.url);
                        // Run the bypass and render functions after the update request completes
                        setTimeout(function() {
                            console.log('Now running functions after the update:');
                            bypassRedirects();
                            renderCatboxMedia();
                            preloadImages();
                        }, 1000);
                    });
                }
                return response;
            });
        };
    })();

    // Intercept XMLHttpRequest requests
    (function() {
        const originalOpen = XMLHttpRequest.prototype.open;
        XMLHttpRequest.prototype.open = function(method, url) {
            this.addEventListener('load', function() {
                // Check if the request URL matches the pattern for thread updates
                if (url.includes('.json')) {
                    console.log('Update request completed:', url);
                    // Run the bypass and render functions after the update request completes
                    setTimeout(function() {
                        console.log('Now running functions after the update:');
                        bypassRedirects();
                        renderCatboxMedia();
                        preloadImages();
                    }, 1000);
                }
            });
            originalOpen.apply(this, arguments);
        };
    })();
})();