Embeds images and videos from catbox.moe links in 8chan using data URLs to comply with CSP
// ==UserScript==
// @name 8chan Catbox Media Embedder
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Embeds images and videos from catbox.moe links in 8chan using data URLs to comply with CSP
// @author You
// @match *://8chan.moe/*
// @match *://8chan.se/*
// @grant GM_xmlhttpRequest
// @run-at document-end
// @connect catbox.moe
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// Function to check if URL is from catbox
function isCatboxURL(url) {
return url.includes('catbox.moe/');
}
// Function to determine media type from URL
function getMediaType(url) {
const extension = url.split('.').pop().toLowerCase();
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
const videoExtensions = ['mp4', 'webm'];
if (imageExtensions.includes(extension)) {
return 'image';
} else if (videoExtensions.includes(extension)) {
return 'video';
} else {
return 'unknown';
}
}
// Function to embed media using data URLs
function embedMedia(linkElement) {
const mediaUrl = linkElement.href;
const mediaType = getMediaType(mediaUrl);
const container = document.createElement('div');
container.className = 'catbox-embed';
container.style.marginTop = '10px';
container.style.marginBottom = '10px';
container.style.maxWidth = '100%';
if (mediaType === 'image') {
const img = document.createElement('img');
img.alt = 'Loading...';
img.style.maxWidth = '100%';
img.style.maxHeight = '500px';
img.style.display = 'block';
container.appendChild(img);
// Fetch and convert to data URL
GM_xmlhttpRequest({
method: 'GET',
url: mediaUrl,
responseType: 'blob',
onload: function(response) {
const reader = new FileReader();
reader.onload = function() {
img.src = reader.result;
};
reader.readAsDataURL(response.response);
},
onerror: function(error) {
img.alt = 'Error loading image';
console.error('Image load error:', error);
}
});
} else if (mediaType === 'video') {
const video = document.createElement('video');
video.controls = true;
video.loop = true;
video.style.maxWidth = '100%';
video.style.maxHeight = '500px';
video.style.display = 'block';
container.appendChild(video);
// Fetch and convert to data URL
GM_xmlhttpRequest({
method: 'GET',
url: mediaUrl,
responseType: 'blob',
onload: function(response) {
const reader = new FileReader();
reader.onload = function() {
video.src = reader.result;
};
reader.readAsDataURL(response.response);
},
onerror: function(error) {
console.error('Video load error:', error);
}
});
} else {
return;
}
// Toggle functionality
const toggleBtn = document.createElement('a');
toggleBtn.href = 'javascript:void(0)';
toggleBtn.textContent = '[ Hide ]';
toggleBtn.style.fontSize = '12px';
toggleBtn.style.marginLeft = '5px';
toggleBtn.style.color = '#b25f5f';
toggleBtn.style.textDecoration = 'none';
toggleBtn.addEventListener('click', function() {
const mediaElement = container.querySelector('img, video');
if (mediaElement.style.display === 'none') {
mediaElement.style.display = 'block';
toggleBtn.textContent = '[ Hide ]';
} else {
mediaElement.style.display = 'none';
toggleBtn.textContent = '[ Show ]';
}
});
linkElement.parentNode.insertBefore(container, linkElement.nextSibling);
linkElement.parentNode.insertBefore(toggleBtn, linkElement.nextSibling);
}
function processPage() {
const postMessages = document.querySelectorAll('.divMessage');
postMessages.forEach(message => {
const links = message.querySelectorAll('a[href]');
links.forEach(link => {
if (isCatboxURL(link.href) && !link.dataset.processed) {
link.dataset.processed = 'true';
embedMedia(link);
}
});
});
}
// Initial processing
processPage();
// Mutation Observer for dynamic content
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
if (mutation.addedNodes.length) {
processPage();
}
});
});
observer.observe(document.body, { childList: true, subtree: true });
// Handle lazy-loaded content on scroll
window.addEventListener('scroll', processPage);
})();