Greasy Fork is available in English.

8chan Catbox Media Embedder

Embeds images and videos from catbox.moe links in 8chan using data URLs to comply with CSP

  1. // ==UserScript==
  2. // @name 8chan Catbox Media Embedder
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.1
  5. // @description Embeds images and videos from catbox.moe links in 8chan using data URLs to comply with CSP
  6. // @author You
  7. // @match *://8chan.moe/*
  8. // @match *://8chan.se/*
  9. // @grant GM_xmlhttpRequest
  10. // @run-at document-end
  11. // @connect catbox.moe
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. // Function to check if URL is from catbox
  19. function isCatboxURL(url) {
  20. return url.includes('catbox.moe/');
  21. }
  22.  
  23. // Function to determine media type from URL
  24. function getMediaType(url) {
  25. const extension = url.split('.').pop().toLowerCase();
  26. const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
  27. const videoExtensions = ['mp4', 'webm'];
  28.  
  29. if (imageExtensions.includes(extension)) {
  30. return 'image';
  31. } else if (videoExtensions.includes(extension)) {
  32. return 'video';
  33. } else {
  34. return 'unknown';
  35. }
  36. }
  37.  
  38. // Function to embed media using data URLs
  39. function embedMedia(linkElement) {
  40. const mediaUrl = linkElement.href;
  41. const mediaType = getMediaType(mediaUrl);
  42.  
  43. const container = document.createElement('div');
  44. container.className = 'catbox-embed';
  45. container.style.marginTop = '10px';
  46. container.style.marginBottom = '10px';
  47. container.style.maxWidth = '100%';
  48.  
  49. if (mediaType === 'image') {
  50. const img = document.createElement('img');
  51. img.alt = 'Loading...';
  52. img.style.maxWidth = '100%';
  53. img.style.maxHeight = '500px';
  54. img.style.display = 'block';
  55. container.appendChild(img);
  56.  
  57. // Fetch and convert to data URL
  58. GM_xmlhttpRequest({
  59. method: 'GET',
  60. url: mediaUrl,
  61. responseType: 'blob',
  62. onload: function(response) {
  63. const reader = new FileReader();
  64. reader.onload = function() {
  65. img.src = reader.result;
  66. };
  67. reader.readAsDataURL(response.response);
  68. },
  69. onerror: function(error) {
  70. img.alt = 'Error loading image';
  71. console.error('Image load error:', error);
  72. }
  73. });
  74. } else if (mediaType === 'video') {
  75. const video = document.createElement('video');
  76. video.controls = true;
  77. video.loop = true;
  78. video.style.maxWidth = '100%';
  79. video.style.maxHeight = '500px';
  80. video.style.display = 'block';
  81. container.appendChild(video);
  82.  
  83. // Fetch and convert to data URL
  84. GM_xmlhttpRequest({
  85. method: 'GET',
  86. url: mediaUrl,
  87. responseType: 'blob',
  88. onload: function(response) {
  89. const reader = new FileReader();
  90. reader.onload = function() {
  91. video.src = reader.result;
  92. };
  93. reader.readAsDataURL(response.response);
  94. },
  95. onerror: function(error) {
  96. console.error('Video load error:', error);
  97. }
  98. });
  99. } else {
  100. return;
  101. }
  102.  
  103. // Toggle functionality
  104. const toggleBtn = document.createElement('a');
  105. toggleBtn.href = 'javascript:void(0)';
  106. toggleBtn.textContent = '[ Hide ]';
  107. toggleBtn.style.fontSize = '12px';
  108. toggleBtn.style.marginLeft = '5px';
  109. toggleBtn.style.color = '#b25f5f';
  110. toggleBtn.style.textDecoration = 'none';
  111.  
  112. toggleBtn.addEventListener('click', function() {
  113. const mediaElement = container.querySelector('img, video');
  114. if (mediaElement.style.display === 'none') {
  115. mediaElement.style.display = 'block';
  116. toggleBtn.textContent = '[ Hide ]';
  117. } else {
  118. mediaElement.style.display = 'none';
  119. toggleBtn.textContent = '[ Show ]';
  120. }
  121. });
  122.  
  123. linkElement.parentNode.insertBefore(container, linkElement.nextSibling);
  124. linkElement.parentNode.insertBefore(toggleBtn, linkElement.nextSibling);
  125. }
  126.  
  127. function processPage() {
  128. const postMessages = document.querySelectorAll('.divMessage');
  129. postMessages.forEach(message => {
  130. const links = message.querySelectorAll('a[href]');
  131. links.forEach(link => {
  132. if (isCatboxURL(link.href) && !link.dataset.processed) {
  133. link.dataset.processed = 'true';
  134. embedMedia(link);
  135. }
  136. });
  137. });
  138. }
  139.  
  140. // Initial processing
  141. processPage();
  142.  
  143. // Mutation Observer for dynamic content
  144. const observer = new MutationObserver(mutations => {
  145. mutations.forEach(mutation => {
  146. if (mutation.addedNodes.length) {
  147. processPage();
  148. }
  149. });
  150. });
  151. observer.observe(document.body, { childList: true, subtree: true });
  152.  
  153. // Handle lazy-loaded content on scroll
  154. window.addEventListener('scroll', processPage);
  155. })();