Discord Catbox Uploader

adds a button to upload files to catbox.moe, output gets copied to your clipboard

  1. // ==UserScript==
  2. // @name Discord Catbox Uploader
  3. // @namespace https://tampermonkey.net/
  4. // @version 1.6
  5. // @description adds a button to upload files to catbox.moe, output gets copied to your clipboard
  6. // @author OasisVee
  7. // @match https://*.discord.com/*
  8. // @grant GM_xmlhttpRequest
  9. // @grant GM_setClipboard
  10. // @grant GM_addStyle
  11. // @grant GM_getValue
  12. // @grant GM_setValue
  13. // @grant GM_log
  14. // @connect catbox.moe
  15. // @icon https://www.google.com/s2/favicons?sz=64&domain=catbox.moe
  16. // @license MIT
  17. // ==/UserScript==
  18.  
  19. (function() {
  20. 'use strict';
  21.  
  22. const STYLES = `
  23. .catbox-tooltip {
  24. position: absolute;
  25. background-color: #202225;
  26. color: #dcddde;
  27. padding: 8px 12px;
  28. border-radius: 5px;
  29. font-size: 14px;
  30. font-weight: 500;
  31. pointer-events: none;
  32. opacity: 0;
  33. transition: opacity 0.1s ease-in-out;
  34. z-index: 9999;
  35. top: -30px;
  36. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  37. white-space: nowrap;
  38. border: 1px solid #36393F;
  39. }
  40. .catbox-tooltip::before {
  41. content: '';
  42. position: absolute;
  43. width: 0;
  44. height: 0;
  45. border: 8px solid transparent;
  46. border-top-color: #202225;
  47. bottom: -16px;
  48. left: 50%;
  49. transform: translateX(-50%);
  50. }
  51. #catbox-settings {
  52. position: fixed;
  53. top: 50%;
  54. left: 50%;
  55. transform: translate(-50%, -50%);
  56. background-color: #36393f;
  57. padding: 20px;
  58. border-radius: 5px;
  59. z-index: 10000;
  60. display: none;
  61. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  62. }
  63. #catbox-settings input {
  64. width: 100%;
  65. margin-bottom: 10px;
  66. padding: 8px;
  67. background-color: #40444b;
  68. border: none;
  69. color: #dcddde;
  70. border-radius: 3px;
  71. }
  72. #catbox-settings button {
  73. background-color: #5865f2;
  74. color: white;
  75. border: none;
  76. padding: 8px 16px;
  77. border-radius: 3px;
  78. cursor: pointer;
  79. transition: background-color 0.2s;
  80. }
  81. #catbox-settings button:hover {
  82. background-color: #4752c4;
  83. }
  84. #debug-console {
  85. position: fixed;
  86. top: 25%;
  87. right: 10px;
  88. width: 300px;
  89. max-height: 200px;
  90. overflow-y: auto;
  91. background-color: rgba(0, 0, 0, 0.8);
  92. color: #ffffff;
  93. padding: 10px;
  94. border-radius: 5px;
  95. font-family: monospace;
  96. z-index: 9999;
  97. display: none;
  98. }
  99. .debug-entry {
  100. margin-bottom: 5px;
  101. border-bottom: 1px solid #333;
  102. padding-bottom: 5px;
  103. }
  104. `;
  105.  
  106. const CAT_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="currentColor"><path d="M12,8L10.67,8.09C9.81,7.07 7.4,4.5 5,4.5C5,4.5 3.03,7.46 4.96,11.41C4.41,12.24 4.07,12.67 4,13.66L2.07,18.37L2.06,18.39C1.61,19.31 2.08,20.68 3,21.13L3.09,21.17C3.42,21.31 3.77,21.35 4.09,21.3C4.39,21.33 4.7,21.27 4.95,21.13L5.36,20.94C6.35,20.44 6.69,20.18 7.12,20.03C7.88,19.83 8.88,19.9 10.01,19.9H14C15.15,19.9 16.15,19.83 16.91,20.03C17.34,20.18 17.66,20.44 18.65,20.94L19.06,21.13C19.3,21.27 19.61,21.33 19.91,21.3C20.23,21.35 20.58,21.31 20.91,21.17L21,21.13C21.92,20.68 22.39,19.31 21.94,18.39L21.93,18.37L20,13.66C19.93,12.67 19.59,12.24 19.04,11.41C20.97,7.46 19,4.5 19,4.5C16.6,4.5 14.19,7.07 13.33,8.09L12,8M9,11A1,1 0 0,1 10,12A1,1 0 0,1 9,13A1,1 0 0,1 8,12A1,1 0 0,1 9,11M15,11A1,1 0 0,1 16,12A1,1 0 0,1 15,13A1,1 0 0,1 14,12A1,1 0 0,1 15,11M11,14H13L12.3,15.39C12.5,16.03 13.06,16.5 13.75,16.5A1.5,1.5 0 0,0 15.25,15H15.75A2,2 0 0,1 13.75,17C13,17 12.35,16.59 12,16V16H12C11.65,16.59 11,17 10.25,17A2,2 0 0,1 8.25,15H8.75A1.5,1.5 0 0,0 10.25,16.5C10.94,16.5 11.5,16.03 11.7,15.39L11,14Z"/></svg>`;
  107.  
  108. // Update the selectors to make them more reliable
  109. const BUTTON_CONTAINER_SELECTOR = 'div[class*="buttonContainer"]';
  110. const UPLOAD_BUTTON_SELECTOR = 'button[class*="attachButton"]';
  111. const NOTIFICATION_DURATION = 3000;
  112. const FADE_DURATION = 300;
  113. const DEBUG_MODE = true; // Set to true to enable debug console
  114.  
  115. class CatboxUploader {
  116. constructor() {
  117. this.setupDebugConsole();
  118. this.debugLog('CatboxUploader initialized');
  119. this.setupElements();
  120. this.setupEventListeners();
  121. this.init();
  122. }
  123.  
  124. setupDebugConsole() {
  125. if (!DEBUG_MODE) return;
  126.  
  127. this.debugConsole = document.createElement('div');
  128. this.debugConsole.id = 'debug-console';
  129. this.debugConsole.style.display = 'none'; // Start hidden by default
  130. document.body.appendChild(this.debugConsole);
  131.  
  132. // Add toggle shortcut (Alt+D)
  133. document.addEventListener('keydown', (e) => {
  134. if (e.altKey && e.key === 'd') {
  135. this.debugConsole.style.display = this.debugConsole.style.display === 'none' ? 'block' : 'none';
  136. }
  137. });
  138. }
  139.  
  140. debugLog(message, type = 'info') {
  141. console.log(`[Catbox] ${message}`);
  142. if (DEBUG_MODE) {
  143. const entry = document.createElement('div');
  144. entry.className = 'debug-entry';
  145. entry.style.color = type === 'error' ? '#ff6b6b' : type === 'success' ? '#51cf66' : '#74c0fc';
  146. entry.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
  147. this.debugConsole.appendChild(entry);
  148. this.debugConsole.scrollTop = this.debugConsole.scrollHeight;
  149.  
  150. // Remove this line to prevent auto-showing the console on every log
  151. // this.debugConsole.style.display = 'block';
  152. }
  153. }
  154. setupElements() {
  155. this.debugLog('Setting up elements');
  156. this.fileInput = document.createElement('input');
  157. this.fileInput.type = 'file';
  158. this.fileInput.accept = 'image/*,video/*,audio/*,application/*';
  159. document.body.appendChild(this.fileInput);
  160. this.fileInput.style.display = 'none';
  161.  
  162. this.tooltipElement = document.createElement('div');
  163. this.tooltipElement.className = 'catbox-tooltip';
  164.  
  165. this.notificationElement = document.createElement('div');
  166. this.notificationElement.style.cssText = `
  167. position: fixed;
  168. top: 10px;
  169. right: 10px;
  170. padding: 10px 20px;
  171. border-radius: 5px;
  172. color: white;
  173. font-weight: bold;
  174. z-index: 9999;
  175. opacity: 0;
  176. transition: opacity 0.3s ease-in-out;
  177. `;
  178.  
  179. this.createCatboxButton();
  180. }
  181.  
  182. createCatboxButton() {
  183. this.debugLog('Creating Catbox button template');
  184. this.catboxButtonTemplate = document.createElement('button');
  185. this.catboxButtonTemplate.id = 'catbox-upload-btn';
  186. this.catboxButtonTemplate.innerHTML = CAT_SVG;
  187. this.catboxButtonTemplate.setAttribute('data-tooltip', 'Upload to Catbox');
  188. }
  189.  
  190. setupEventListeners() {
  191. this.debugLog('Setting up event listeners');
  192. this.fileInput.addEventListener('change', this.handleFileSelect.bind(this));
  193. document.addEventListener('click', this.handleOutsideClick.bind(this));
  194. }
  195.  
  196. handleFileSelect(event) {
  197. const file = event.target.files[0];
  198. if (file) {
  199. this.debugLog(`File selected: ${file.name} (${file.size} bytes, ${file.type})`);
  200. this.uploadFile(file);
  201. } else {
  202. this.debugLog('No file selected', 'error');
  203. }
  204. }
  205.  
  206. handleOutsideClick(event) {
  207. const settingsDiv = document.getElementById('catbox-settings');
  208. if (settingsDiv && !settingsDiv.contains(event.target) &&
  209. event.target.id !== 'catbox-upload-btn' &&
  210. !event.target.closest('#catbox-upload-btn')) {
  211. settingsDiv.remove();
  212. }
  213. }
  214.  
  215. addCatboxButton() {
  216. // Find the container of buttons first
  217. const buttonContainer = document.querySelector(BUTTON_CONTAINER_SELECTOR);
  218. const uploadButton = document.querySelector(UPLOAD_BUTTON_SELECTOR);
  219.  
  220. if (buttonContainer && uploadButton && !document.getElementById('catbox-upload-btn')) {
  221. this.debugLog('Found button container and upload button');
  222. const catboxButton = this.catboxButtonTemplate.cloneNode(true);
  223. catboxButton.className = uploadButton.className;
  224. this.styleCatboxButton(catboxButton);
  225. this.attachButtonEventListeners(catboxButton);
  226.  
  227. // Insert directly into the button container, right after the upload button
  228. try {
  229. buttonContainer.insertBefore(catboxButton, uploadButton.nextSibling);
  230. this.debugLog('Catbox button added successfully');
  231.  
  232. // Fix the spacing to match Discord's UI
  233. catboxButton.style.marginLeft = '2px';
  234. return true;
  235. } catch (e) {
  236. this.debugLog(`Error adding button: ${e.message}`, 'error');
  237. return false;
  238. }
  239. }
  240. return false;
  241. }
  242.  
  243. styleCatboxButton(button) {
  244. button.style.cssText = `
  245. vertical-align: top;
  246. padding: 0px 8px;
  247. height: 44px;
  248. line-height: 0px;
  249. position: relative;
  250. color: white;
  251. opacity: 0.8;
  252. display: inline-flex;
  253. align-items: center;
  254. justify-content: center;
  255. transition: opacity 0.2s;
  256. margin: 0;
  257. top: 0;
  258. transform: none;
  259. background: transparent;
  260. border: none;
  261. cursor: pointer;
  262. `;
  263. button.addEventListener('mouseenter', () => button.style.opacity = '1');
  264. button.addEventListener('mouseleave', () => button.style.opacity = '0.8');
  265. }
  266.  
  267. attachButtonEventListeners(button) {
  268. button.addEventListener('click', this.handleCatboxUpload.bind(this));
  269. button.addEventListener('mouseenter', this.showTooltip.bind(this));
  270. button.addEventListener('mouseleave', this.hideTooltip.bind(this));
  271. button.addEventListener('contextmenu', this.toggleSettings.bind(this));
  272. }
  273.  
  274. handleCatboxUpload(event) {
  275. event.preventDefault();
  276. event.stopPropagation();
  277. this.debugLog('Catbox upload button clicked');
  278.  
  279. // Need to remove and re-add the input to avoid issues
  280. if (this.fileInput.parentNode) {
  281. document.body.removeChild(this.fileInput);
  282. }
  283.  
  284. this.fileInput = document.createElement('input');
  285. this.fileInput.type = 'file';
  286. this.fileInput.accept = 'image/*,video/*,audio/*,application/*';
  287. this.fileInput.style.display = 'none';
  288. this.fileInput.addEventListener('change', this.handleFileSelect.bind(this));
  289. document.body.appendChild(this.fileInput);
  290.  
  291. // Trigger file selection
  292. this.fileInput.click();
  293. }
  294.  
  295. uploadFile(file) {
  296. this.debugLog(`Attempting to upload file: ${file.name}`);
  297. this.showNotification('Uploading to Catbox.moe...', 'info');
  298.  
  299. const formData = new FormData();
  300. formData.append('reqtype', 'fileupload');
  301. formData.append('fileToUpload', file);
  302.  
  303. const userHash = GM_getValue('catboxUserHash', '');
  304. if (userHash) {
  305. this.debugLog('Using saved user hash');
  306. formData.append('userhash', userHash);
  307. } else {
  308. this.debugLog('No user hash found');
  309. }
  310.  
  311. try {
  312. this.debugLog('Sending request to Catbox API...');
  313. GM_xmlhttpRequest({
  314. method: 'POST',
  315. url: 'https://catbox.moe/user/api.php',
  316. data: formData,
  317. headers: {
  318. 'User-Agent': 'Discord-Catbox-Uploader/1.6'
  319. },
  320. onload: (response) => {
  321. this.debugLog(`Response received: Status ${response.status}`);
  322. this.handleUploadResponse(response);
  323. },
  324. onerror: (error) => {
  325. this.debugLog(`Upload error: ${error}`, 'error');
  326. this.showNotification('Error uploading to Catbox.moe. Please try again.', 'error');
  327. },
  328. onprogress: (progress) => {
  329. if (progress.lengthComputable) {
  330. const percentComplete = Math.round((progress.loaded / progress.total) * 100);
  331. this.debugLog(`Upload progress: ${percentComplete}%`);
  332. }
  333. }
  334. });
  335. } catch (e) {
  336. this.debugLog(`Exception during upload: ${e.message}`, 'error');
  337. this.showNotification('Error uploading to Catbox.moe. Please try again.', 'error');
  338. }
  339. }
  340.  
  341. handleUploadResponse(response) {
  342. this.debugLog(`Response text: ${response.responseText}`);
  343.  
  344. if (response.status === 200 && response.responseText) {
  345. // Validate that the response is a URL
  346. if (response.responseText.startsWith('https://')) {
  347. GM_setClipboard(response.responseText);
  348. this.debugLog('File uploaded successfully, URL copied to clipboard', 'success');
  349. this.showNotification('File uploaded and link copied to clipboard!', 'success');
  350. } else {
  351. this.debugLog(`Invalid response, not a URL: ${response.responseText}`, 'error');
  352. this.showNotification('Error: Received invalid response from Catbox.', 'error');
  353. }
  354. } else {
  355. this.debugLog('Upload failed: Bad response status or empty response', 'error');
  356. this.showNotification('Error uploading to Catbox.moe. Please try again.', 'error');
  357. }
  358. }
  359.  
  360. showTooltip(event) {
  361. const button = event.currentTarget;
  362. this.tooltipElement.textContent = button.getAttribute('data-tooltip');
  363. document.body.appendChild(this.tooltipElement);
  364.  
  365. const buttonRect = button.getBoundingClientRect();
  366. const tooltipRect = this.tooltipElement.getBoundingClientRect();
  367.  
  368. this.tooltipElement.style.left = `${buttonRect.left + (buttonRect.width / 2) - (tooltipRect.width / 2)}px`;
  369. this.tooltipElement.style.top = `${buttonRect.top - tooltipRect.height - 15}px`;
  370.  
  371. requestAnimationFrame(() => this.tooltipElement.style.opacity = '1');
  372. }
  373.  
  374. hideTooltip() {
  375. this.tooltipElement.style.opacity = '0';
  376. setTimeout(() => {
  377. if (this.tooltipElement.parentNode) {
  378. this.tooltipElement.parentNode.removeChild(this.tooltipElement);
  379. }
  380. }, 100);
  381. }
  382.  
  383. showNotification(message, type) {
  384. this.debugLog(`Notification: ${message}`, type);
  385. this.notificationElement.textContent = message;
  386. this.notificationElement.style.backgroundColor =
  387. type === 'error' ? '#ff4444' :
  388. type === 'info' ? '#0099cc' : '#00C851';
  389.  
  390. if (this.notificationElement.parentNode) {
  391. document.body.removeChild(this.notificationElement);
  392. }
  393.  
  394. document.body.appendChild(this.notificationElement);
  395.  
  396. requestAnimationFrame(() => {
  397. this.notificationElement.style.opacity = '1';
  398. setTimeout(() => {
  399. this.notificationElement.style.opacity = '0';
  400. setTimeout(() => {
  401. if (this.notificationElement.parentNode) {
  402. document.body.removeChild(this.notificationElement);
  403. }
  404. }, FADE_DURATION);
  405. }, NOTIFICATION_DURATION);
  406. });
  407. }
  408.  
  409. toggleSettings(event) {
  410. event.preventDefault();
  411. this.debugLog('Settings dialog requested');
  412.  
  413. const existingSettings = document.getElementById('catbox-settings');
  414. if (existingSettings) {
  415. existingSettings.remove();
  416. return;
  417. }
  418.  
  419. const settingsDiv = document.createElement('div');
  420. settingsDiv.id = 'catbox-settings';
  421. settingsDiv.innerHTML = `
  422. <h3 style="color: #dcddde; margin-top: 0; margin-bottom: 10px;">Catbox Settings</h3>
  423. <p style="color: #b9bbbe; margin-bottom: 15px; font-size: 13px;">Enter your Catbox user hash to associate uploads with your account</p>
  424. <input type="text" id="catbox-user-hash" placeholder="Enter Catbox User Hash">
  425. <button id="save-catbox-settings">Save</button>
  426. <button id="test-catbox-connection" style="margin-top: 10px; background-color: #5d5d5d;">Test Connection</button>
  427. `;
  428.  
  429. document.body.appendChild(settingsDiv);
  430.  
  431. const userHashInput = document.getElementById('catbox-user-hash');
  432. userHashInput.value = GM_getValue('catboxUserHash', '');
  433.  
  434. document.getElementById('save-catbox-settings').addEventListener('click', () => {
  435. const userHash = userHashInput.value.trim();
  436. GM_setValue('catboxUserHash', userHash);
  437. settingsDiv.remove();
  438. this.debugLog(`User hash saved: ${userHash ? '(hash saved)' : '(empty)'}`);
  439. this.showNotification('Catbox settings saved!', 'success');
  440. });
  441.  
  442. document.getElementById('test-catbox-connection').addEventListener('click', () => {
  443. this.testCatboxConnection();
  444. });
  445.  
  446. settingsDiv.style.display = 'block';
  447. }
  448.  
  449. testCatboxConnection() {
  450. this.debugLog('Testing connection to Catbox.moe');
  451. this.showNotification('Testing connection to Catbox.moe...', 'info');
  452.  
  453. // Use a test file upload to check the connection
  454. const formData = new FormData();
  455. formData.append('reqtype', 'fileupload');
  456.  
  457. // Create a tiny test file
  458. const blob = new Blob(['test'], { type: 'text/plain' });
  459. const testFile = new File([blob], 'connection_test.txt', { type: 'text/plain' });
  460. formData.append('fileToUpload', testFile);
  461.  
  462. const userHash = GM_getValue('catboxUserHash', '');
  463. if (userHash) {
  464. formData.append('userhash', userHash);
  465. }
  466.  
  467. GM_xmlhttpRequest({
  468. method: 'POST',
  469. url: 'https://catbox.moe/user/api.php',
  470. data: formData,
  471. headers: {
  472. 'User-Agent': 'Discord-Catbox-Uploader/1.6'
  473. },
  474. onload: (response) => {
  475. this.debugLog(`Test connection response: Status ${response.status}, Response: ${response.responseText}`);
  476. if (response.status >= 200 && response.status < 300 && response.responseText.startsWith('https://')) {
  477. this.showNotification('Connection to Catbox.moe successful!', 'success');
  478. } else {
  479. this.showNotification(`Connection test failed with status ${response.status}`, 'error');
  480. }
  481. },
  482. onerror: (error) => {
  483. this.debugLog(`Test connection error: ${error}`, 'error');
  484. this.showNotification('Connection to Catbox.moe failed!', 'error');
  485. }
  486. });
  487. }
  488.  
  489. init() {
  490. this.debugLog('Initializing script');
  491. GM_addStyle(STYLES);
  492.  
  493. const observer = new MutationObserver((mutations) => {
  494. if (mutations.some(mutation => mutation.addedNodes.length > 0)) {
  495. if (!document.getElementById('catbox-upload-btn')) {
  496. this.addCatboxButton();
  497. }
  498. }
  499. });
  500.  
  501. observer.observe(document.body, { childList: true, subtree: true });
  502. this.addCatboxButton();
  503. this.debugLog('Init complete');
  504. }
  505. }
  506.  
  507. // Initialize the uploader
  508. new CatboxUploader();
  509. })();