YouTube - Add Playlist Remove Video Button

adds a remove button next to each video on each playlist page

  1. // ==UserScript==
  2. // @name YouTube - Add Playlist Remove Video Button
  3. // @namespace https://openuserjs.org/users/zachhardesty7
  4. // @author Zach Hardesty <zachhardesty7@users.noreply.github.com> (https://github.com/zachhardesty7)
  5. // @description adds a remove button next to each video on each playlist page
  6. // @copyright 2019-2021, Zach Hardesty (https://zachhardesty.com/)
  7. // @license GPL-3.0-only; http://www.gnu.org/licenses/gpl-3.0.txt
  8. // @version 2.0.2
  9.  
  10. // @homepageURL https://github.com/zachhardesty7/tamper-monkey-scripts-collection/raw/master/youtube-add-watch-later-button.user.js
  11. // @homepage https://github.com/zachhardesty7/tamper-monkey-scripts-collection/raw/master/youtube-add-watch-later-button.user.js
  12. // @homepageURL https://openuserjs.org/scripts/zachhardesty7/YouTube_-_Add_Playlist_Remove_Video_Button
  13. // @homepage https://openuserjs.org/scripts/zachhardesty7/YouTube_-_Add_Playlist_Remove_Video_Button
  14. // @supportURL https://github.com/zachhardesty7/tamper-monkey-scripts-collection/issues
  15.  
  16.  
  17. // @match https://www.youtube.com/*
  18. // @require https://gf.qytechs.cn/scripts/419640-onelementready/code/onElementReady.js?version=887637
  19. // ==/UserScript==
  20.  
  21. // prevent eslint from complaining when redefining private function queryForElements from gist
  22. // eslint-disable-next-line no-unused-vars
  23. /* global onElementReady, queryForElements:true */
  24.  
  25. const STYLE_SCOPE = "style-scope"
  26. const YT_ICON = "yt-icon"
  27. const ZH_MARKER = "zh-delete-button"
  28.  
  29. /**
  30. * Query for new DOM nodes matching a specified selector.
  31. *
  32. * @override
  33. */
  34. // @ts-ignore
  35. queryForElements = (selector, _, callback) => {
  36. // search for elements by selector
  37. const elementList = document.querySelectorAll(selector) || []
  38. for (const element of elementList) {
  39. callback(element)
  40. }
  41. }
  42.  
  43. /**
  44. * build the button el tediously but like the rest
  45. *
  46. * @param {HTMLElement} buttons - html node
  47. */
  48. function addPlaylistVideoDeleteButton(buttons) {
  49. // noop if button already present
  50. if (buttons.querySelector(`.${ZH_MARKER}`)) {
  51. return
  52. }
  53.  
  54. // normal action
  55. const container = document.createElement("div")
  56.  
  57. container.id = "menu"
  58. container.className = `${STYLE_SCOPE} ytd-playlist-video-renderer ${ZH_MARKER}`
  59. buttons.append(container)
  60.  
  61. const buttonContainer = document.createElement("yt-icon-button")
  62. buttonContainer.id = "button"
  63. buttonContainer.className = `dropdown-trigger ${STYLE_SCOPE} ytd-menu-renderer`
  64. container.append(buttonContainer)
  65.  
  66. // wrapping button field automatically created
  67. const icon = document.createElement(YT_ICON)
  68. icon.className = `${STYLE_SCOPE} ytd-menu-renderer`
  69. buttonContainer.firstElementChild?.append(icon)
  70.  
  71. // copy icon from triple dot menu
  72. const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
  73. svg.setAttribute("viewBox", "0 0 24 24")
  74. svg.setAttribute("preserveAspectRatio", "xMidYMid meet")
  75. svg.setAttribute("focusable", "false")
  76. svg.setAttribute("class", `${STYLE_SCOPE} ${YT_ICON}`)
  77. svg.setAttribute(
  78. "style",
  79. "pointer-events: none; display: block; width: 100%; height: 100%;",
  80. )
  81. icon.append(svg)
  82.  
  83. const g = document.createElementNS("http://www.w3.org/2000/svg", "g")
  84. g.setAttribute("class", `${STYLE_SCOPE} ${YT_ICON}`)
  85. svg.append(g)
  86.  
  87. const path = document.createElementNS("http://www.w3.org/2000/svg", "path")
  88. path.setAttribute("class", `${STYLE_SCOPE} ${YT_ICON}`)
  89. path.setAttribute(
  90. "d",
  91. "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z",
  92. )
  93. g.append(path)
  94.  
  95. buttonContainer.addEventListener("click", () => {
  96. const overflowMenuButton = [...buttons.children].at(-2)?.firstElementChild
  97. ?.lastElementChild
  98. overflowMenuButton?.click()
  99.  
  100. // allow the menu to be created before clicking (usually too quick to see)
  101. onElementReady(
  102. "#items > ytd-menu-service-item-renderer",
  103. { findOnce: false },
  104. (menuButton) => {
  105. // TODO: come up with i18n friendly solution
  106. if (menuButton.textContent?.includes("Remove from")) {
  107. menuButton.click()
  108. }
  109. },
  110. )
  111. })
  112. }
  113.  
  114. // YouTube uses a bunch of duplicate 'id' tag values. why?
  115. // this makes it much more likely to target right one, but at the cost of being brittle
  116. onElementReady(
  117. "ytd-playlist-video-renderer.ytd-playlist-video-list-renderer",
  118. { findOnce: false },
  119. addPlaylistVideoDeleteButton,
  120. )

QingJ © 2025

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