This script enhances your 4chan browsing experience by removing redirect URLs from links (when the "Linkify URLs" option is enabled), automatically loading all media in a thread upon entry, and seamlessly displaying Catbox-hosted videos and images directly within the thread.
// ==UserScript==
// @name 4chan - Vanilla+
// @namespace http://tampermonkey.net/
// @version 2.42
// @description This script enhances your 4chan browsing experience by removing redirect URLs from links (when the "Linkify URLs" option is enabled), automatically loading all media in a thread upon entry, and seamlessly displaying Catbox-hosted videos and images directly within 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);
};
})();
})();