8chan.moe Delete/Trash No Refresh

Prevents page refresh when clicking Delete or Trash buttons on 8chan.moe moderation pages and handles deletions via AJAX

  1. // ==UserScript==
  2. // @name 8chan.moe Delete/Trash No Refresh
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.1
  5. // @description Prevents page refresh when clicking Delete or Trash buttons on 8chan.moe moderation pages and handles deletions via AJAX
  6. // @author Anonymous
  7. // @match https://8chan.moe/*/res/*.html*
  8. // @match https://8chan.moe/mod.js?*
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. console.log('8chan.moe Delete/Trash No Refresh script v2.1 loaded');
  17.  
  18. // Function to handle button clicks and send AJAX request
  19. function handleButtonClick(event) {
  20. event.preventDefault(); // Prevent default button behavior
  21. event.stopPropagation(); // Stop event bubbling
  22. event.stopImmediatePropagation(); // Stop other handlers
  23.  
  24. const button = event.target;
  25. console.log(`Clicked button: ${button.id} with value: ${button.value}`);
  26.  
  27. // Try to find the form
  28. let form = button.closest('form');
  29. if (!form) {
  30. // Fallback: search for a form with deletion checkboxes
  31. form = document.querySelector('form input[name*="-"][name*="-"]:checked')?.closest('form') || document.querySelector('form');
  32. if (!form) {
  33. console.error('No form found in the document');
  34. alert('Error: No form found. Please report this issue.');
  35. return;
  36. }
  37. }
  38. console.log(`Form found: ${form.outerHTML}`);
  39.  
  40. // Prevent form submission
  41. form.addEventListener('submit', (e) => {
  42. e.preventDefault();
  43. e.stopPropagation();
  44. console.log('Form submission prevented for form:', form);
  45. }, { capture: true, once: true });
  46.  
  47. // Disable button to prevent multiple clicks
  48. button.disabled = true;
  49. setTimeout(() => { button.disabled = false; }, 500); // Re-enable after 500ms
  50.  
  51. // Collect form data
  52. const formData = new FormData();
  53. formData.append('action', button.value); // Add the button's value (delete or trash)
  54.  
  55. // Add checked post checkboxes (e.g., a-9-12)
  56. const checkedCheckboxes = form.querySelectorAll('input[name*="-"][name*="-"]:checked');
  57. if (checkedCheckboxes.length === 0) {
  58. console.error('No checked checkboxes found');
  59. alert('Error: No posts selected. Please check at least one post.');
  60. return;
  61. }
  62. checkedCheckboxes.forEach(checkbox => {
  63. formData.append(checkbox.name, 'true');
  64. console.log(`Added post checkbox: ${checkbox.name}=true`);
  65. });
  66.  
  67. // Log form data for debugging
  68. for (const [key, value] of formData.entries()) {
  69. console.log(`FormData: ${key}=${value}`);
  70. }
  71.  
  72. // Log form details
  73. console.log(`Form action attribute: ${form.getAttribute('action') || 'undefined'}`);
  74. console.log(`Form method: ${form.getAttribute('method') || 'undefined'}`);
  75.  
  76. // Use the correct deletion endpoint
  77. const actionUrl = 'https://8chan.moe/contentActions.js?json=1';
  78. console.log(`Final action URL: ${actionUrl}`);
  79.  
  80. // Send AJAX request
  81. fetch(actionUrl, {
  82. method: 'POST',
  83. body: formData, // Use multipart/form-data
  84. credentials: 'same-origin', // Include cookies for authentication
  85. headers: {
  86. 'Accept': 'application/json',
  87. 'X-Requested-With': 'XMLHttpRequest'
  88. // Omit Content-Type to let browser set multipart/form-data with boundary
  89. }
  90. })
  91. .then(response => {
  92. console.log(`Response status: ${response.status}, ok: ${response.ok}`);
  93. return response.json().catch(() => response.text()).then(data => ({ status: response.status, ok: response.ok, data }));
  94. })
  95. .then(({ status, ok, data }) => {
  96. if (ok) {
  97. console.log(`${button.value} action successful`, data);
  98. // Provide visual feedback for each checked post
  99. checkedCheckboxes.forEach(checkbox => {
  100. const postElement = checkbox.closest('.post') || checkbox.closest('.post-container') || checkbox.closest('article');
  101. if (postElement) {
  102. postElement.style.opacity = '0.5'; // Visual indication
  103. postElement.style.pointerEvents = 'none'; // Disable interactions
  104. }
  105. });
  106. } else {
  107. throw new Error(`Failed to ${button.value}: ${status} ${JSON.stringify(data)}`);
  108. }
  109. })
  110. .catch(error => {
  111. console.error('Error:', error);
  112. alert(`Error performing ${button.value} action: ${error.message}`);
  113. // Revert visual changes on failure
  114. checkedCheckboxes.forEach(checkbox => {
  115. const postElement = checkbox.closest('.post') || checkbox.closest('.post-container') || checkbox.closest('article');
  116. if (postElement) {
  117. postElement.style.opacity = '';
  118. postElement.style.pointerEvents = '';
  119. }
  120. });
  121. });
  122. }
  123.  
  124. // Add event listeners to Delete and Trash buttons
  125. document.addEventListener('click', function(event) {
  126. if (event.target.matches('#deleteFormButton') || event.target.matches('#trashFormButton')) {
  127. console.log('Button click detected:', event.target.id);
  128. handleButtonClick(event);
  129. }
  130. }, { capture: true });
  131.  
  132. // Prevent form submission for all forms with delete/trash buttons or checkboxes
  133. document.querySelectorAll('form').forEach(form => {
  134. if (form.querySelector('#deleteFormButton') || form.querySelector('#trashFormButton') || form.querySelector('input[name*="-"][name*="-"]')) {
  135. form.addEventListener('submit', (e) => {
  136. e.preventDefault();
  137. e.stopPropagation();
  138. console.log('Form submission blocked for form:', form);
  139. }, { capture: true });
  140. }
  141. });
  142.  
  143. // Global submit prevention for safety
  144. document.addEventListener('submit', (e) => {
  145. if (e.target.querySelector('#deleteFormButton') || e.target.querySelector('#trashFormButton') || e.target.querySelector('input[name*="-"][name*="-"]')) {
  146. e.preventDefault();
  147. console.log('Global form submission blocked');
  148. }
  149. }, { capture: true });
  150.  
  151. })();