YouTube Watch Later Remove Button on Hover

Show a remove button on hover over video thumbnails in the Watch Later list

  1. // ==UserScript==
  2. // @name YouTube Watch Later Remove Button on Hover
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.6.2
  5. // @description Show a remove button on hover over video thumbnails in the Watch Later list
  6. // @license MIT
  7. // @match https://www.youtube.com/*
  8. // @run-at document-start
  9. // @grant none
  10. // @inject-into page
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. 'use strict';
  15.  
  16. let scriptEnable = false;
  17.  
  18. const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0);
  19.  
  20. function getItemData(menu) {
  21. if (!menu) return;
  22. let items = ((menu || 0).menuRenderer || 0).items || 0;
  23. if (!(items.length >= 1)) return;
  24. const filtered = items.filter((entry) => {
  25. if (!entry || typeof entry !== 'object') return false;
  26. try {
  27. const t = (JSON.stringify(entry) || '');
  28. return t.includes('ACTION_REMOVE_VIDEO');
  29. } catch (e) {
  30. return false;
  31. }
  32. });
  33. if (filtered.length !== 1) return;
  34. let itemData = filtered[0];
  35. itemData = Object.values(itemData).filter(e => e.serviceEndpoint || e.command)[0];
  36. if (!itemData) return;
  37. return itemData;
  38. }
  39.  
  40. const removeButtonOnclick = function (e) {
  41. const button = this;
  42. if (!scriptEnable) return;
  43.  
  44. let videoElement = button.closest('ytd-thumbnail');
  45. if (!videoElement) return;
  46. let renderer = videoElement.closest('ytd-playlist-video-renderer.style-scope.ytd-playlist-video-list-renderer');
  47. if (!renderer) return;
  48.  
  49. let data = insp(renderer).data || 0;
  50. const itemData = getItemData(data.menu);
  51. if (!itemData) return;
  52.  
  53. e.preventDefault(); // Prevent the default action of the event
  54. e.stopPropagation(); // Stop the event from bubbling up
  55. e.stopImmediatePropagation(); // Prevents other listeners of the same event from being called
  56.  
  57. const cntNode = (document.querySelector('ytd-app') || renderer);
  58.  
  59. (function (data) {
  60.  
  61. const a = data.serviceEndpoint;
  62. const b = data.command;
  63.  
  64. a && this.ytComponentBehavior.resolveCommand(a);
  65. b && this.ytComponentBehavior.resolveCommand(b);
  66.  
  67. }).call(insp(cntNode), itemData);
  68.  
  69. };
  70.  
  71. function createRemoveButton(videoElement) {
  72. let removeButton = document.createElement('button');
  73. removeButton.className = 'watch-later-remove-button';
  74. removeButton.textContent = 'Remove';
  75. removeButton.style.position = 'absolute';
  76. removeButton.style.top = '5px';
  77. removeButton.style.right = '5px';
  78. removeButton.style.zIndex = '1000';
  79.  
  80. removeButton.onclick = removeButtonOnclick;
  81. videoElement.appendChild(removeButton);
  82. }
  83.  
  84. const mouseenterHandler = function (e) {
  85. if (!e || !(e.target instanceof HTMLElement)) return;
  86. if (!scriptEnable) return;
  87. if (!e.target.classList.contains('ytd-playlist-video-renderer')) return;
  88. let videoElement = e.target.closest('ytd-thumbnail');
  89. if (!videoElement) return;
  90. let renderer = videoElement.closest('ytd-playlist-video-renderer.style-scope.ytd-playlist-video-list-renderer');
  91. if (!renderer || renderer.is !== 'ytd-playlist-video-renderer') return;
  92. let data = insp(renderer).data || 0;
  93. let itemData = getItemData(data.menu);
  94. if (!itemData) return;
  95. let button = videoElement.querySelector('button.watch-later-remove-button')
  96. if (!button) {
  97. createRemoveButton(videoElement);
  98. } else {
  99. button.style.display = '';
  100. }
  101. };
  102.  
  103. const mouseleaveHandler = function (e) {
  104. if (!e || !(e.target instanceof HTMLElement)) return;
  105. if (!e.target.classList.contains('ytd-playlist-video-renderer')) return;
  106. let button = e.target.querySelector('button.watch-later-remove-button');
  107. if (button) {
  108. button.style.display = 'none';
  109. }
  110. };
  111.  
  112.  
  113. document.addEventListener('yt-navigate-finish', () => {
  114.  
  115. const newEnable = location.pathname === '/playlist' && location.search.includes('list=WL');
  116.  
  117. if (scriptEnable ^ newEnable) {
  118.  
  119. if (scriptEnable) {
  120. scriptEnable = false;
  121. document.removeEventListener('mouseenter', mouseenterHandler, true);
  122. document.removeEventListener('mouseleave', mouseleaveHandler, true);
  123. } else if (newEnable) {
  124. scriptEnable = true;
  125. document.addEventListener('mouseenter', mouseenterHandler, true);
  126. document.addEventListener('mouseleave', mouseleaveHandler, true);
  127. }
  128.  
  129. }
  130.  
  131. }, false);
  132.  
  133. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址