Youtube 隐藏工具

快捷隐藏 YouTube 评论区、相关推荐、视频结尾推荐和设置菜单

当前为 2023-09-06 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Youtube Hide Tool
  3. // @name:zh-TW Youtube 隱藏工具
  4. // @name:zh-CN Youtube 隐藏工具
  5. // @name:ja Youtube 非表示ツール
  6. // @name:ko 유튜브 숨기기 도구
  7. // @name:en Youtube Hide Tool
  8. // @name:de Youtube Versteckwerkzeug
  9. // @name:pt Ferramenta de Ocultação do Youtube
  10. // @name:es Herramienta de Ocultación de Youtube
  11. // @name:fr Outil de Masquage de Youtube
  12. // @name:hi यूट्यूब छुपाने का उपकरण
  13. // @name:id Alat Sembunyikan Youtube
  14. // @version 0.0.21
  15. // @author HentaiSaru
  16. // @description 快捷隱藏 YouTube 留言區、相關推薦、影片結尾推薦和設置選單
  17. // @description:zh-TW 快捷隱藏 YouTube 留言區、相關推薦、影片結尾推薦和設置選單
  18. // @description:zh-CN 快捷隐藏 YouTube 评论区、相关推荐、视频结尾推荐和设置菜单
  19. // @description:ja YouTubeのコメント欄、関連おすすめ、動画の最後のおすすめ、設定メニューを素早く非表示にする
  20. // @description:ko 빠른 YouTube 댓글 영역, 관련 추천, 비디오 끝 추천 및 설정 메뉴 숨기기
  21. // @description:en Quickly hide YouTube comments, related recommendations, video end recommendations, and settings menu
  22. // @description:de Schnell verstecken YouTube Kommentare, verwandte Empfehlungen, Video-Ende-Empfehlungen und Einstellungsmenü
  23. // @description:pt Ocultar rapidamente comentários do YouTube, recomendações relacionadas, recomendações de final de vídeo e menu de configurações
  24. // @description:es Ocultar rápidamente comentarios de YouTube, recomendaciones relacionadas, recomendaciones de final de video y menú de configuración
  25. // @description:fr Masquer rapidement les commentaires de YouTube, les recommandations connexes, les recommandations de fin de vidéo et le menu des paramètres
  26. // @description:hi यूट्यूब टिप्पणियाँ, संबंधित सिफारिशें, वीडियो के अंत की सिफारिशें और सेटिंग्स मेनू को त्वरित रूप से छुपाएं
  27. // @description:id Sembunyikan cepat komentar YouTube, rekomendasi terkait, rekomendasi akhir video, dan menu pengaturan
  28.  
  29. // @match *://www.youtube.com/*
  30. // @icon https://cdn-icons-png.flaticon.com/512/1383/1383260.png
  31.  
  32. // @license MIT
  33. // @namespace https://greasyfork.org/users/989635
  34.  
  35. // @run-at document-end
  36. // @grant GM_setValue
  37. // @grant GM_getValue
  38. // @grant GM_addStyle
  39. // @grant GM_registerMenuCommand
  40. // ==/UserScript==
  41.  
  42. (function() {
  43. const pattern = /^https:\/\/www\.youtube\.com\/.+$/;
  44. var currentUrl;
  45. const observer = new MutationObserver(() => {
  46. currentUrl = window.location.href;
  47. if (pattern.test(currentUrl) && !document.body.hasAttribute("data-hide")) {
  48. document.body.setAttribute("data-hide", true);
  49. let transform = false, set;
  50.  
  51. /***
  52. * _ooOoo_
  53. * o8888888o
  54. * 88" . "88
  55. * (| -_- |)
  56. * O\ = /O
  57. * ___/`---'\____
  58. * . ' \\| |// `.
  59. * / \\||| : |||// \
  60. * / _||||| -:- |||||- \
  61. * | | \\\ - /// | |
  62. * | \_| ''\---/'' | |
  63. * \ .-\__ `-` ___/-. /
  64. * ___`. .' /--.--\ `. . __
  65. * ."" '< `.___\_<|>_/___.' >'"".
  66. * | | : `- \`.;`\ _ /`;.`/ - ` : | |
  67. * \ \ `-. \_ __\ /__ _/ .-` / /
  68. * ======`-.____`-.___\_____/___.-`____.-'======
  69. * `=---='
  70. * .............................................
  71. * 要準確的判斷快捷, 要完全自訂需要寫一堆定義, 實在是有點麻煩(懶)
  72. * 懂設置可於這邊修改快捷!
  73. */
  74. const HotKey = {
  75. RecomCard: event => event.shiftKey, // 影片結尾推薦卡
  76. MinimaList: event => event.ctrlKey && event.key == "z", // 極簡化
  77. RecomPlay: event => event.altKey && event.key == "1", // 推薦播放
  78. Message: event => event.altKey && event.key == "2", // 留言區
  79. FunctionBar: event => event.altKey && event.key == "3", // 功能區
  80. ListDesc: event => event.altKey && event.key == "4" // 播放清單資訊
  81. }
  82.  
  83. /* ======================= 主運行 ========================= */
  84.  
  85. RunMaim();
  86. async function RunMaim() {
  87. /* 修改樣式 */
  88. GM_addStyle(`
  89. .ytp-ce-element{opacity: 0.1 !important;}
  90. .ytp-ce-element:hover{opacity: 1 !important;}
  91. `);
  92.  
  93. /* 宣告 */
  94. const VVP_Pattern = /^https:\/\/www\.youtube\.com\/watch\?v=.+$/, // 判斷在播放頁面運行
  95. Playlist_Pattern = /^https:\/\/www\.youtube\.com\/playlist\?list=.+$/, // 判斷在播放清單運行
  96. language = display_language(navigator.language),
  97. ListenerRecord = new Map(),
  98. Lookup_Delay = 300,
  99. Dev = false;
  100.  
  101. /* 註冊菜單 */
  102. GM_registerMenuCommand(language[0], function() {alert(language[1])});
  103.  
  104. /* ======================= 設置 API ========================= */
  105.  
  106. /* 觸發設置 API */
  107. async function SetTrigger(element) {
  108. element.style.display = "none";
  109. return new Promise(resolve => {
  110. if (element.style.display === "none") {resolve(true)}
  111. else {resolve(false)}
  112. });
  113. }
  114.  
  115. /* 設置判斷 API */
  116. async function HideJudgment(element, gm="") {
  117. if (element.style.display === "none" || transform) {
  118. element.style.display = "block";
  119. if (gm !== "") {GM_setValue(gm, false)}
  120. } else {
  121. element.style.display = "none";
  122. if (gm !== "") {GM_setValue(gm, true)}
  123. }
  124. }
  125.  
  126. /* 添加 監聽器 API */
  127. async function addlistener(element, type, listener, add={}) {
  128. if (!ListenerRecord.has(element) || !ListenerRecord.get(element).has(type)) {
  129. element.addEventListener(type, listener, add);
  130. if (!ListenerRecord.has(element)) {
  131. ListenerRecord.set(element, new Map());
  132. }
  133. ListenerRecord.get(element).set(type, listener);
  134. }
  135. }
  136.  
  137. /* 等待元素出現 API */
  138. async function WaitElem(selectors, timeout, callback) {
  139. let timer, elements;
  140.  
  141. const observer = new MutationObserver(() => {
  142. elements = selectors.map(selector => document.getElementById(selector));
  143. if (Dev) {console.log(elements)}
  144. if (elements.every(element => element)) {
  145. observer.disconnect();
  146. clearTimeout(timer);
  147. callback(elements);
  148. }
  149. });
  150.  
  151. observer.observe(document.body, { childList: true, subtree: true });
  152. timer = setTimeout(() => {
  153. observer.disconnect();
  154. }, timeout);
  155. }
  156. /* ======================= 讀取設置 ========================= */
  157. const HideElem = ["end", "below", "secondary", "related", "secondary-inner", "chat-container", "comments", "menu-container"];
  158. WaitElem(HideElem, 8000, element => {
  159. const [end, below, secondary, related, inner, chat, comments, menu] = element;
  160.  
  161. /* 獲取設置 */
  162. if (VVP_Pattern.test(currentUrl)) {
  163. // 極簡化
  164. set = GM_getValue("Minimalist", null);
  165. if (set && set !== null) {
  166. Promise.all([SetTrigger(end), SetTrigger(below), SetTrigger(secondary), SetTrigger(related)]).then(results => {
  167. if (results.every(result => result)) {
  168. if (Dev) {console.log("極簡化")}
  169. }
  170. });
  171. } else {
  172. // 推薦播放
  173. set = GM_getValue("Trigger_1", null);
  174. if (set && set !== null){
  175. Promise.all([SetTrigger(chat), SetTrigger(secondary), SetTrigger(related)]).then(results => {
  176. if (results.every(result => result)) {
  177. if (Dev) {console.log("隱藏推薦播放")}
  178. }
  179. });
  180. }
  181. // 留言區
  182. set = GM_getValue("Trigger_2", null);
  183. if (set && set !== null){
  184. SetTrigger(comments).then(() => {
  185. if (Dev) {console.log("隱藏留言區")}
  186. });
  187. }
  188. // 功能選項
  189. set = GM_getValue("Trigger_3", null);
  190. if (set && set !== null){
  191. SetTrigger(menu).then(() => {
  192. if (Dev) {console.log("隱藏功能選項")}
  193. });
  194. }
  195. }
  196. } else if (Playlist_Pattern.test(currentUrl)) {
  197. // 播放清單資訊
  198. set = GM_getValue("Trigger_4", null);
  199. if (set && set !== null){
  200. let interval;
  201. interval = setInterval(function() {
  202. let playlist = document.querySelector("#page-manager > ytd-browse > ytd-playlist-header-renderer > div");
  203. if (playlist) {
  204. SetTrigger(playlist).then(() => {clearInterval(interval)});
  205. }
  206. }, Lookup_Delay);
  207. }
  208. }
  209.  
  210. /* ======================= 快捷設置 ========================= */
  211. addlistener(document, "keydown", event => {
  212. if (HotKey.RecomCard(event)) {
  213. event.preventDefault();
  214. let elements = document.querySelectorAll(".ytp-ce-element, .ytp-ce-covering");
  215. elements.forEach(function(element) {
  216. HideJudgment(element);
  217. });
  218. } else if (HotKey.MinimaList(event)) {
  219. event.preventDefault();
  220. set = GM_getValue("Minimalist", null);
  221. if (set && set != null) {
  222. end.style.display = "block";
  223. below.style.display = "block";
  224. secondary.style.display = "block";
  225. related.style.display = "block";
  226. GM_setValue("Minimalist", false);
  227. } else {
  228. end.style.display = "none";
  229. below.style.display = "none";
  230. secondary.style.display = "none";
  231. related.style.display = "none";
  232. GM_setValue("Minimalist", true);
  233. }
  234. } else if (HotKey.RecomPlay(event)) {
  235. event.preventDefault();
  236. let child = inner.childElementCount;
  237. if (child > 1) {// 子元素數量
  238. HideJudgment(chat, "Trigger_1");
  239. HideJudgment(secondary);
  240. HideJudgment(related);
  241. transform = false;
  242. } else {
  243. HideJudgment(chat, "Trigger_1");
  244. HideJudgment(related);
  245. transform = true;
  246. }
  247. } else if (HotKey.Message(event)) {
  248. event.preventDefault();
  249. HideJudgment(comments, "Trigger_2");
  250. } else if (HotKey.FunctionBar(event)) {
  251. event.preventDefault();
  252. HideJudgment(menu, "Trigger_3");
  253. } else if (HotKey.ListDesc(event)) {
  254. event.preventDefault();
  255. let playlist = document.querySelector("#page-manager > ytd-browse > ytd-playlist-header-renderer > div");
  256. HideJudgment(playlist, "Trigger_4");
  257. }
  258. }, { capture: true })
  259. });
  260. }
  261. }
  262. });
  263. /* 啟用觀察 */
  264. observer.observe(document.head, {childList: true, subtree: true});
  265.  
  266. /* ======================= 語言設置 ========================= */
  267.  
  268. function display_language(language) {
  269. let display = {
  270. "zh-TW": ["📜 設置快捷", `@ 功能失效時 [請重新整理] =>
  271.  
  272. (Shift) : 完全隱藏影片尾部推薦
  273. (Alt + 1) : 隱藏右側影片推薦
  274. (Alt + 2) : 隱藏留言區
  275. (Alt + 3) : 隱藏功能選項
  276. (Alt + 4) : 隱藏播放清單資訊
  277. (Ctrl + Z) : 使用極簡化`],
  278.  
  279. "zh-CN": ["📜 设置快捷", `@ 功能失效时 [请重新刷新] =>
  280. (Shift) : 全部隐藏视频尾部推荐
  281. (Alt + 1) : 隐藏右侧视频推荐
  282. (Alt + 2) : 隐藏评论区
  283. (Alt + 3) : 隐藏功能选项
  284. (Alt + 4) : 隐藏播放列表信息
  285. (Ctrl + Z) : 使用极简化`],
  286.  
  287. "ja": ["📜 設定ショートカット", `@ 機能が無効になった場合 [再読み込みしてください] =>
  288. (Shift) : 動画の最後のおすすめを完全に非表示にする
  289. (Alt + 1) : 右側の動画おすすめを非表示にする
  290. (Alt + 2) : コメント欄を非表示にする
  291. (Alt + 3) : 機能オプションを非表示にする
  292. (Alt + 4) : プレイリスト情報を非表示にする
  293. (Ctrl + Z) : 簡素化を使用する`],
  294.  
  295. "en-US": ["📜 Settings Shortcut", `@ When function fails [Please refresh] =>
  296. (Shift) : Fully hide video end recommendations
  297. (Alt + 1) : Hide right side video recommendations
  298. (Alt + 2) : Hide comments section
  299. (Alt + 3) : Hide function options
  300. (Alt + 4) : Hide playlist information
  301. (Ctrl + Z) : Use minimalism`],
  302.  
  303. "ko": ["📜 설정 바로 가기", `@ 기능이 실패하면 [새로 고침하세요] =>
  304. (Shift) : 비디오 추천을 완전히 숨기기
  305. (Alt + 1) : 오른쪽 비디오 추천 숨기기
  306. (Alt + 2) : 댓글 섹션 숨기기
  307. (Alt + 3) : 기능 옵션 숨기기
  308. (Alt + 4) : 재생 목록 정보 숨기기
  309. (Ctrl + Z) : 미니멀리즘 사용하기`]};
  310.  
  311. return display[language] || display["en-US"];
  312. }
  313. })();