YouTube Mobile Repeated Recommendations Hider

Hide from YouTube's mobile browser homepage any videos that are recommended more than twice. You can also hide by channel or by partial title.

当前为 2021-01-14 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name YouTube Mobile Repeated Recommendations Hider
  3. // @description Hide from YouTube's mobile browser homepage any videos that are recommended more than twice. You can also hide by channel or by partial title.
  4. // @version 1.8
  5. // @author BLBC (github.com/hjk789, greasyfork.org/users/679182-hjk789)
  6. // @copyright 2020+, BLBC (github.com/hjk789, greasyfork.org/users/679182-hjk789)
  7. // @homepage https://github.com/hjk789/Creations/tree/master/JavaScript/Userscripts/YouTube-Mobile-Repeated-Recommendations-Hider
  8. // @license https://github.com/hjk789/Creations/tree/master/JavaScript/Userscripts/YouTube-Mobile-Repeated-Recommendations-Hider#license
  9. // @match https://m.youtube.com
  10. // @grant GM.setValue
  11. // @grant GM.getValue
  12. // @grant GM.listValues
  13. // @grant GM.deleteValue
  14. // @namespace https://greasyfork.org/users/679182
  15. // ==/UserScript==
  16.  
  17. //******* SETTINGS ********
  18.  
  19. const maxRepetitions = 2 // The maximum number of times that the same recommended video is allowed to appear on your
  20. // homepage before starting to get hidden. Set this to 1 if you want one-time recommendations.
  21. //*************************
  22.  
  23. let channelsToHide, partialTitlesToHide
  24. let processedVideosList
  25.  
  26. GM.getValue("channels").then(function(value)
  27. {
  28. if (!value)
  29. {
  30. value = JSON.stringify([])
  31. GM.setValue("channels", value)
  32. }
  33.  
  34. channelsToHide = JSON.parse(value)
  35.  
  36. GM.getValue("partialTitles").then(function(value)
  37. {
  38. if (!value)
  39. {
  40. value = JSON.stringify([])
  41. GM.setValue("partialTitles", value)
  42. }
  43.  
  44. partialTitlesToHide = JSON.parse(value)
  45.  
  46. GM.listValues().then(function(GmList) // Get in an array all the items currently in the script's storage. Searching for a value in
  47. { // an array is much faster and lighter than calling GM.getValue for every recommendation.
  48. processedVideosList = GmList
  49.  
  50. const recommendationsContainer = document.querySelector(".rich-grid-renderer-contents")
  51.  
  52. const firstVideos = recommendationsContainer.querySelectorAll("ytm-rich-item-renderer") // Because a mutation observer is being used and the script is run after the page is fully
  53. // loaded, the observer isn't triggered with the recommendations that appear first.
  54. for (let i=0; i < firstVideos.length; i++) // This does the processing manually to these first ones.
  55. processRecommendation(firstVideos[i])
  56.  
  57.  
  58. const loadedRecommendedVideosObserver = new MutationObserver(function(mutations) // A mutation observer is being used so that all processings happen only
  59. { // when actually needed, which is when more recommendations are loaded.
  60. for (let i=0; i < mutations.length; i++)
  61. processRecommendation(mutations[i].addedNodes[0])
  62. })
  63.  
  64. loadedRecommendedVideosObserver.observe(recommendationsContainer, {childList: true})
  65. })
  66. })
  67. })
  68.  
  69. async function processRecommendation(node)
  70. {
  71. if (!node) return
  72.  
  73. const videoTitleEll = node.querySelector("h3")
  74. const videoTitleText = videoTitleEll.textContent.toLowerCase() // Convert the title's text to lowercase so that there's no distinction with uppercase letters.
  75. const videoChannel = videoTitleEll.nextSibling.firstChild.firstChild.textContent
  76. const videoUrl = videoTitleEll.parentElement.href
  77. const videoMenuBtn = node.querySelector("ytm-menu")
  78.  
  79. if (videoMenuBtn)
  80. {
  81. videoMenuBtn.onclick = function()
  82. {
  83. waitForMenu = setInterval(function(node, videoChannel)
  84. {
  85. const menu = document.getElementById("menu")
  86.  
  87. if (menu)
  88. {
  89. clearInterval(waitForMenu)
  90.  
  91. const hideChannelButton = document.createElement("button")
  92. hideChannelButton.className = "menu-item-button"
  93. hideChannelButton.innerText = "Hide videos from this channel"
  94. hideChannelButton.onclick = function()
  95. {
  96. if (confirm("Are you sure you want to hide all videos from the channel ''" + videoChannel + "''?"))
  97. {
  98. channelsToHide.push(videoChannel)
  99. GM.setValue("channels", JSON.stringify(channelsToHide))
  100. }
  101. }
  102.  
  103. const hidePartialTitleButton = document.createElement("button")
  104. hidePartialTitleButton.className = "menu-item-button"
  105. hidePartialTitleButton.innerText = "Hide videos that include a text"
  106. hidePartialTitleButton.onclick = function()
  107. {
  108. const partialText = prompt("Specify the partial title of the videos to hide. All videos that contain this text in the title will get hidden.")
  109.  
  110. if (partialText)
  111. {
  112. partialTitlesToHide.push(partialText.toLowerCase())
  113. GM.setValue("partialTitles", JSON.stringify(partialTitlesToHide))
  114. }
  115. }
  116.  
  117. menu.firstChild.appendChild(hideChannelButton)
  118. menu.firstChild.appendChild(hidePartialTitleButton)
  119.  
  120.  
  121. }
  122.  
  123. }, 100, node, videoChannel)
  124. }
  125. }
  126.  
  127. if (processedVideosList.includes("hide::"+videoUrl) || channelsToHide.includes(videoChannel) || partialTitlesToHide.some(p => videoTitleText.includes(p)))
  128. node.style.display = "none"
  129. else
  130. {
  131. if (maxRepetitions == 1) // If the script is set to show only one-time recommendations, to avoid unnecessary processings,
  132. { // rightaway mark to hide in the next time the page is loaded every video not found in the storage.
  133. GM.setValue("hide::"+videoUrl,"")
  134. return
  135. }
  136. else
  137. var value = await GM.getValue(videoUrl)
  138.  
  139. if (typeof value == "undefined")
  140. value = 1
  141. else
  142. {
  143. if (value >= maxRepetitions)
  144. {
  145. node.style.display = "none"
  146.  
  147. GM.deleteValue(videoUrl)
  148. GM.setValue("hide::"+videoUrl,"")
  149. return
  150. }
  151.  
  152. value++
  153. }
  154.  
  155. GM.setValue(videoUrl, value)
  156. }
  157.  
  158. }