YouTube "damn is 😂" Button

Adds a "damn is 😂" button to YouTube comments

目前为 2025-04-09 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube "damn is 😂" Button
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description Adds a "damn is 😂" button to YouTube comments
  6. // @author Claude
  7. // @match https://www.youtube.com/*
  8. // @grant none
  9. // @run-at document-idle
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Set debug mode to true for more console logging
  16. const DEBUG = true;
  17.  
  18. function log(...args) {
  19. if (DEBUG) {
  20. console.log('[damn-is-button]', ...args);
  21. }
  22. }
  23.  
  24. // Function to generate random emoji string
  25. function generateRandomEmojiString() {
  26. const emojis = ["😂", "❤️", "🎉"];
  27. const length = 5 + Math.floor(Math.random() * 46); // Between 5 and 50
  28. let result = "";
  29.  
  30. for (let i = 0; i < length; i++) {
  31. result += emojis[Math.floor(Math.random() * emojis.length)];
  32. }
  33.  
  34. return "damn is " + result;
  35. }
  36.  
  37. // Function to process a single toolbar
  38. function processToolbar(toolbar) {
  39. // Skip if already processed
  40. if (toolbar.querySelector('.damn-is-button')) {
  41. return;
  42. }
  43.  
  44. // Create a simple button
  45. const damnIsButton = document.createElement('button');
  46. damnIsButton.className = 'damn-is-button';
  47. damnIsButton.textContent = 'damn is 😂';
  48. damnIsButton.title = 'Add a "damn is 😂" comment';
  49.  
  50. // Style the button to match YouTube's design
  51. damnIsButton.style.marginLeft = '8px';
  52. damnIsButton.style.border = 'none';
  53. damnIsButton.style.borderRadius = '18px';
  54. damnIsButton.style.padding = '0 16px';
  55. damnIsButton.style.height = '36px';
  56. damnIsButton.style.cursor = 'pointer';
  57. damnIsButton.style.fontSize = '14px';
  58. damnIsButton.style.fontFamily = 'Roboto, Arial, sans-serif';
  59. damnIsButton.style.color = 'var(--yt-spec-text-primary, #0f0f0f)';
  60. damnIsButton.style.backgroundColor = 'var(--yt-spec-badge-chip-background, rgba(0, 0, 0, 0.05))';
  61.  
  62. // Add hover effects
  63. damnIsButton.addEventListener('mouseover', function() {
  64. this.style.backgroundColor = 'var(--yt-spec-10-percent-layer, rgba(0, 0, 0, 0.1))';
  65. });
  66.  
  67. damnIsButton.addEventListener('mouseout', function() {
  68. this.style.backgroundColor = 'var(--yt-spec-badge-chip-background, rgba(0, 0, 0, 0.05))';
  69. });
  70.  
  71. // Add click event
  72. damnIsButton.addEventListener('click', function() {
  73. log('Button clicked!');
  74.  
  75. // Step 1: Click the reply button
  76. const replyButton = toolbar.querySelector('#reply-button-end>yt-button-shape>button');
  77.  
  78. if (replyButton) {
  79. replyButton.click();
  80. log('Clicked reply button');
  81. } else {
  82. log('Reply button not found');
  83. return;
  84. }
  85.  
  86. // Step 2: Wait for the input field to appear
  87. setTimeout(() => {
  88. // Find the input field
  89. const commentContainer = toolbar.closest('ytd-comment-thread-renderer');
  90.  
  91. if (!commentContainer) {
  92. log('Comment container not found');
  93. return;
  94. }
  95.  
  96. // Try different selectors for the input field
  97. const possibleSelectors = [
  98. '#contenteditable-root',
  99. 'div[contenteditable="true"]',
  100. '#commentbox div[contenteditable="true"]',
  101. '#reply-dialog div[contenteditable="true"]'
  102. ];
  103.  
  104. let inputField = null;
  105.  
  106. // Try selectors on the comment container first
  107. for (const selector of possibleSelectors) {
  108. inputField = commentContainer.querySelector(selector);
  109. if (inputField) {
  110. log('Found input field with selector:', selector);
  111. break;
  112. }
  113. }
  114.  
  115. // If not found, try document-wide
  116. if (!inputField) {
  117. for (const selector of possibleSelectors) {
  118. inputField = document.querySelector(selector);
  119. if (inputField) {
  120. log('Found input field in document with selector:', selector);
  121. break;
  122. }
  123. }
  124. }
  125.  
  126. if (!inputField) {
  127. log('Input field not found');
  128. return;
  129. }
  130.  
  131. // Generate and set the text
  132. const randomText = generateRandomEmojiString();
  133. log('Setting text:', randomText);
  134.  
  135. inputField.textContent = randomText;
  136. inputField.dispatchEvent(new Event('input', { bubbles: true }));
  137.  
  138. // Step 3: Wait for the submit button to become enabled
  139. const trySubmit = (attempts = 0) => {
  140. if (attempts > 10) {
  141. log('Max submit attempts reached');
  142. return;
  143. }
  144.  
  145. // Try different selectors for the submit button
  146. const submitSelectors = [
  147. '#submit-button button',
  148. 'ytd-button-renderer[id="submit-button"] button',
  149. '#reply-dialog #submit-button button'
  150. ];
  151.  
  152. let submitButton = null;
  153.  
  154. // Try selectors on the comment container first
  155. for (const selector of submitSelectors) {
  156. submitButton = commentContainer.querySelector(selector);
  157. if (submitButton && !submitButton.disabled) {
  158. log('Found enabled submit button with selector:', selector);
  159. submitButton.click();
  160. log('Clicked submit button');
  161. return;
  162. }
  163. }
  164.  
  165. // If not found, try document-wide
  166. for (const selector of submitSelectors) {
  167. submitButton = document.querySelector(selector);
  168. if (submitButton && !submitButton.disabled) {
  169. log('Found enabled submit button in document with selector:', selector);
  170. submitButton.click();
  171. log('Clicked submit button');
  172. return;
  173. }
  174. }
  175.  
  176. // Not found or disabled, try again
  177. log(`Submit button not found or disabled, attempt ${attempts + 1}/10`);
  178. setTimeout(() => trySubmit(attempts + 1), 300);
  179. };
  180.  
  181. // Start trying to submit
  182. setTimeout(() => trySubmit(), 500);
  183.  
  184. }, 500); // Wait for reply box to appear
  185. });
  186.  
  187. // Append the button to the toolbar
  188. toolbar.appendChild(damnIsButton);
  189. log('Added button to toolbar');
  190. }
  191.  
  192. // Function to process all comments
  193. function processComments() {
  194. const toolbars = document.querySelectorAll('ytd-comment-engagement-bar > #toolbar');
  195. log(`Found ${toolbars.length} comment toolbars`);
  196.  
  197. toolbars.forEach(toolbar => {
  198. processToolbar(toolbar);
  199. });
  200. }
  201.  
  202. // Set up MutationObserver to detect new comments
  203. function setupObserver() {
  204. const commentsSection = document.querySelector('ytd-comments, #comments');
  205. if (!commentsSection) {
  206. log('Comments section not found, will retry in 1s');
  207. setTimeout(setupObserver, 1000);
  208. return;
  209. }
  210.  
  211. log('Comments section found, setting up observer');
  212.  
  213. // Process existing comments
  214. processComments();
  215.  
  216. // Create observer for new comments
  217. const observer = new MutationObserver(mutations => {
  218. let shouldProcess = false;
  219.  
  220. for (const mutation of mutations) {
  221. if (mutation.addedNodes.length) {
  222. shouldProcess = true;
  223. break;
  224. }
  225. }
  226.  
  227. if (shouldProcess) {
  228. if (observer.timeout) {
  229. clearTimeout(observer.timeout);
  230. }
  231.  
  232. observer.timeout = setTimeout(() => {
  233. log('New content detected, processing comments');
  234. processComments();
  235. observer.timeout = null;
  236. }, 500);
  237. }
  238. });
  239.  
  240. // Start observing
  241. observer.observe(commentsSection, {
  242. childList: true,
  243. subtree: true
  244. });
  245.  
  246. // Also run periodically
  247. setInterval(processComments, 5000);
  248.  
  249. log('Observer set up successfully');
  250. }
  251.  
  252. // Initialize when DOM is ready
  253. log('Script loaded, waiting for page to fully load');
  254. if (document.readyState === 'loading') {
  255. document.addEventListener('DOMContentLoaded', setupObserver);
  256. } else {
  257. setupObserver();
  258. }
  259.  
  260. // Also run on page navigation (for SPA behavior)
  261. let lastUrl = location.href;
  262. const urlObserver = new MutationObserver(() => {
  263. if (location.href !== lastUrl) {
  264. lastUrl = location.href;
  265. log('URL changed, reinitializing');
  266. setTimeout(setupObserver, 2000);
  267. }
  268. });
  269.  
  270. if (document.querySelector('head > title')) {
  271. urlObserver.observe(document.querySelector('head > title'), { subtree: true, childList: true });
  272. }
  273. })();