Enhanced Catbox File Uploader with Global History

Upload files to Catbox with URL history, timestamps, minimize support, middle-click to open in new tab without shifting focus.

  1. // ==UserScript==
  2. // @name Enhanced Catbox File Uploader with Global History
  3. // @namespace https://catbox.moe/
  4. // @version 1.3.1
  5. // @description Upload files to Catbox with URL history, timestamps, minimize support, middle-click to open in new tab without shifting focus.
  6. // @author heapsofjoy
  7. // @match *://*/*
  8. // @icon https://catbox.moe/pictures/favicon.ico
  9. // @grant GM_addStyle
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. // Create necessary DOM elements
  18. const uploadButton = document.createElement('div');
  19. uploadButton.id = 'uploadButton';
  20. uploadButton.innerHTML = '⬆';
  21. document.body.appendChild(uploadButton);
  22.  
  23. const fileInput = document.createElement('input');
  24. fileInput.type = 'file';
  25. fileInput.style.display = 'none';
  26. document.body.appendChild(fileInput);
  27.  
  28. const urlTextBox = document.createElement('input');
  29. urlTextBox.type = 'text';
  30. urlTextBox.id = 'fileUrl';
  31. urlTextBox.placeholder = 'URL will appear here';
  32. urlTextBox.readOnly = true;
  33. urlTextBox.style.display = 'none';
  34. document.body.appendChild(urlTextBox);
  35.  
  36. const copyButton = document.createElement('div');
  37. copyButton.id = 'copyButton';
  38. copyButton.innerHTML = '📋';
  39. copyButton.style.display = 'none';
  40. document.body.appendChild(copyButton);
  41.  
  42. const dropZone = document.createElement('div');
  43. dropZone.id = 'dropZone';
  44. dropZone.innerText = 'Drag & Drop File Here';
  45. dropZone.style.display = 'none';
  46. document.body.appendChild(dropZone);
  47.  
  48. const minimizeButton = document.createElement('div');
  49. minimizeButton.id = 'minimizeButton';
  50. minimizeButton.innerHTML = '—';
  51. minimizeButton.style.display = 'none';
  52. document.body.appendChild(minimizeButton);
  53.  
  54. const historyButton = document.createElement('div');
  55. historyButton.id = 'historyButton';
  56. historyButton.innerHTML = '📜';
  57. historyButton.style.display = 'none';
  58. document.body.appendChild(historyButton);
  59.  
  60. const clearHistoryButton = document.createElement('div');
  61. clearHistoryButton.id = 'clearHistoryButton';
  62. clearHistoryButton.innerHTML = '🗑️';
  63. clearHistoryButton.style.display = 'none';
  64. document.body.appendChild(clearHistoryButton);
  65.  
  66. const historyList = document.createElement('div');
  67. historyList.id = 'historyList';
  68. historyList.style.display = 'none';
  69. document.body.appendChild(historyList);
  70.  
  71. GM_addStyle(`
  72. #uploadButton, #historyButton, #clearHistoryButton, #minimizeButton {
  73. position: fixed;
  74. bottom: 0;
  75. width: 50px;
  76. height: 50px;
  77. background-color: #333;
  78. color: white;
  79. border: none;
  80. border-radius: 50%;
  81. cursor: pointer;
  82. font-family: Arial, sans-serif;
  83. font-size: 24px;
  84. text-align: center;
  85. line-height: 50px;
  86. box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2);
  87. z-index: 10000;
  88. }
  89. #uploadButton { left: 20px; transition: bottom 0.4s ease; }
  90. #uploadButton.minimized {
  91. bottom: 0;
  92. width: 50px;
  93. height: 10px;
  94. border-radius: 50px 50px 0 0;
  95. font-size: 10px;
  96. line-height: 10px;
  97. }
  98. #minimizeButton, #historyButton, #clearHistoryButton {
  99. width: 40px;
  100. height: 40px;
  101. font-size: 20px;
  102. line-height: 40px;
  103. }
  104. #minimizeButton { left: 80px; }
  105. #historyButton { left: 140px; }
  106. #clearHistoryButton { left: 200px; }
  107.  
  108. #fileUrl, #historyList {
  109. position: fixed;
  110. bottom: 70px;
  111. left: 20px;
  112. width: 270px;
  113. background-color: #333;
  114. color: white;
  115. padding: 10px;
  116. border: none;
  117. border-radius: 5px;
  118. font-size: 14px;
  119. box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2);
  120. z-index: 10000;
  121. overflow-y: auto;
  122. }
  123. #fileUrl { display: block; }
  124. #historyList {
  125. height: 200px;
  126. display: none;
  127. }
  128. #historyList div {
  129. padding: 5px;
  130. border-bottom: 1px solid #555;
  131. cursor: pointer;
  132. color: #66ccff;
  133. }
  134. #historyList div span.timestamp {
  135. display: block;
  136. color: #aaa;
  137. font-size: 12px;
  138. margin-top: 2px;
  139. }
  140. #copyButton {
  141. position: fixed;
  142. bottom: 70px;
  143. left: 300px;
  144. width: 30px;
  145. height: 30px;
  146. background-color: #333;
  147. color: white;
  148. border: none;
  149. border-radius: 5px;
  150. cursor: pointer;
  151. font-family: Arial, sans-serif;
  152. font-size: 16px;
  153. text-align: center;
  154. line-height: 30px;
  155. box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2);
  156. z-index: 10000;
  157. }
  158. #dropZone {
  159. position: fixed;
  160. bottom: 120px;
  161. left: 20px;
  162. width: 300px;
  163. height: 150px;
  164. border: 2px dashed #aaa;
  165. background-color: #444;
  166. color: white;
  167. text-align: center;
  168. line-height: 150px;
  169. font-family: Arial, sans-serif;
  170. font-size: 14px;
  171. border-radius: 5px;
  172. z-index: 10000;
  173. }
  174. #dropZone.dragover {
  175. border-color: #fff;
  176. background-color: #555;
  177. }
  178. `);
  179.  
  180. let isMinimized = true;
  181. uploadButton.classList.add('minimized');
  182.  
  183. uploadButton.addEventListener('click', () => {
  184. if (isMinimized) {
  185. uploadButton.classList.remove('minimized');
  186. isMinimized = false;
  187. minimizeButton.style.display = 'block';
  188. historyButton.style.display = 'block';
  189. clearHistoryButton.style.display = 'block';
  190. } else {
  191. fileInput.click();
  192. }
  193. });
  194.  
  195. minimizeButton.addEventListener('click', () => {
  196. uploadButton.classList.add('minimized');
  197. isMinimized = true;
  198. minimizeButton.style.display = 'none';
  199. urlTextBox.style.display = 'none';
  200. copyButton.style.display = 'none';
  201. historyList.style.display = 'none';
  202. historyButton.style.display = 'none';
  203. clearHistoryButton.style.display = 'none';
  204. dropZone.style.display = 'none';
  205. });
  206.  
  207. fileInput.addEventListener('change', () => {
  208. const file = fileInput.files[0];
  209. if (file) uploadFile(file);
  210. });
  211.  
  212. const uploadedUrlsKey = 'globalUploadedUrls';
  213. const urlLimit = 10; // Limit the number of stored URLs
  214.  
  215. // Load the saved URLs from GM storage
  216. let savedUrls = GM_getValue(uploadedUrlsKey, []);
  217.  
  218. function updateHistoryList() {
  219. historyList.innerHTML = '';
  220. const sortedUrls = [...savedUrls].reverse(); // Reverse order for recent-first
  221.  
  222. sortedUrls.forEach(entry => {
  223. const urlElement = document.createElement('div');
  224. urlElement.innerHTML = `${entry.url} <span class="timestamp">${entry.timestamp}</span>`;
  225.  
  226. // Left-click to open in a new tab (focus)
  227. urlElement.addEventListener('click', () => {
  228. window.open(entry.url, '_blank');
  229. });
  230.  
  231. // Middle-click to open in a background tab
  232. urlElement.addEventListener('auxclick', (e) => {
  233. if (e.button === 1) {
  234. window.open(entry.url, '_blank', 'noopener,noreferrer');
  235. }
  236. });
  237. historyList.appendChild(urlElement);
  238. });
  239. }
  240.  
  241. historyButton.addEventListener('click', () => {
  242. historyList.style.display = historyList.style.display === 'none' ? 'block' : 'none';
  243. });
  244.  
  245. clearHistoryButton.addEventListener('click', () => {
  246. GM_setValue(uploadedUrlsKey, []); // Clear GM storage
  247. savedUrls = []; // Reset local array
  248. historyList.innerHTML = ''; // Clear the displayed list
  249. alert('History cleared!');
  250. });
  251.  
  252. let dragCounter = 0;
  253.  
  254. document.addEventListener('dragover', (e) => {
  255. e.preventDefault();
  256. if (!isMinimized) {
  257. dragCounter++;
  258. dropZone.style.display = 'block';
  259. dropZone.classList.add('dragover');
  260. }
  261. });
  262.  
  263. document.addEventListener('dragleave', (e) => {
  264. e.preventDefault();
  265. if (!isMinimized) {
  266. dragCounter--;
  267. if (dragCounter === 0) {
  268. dropZone.classList.remove('dragover');
  269. dropZone.style.display = 'none';
  270. }
  271. }
  272. });
  273.  
  274. dropZone.addEventListener('drop', (e) => {
  275. if (!isMinimized) {
  276. e.preventDefault();
  277. dragCounter = 0;
  278. dropZone.classList.remove('dragover');
  279. dropZone.style.display = 'none';
  280. const file = e.dataTransfer.files[0];
  281. if (file) uploadFile(file);
  282. }
  283. });
  284.  
  285. function uploadFile(file) {
  286. const formData = new FormData();
  287. formData.append('reqtype', 'fileupload');
  288. formData.append('time', '1h');
  289. formData.append('fileToUpload', file);
  290.  
  291. fetch('https://litterbox.catbox.moe/resources/internals/api.php', {
  292. method: 'POST',
  293. body: formData
  294. })
  295. .then(response => response.text())
  296. .then(url => {
  297. const timestamp = new Date().toLocaleString();
  298. savedUrls.push({ url, timestamp });
  299. // Check for limit and remove oldest if needed
  300. if (savedUrls.length > urlLimit) {
  301. savedUrls.shift(); // Remove the oldest entry
  302. }
  303. GM_setValue(uploadedUrlsKey, savedUrls); // Save to global storage
  304. urlTextBox.style.display = 'block';
  305. urlTextBox.value = url;
  306. copyButton.style.display = 'block';
  307. updateHistoryList();
  308. })
  309. .catch(error => {
  310. urlTextBox.value = 'Upload failed!';
  311. console.error('Error:', error);
  312. });
  313. }
  314.  
  315. copyButton.addEventListener('click', () => {
  316. navigator.clipboard.writeText(urlTextBox.value).then(() => {
  317. copyButton.innerHTML = '✔';
  318. setTimeout(() => copyButton.innerHTML = '📋', 1000);
  319. }).catch(error => console.error('Copy failed:', error));
  320. });
  321.  
  322. updateHistoryList(); // Initial call to populate the history on load
  323. })();