Advanced Bypass 4chan URL Redirect and Toggle Inline Media

Bypass 4chan URL redirect by modifying the URLs directly on the page, including dynamically loaded content, and toggling images and videos inline with improved functionality

目前為 2024-09-22 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Advanced Bypass 4chan URL Redirect and Toggle Inline Media
// @namespace    http://tampermonkey.net/
// @version      2.2
// @description  Bypass 4chan URL redirect by modifying the URLs directly on the page, including dynamically loaded content, and toggling images and videos inline with improved functionality
// @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);
        };
    })();
})();