Youtube 隐藏工具

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

当前为 2023-11-21 提交的版本,查看 最新版本

  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.23
  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. //https://www.youtube.com/
  42. (function() {
  43. let currentUrl, pattern = /^https:\/\/www\.youtube\.com\/.+$/;
  44. /***
  45. * _ooOoo_
  46. * o8888888o
  47. * 88" . "88
  48. * (| -_- |)
  49. * O\ = /O
  50. * ___/`---'\____
  51. * . ' \\| |// `.
  52. * / \\||| : |||// \
  53. * / _||||| -:- |||||- \
  54. * | | \\\ - /// | |
  55. * | \_| ''\---/'' | |
  56. * \ .-\__ `-` ___/-. /
  57. * ___`. .' /--.--\ `. . __
  58. * ."" '< `.___\_<|>_/___.' >'"".
  59. * | | : `- \`.;`\ _ /`;.`/ - ` : | |
  60. * \ \ `-. \_ __\ /__ _/ .-` / /
  61. * ======`-.____`-.___\_____/___.-`____.-'======
  62. * `=---='
  63. * .............................................
  64. * 要準確的判斷快捷, 要完全自訂需要寫一堆定義, 實在是有點麻煩(懶)
  65. * 懂設置可於這邊修改快捷 =>
  66. */
  67. const HotKey = {
  68. RecomCard: event => event.shiftKey, // 影片結尾推薦卡
  69. MinimaList: event => event.ctrlKey && event.key == "z", // 極簡化
  70. RecomPlay: event => event.altKey && event.key == "1", // 推薦播放
  71. Message: event => event.altKey && event.key == "2", // 留言區
  72. FunctionBar: event => event.altKey && event.key == "3", // 功能區
  73. ListDesc: event => event.altKey && event.key == "4" // 播放清單資訊
  74.  
  75. }, observer = new MutationObserver(() => {
  76. currentUrl = document.URL;
  77. if (pattern.test(currentUrl) && !document.body.hasAttribute("data-hide")) {
  78. document.body.setAttribute("data-hide", true);
  79. let set, transform = false;
  80.  
  81. /* 宣告 */
  82. const VVP_Pattern = /^https:\/\/www\.youtube\.com\/watch\?v=.+$/, // 判斷在播放頁面運行
  83. Playlist_Pattern = /^https:\/\/www\.youtube\.com\/playlist\?list=.+$/, // 判斷在播放清單運行
  84. language = display_language(navigator.language),
  85. Lookup_Delay = 500, // 查找間隔
  86. Dev = true; // 開發偵錯
  87.  
  88. RunMaim();
  89. /* 註冊菜單 */
  90. GM_registerMenuCommand(language[0], function() {alert(language[1])});
  91.  
  92. /* ======================= 主運行 ========================= */
  93. async function RunMaim() {
  94. /* 修改樣式 */
  95. GM_addStyle(`
  96. .ytp-ce-element{opacity: 0.1 !important;}
  97. .ytp-ce-element:hover{opacity: 1 !important;}
  98. `);
  99.  
  100. /* ======================= 讀取設置 ========================= */
  101. WaitElem([
  102. "end",
  103. "below",
  104. "secondary",
  105. "related",
  106. "secondary-inner",
  107. "chat-container",
  108. "comments",
  109. "menu-container"
  110. ], element => {
  111. const [end, below, secondary, related, inner, chat, comments, menu] = element;
  112.  
  113. /* 獲取設置 */
  114. if (VVP_Pattern.test(currentUrl)) {
  115. // 極簡化
  116. set = GM_getValue("Minimalist", null);
  117. if (set && set !== null) {
  118. Promise.all([SetTrigger(end), SetTrigger(below), SetTrigger(secondary), SetTrigger(related)]).then(results => {
  119. results.every(result => result) && Dev ? log("極簡化") : null;
  120. });
  121. } else {
  122. // 推薦播放
  123. set = GM_getValue("Trigger_1", null);
  124. if (set && set !== null){
  125. Promise.all([SetTrigger(chat), SetTrigger(secondary), SetTrigger(related)]).then(results => {
  126. results.every(result => result) && Dev ? log("隱藏推薦播放") : null;
  127. });
  128. }
  129. // 留言區
  130. set = GM_getValue("Trigger_2", null);
  131. if (set && set !== null){
  132. SetTrigger(comments).then(() => {Dev ? log("隱藏留言區") : null});
  133. }
  134. // 功能選項
  135. set = GM_getValue("Trigger_3", null);
  136. if (set && set !== null){
  137. SetTrigger(menu).then(() => {Dev ? log("隱藏功能選項") : null});
  138. }
  139. }
  140. } else if (Playlist_Pattern.test(currentUrl)) {
  141. // 播放清單資訊
  142. set = GM_getValue("Trigger_4", null);
  143. if (set && set !== null){
  144. let interval;
  145. interval = setInterval(function() {
  146. let playlist = document.querySelector("#page-manager > ytd-browse > ytd-playlist-header-renderer > div");
  147. playlist ? SetTrigger(playlist).then(() => {clearInterval(interval)}) : null;
  148. }, Lookup_Delay);
  149. }
  150. }
  151.  
  152. /* ======================= 快捷設置 ========================= */
  153. addlistener(document, "keydown", event => {
  154. if (HotKey.RecomCard(event)) {
  155. event.preventDefault();
  156. let elements = document.querySelectorAll(".ytp-ce-element, .ytp-ce-covering");
  157. elements.forEach(function(element) {
  158. HideJudgment(element);
  159. });
  160. } else if (HotKey.MinimaList(event)) {
  161. event.preventDefault();
  162. set = GM_getValue("Minimalist", null);
  163. if (set && set != null) {
  164. end.style.display = "block";
  165. below.style.display = "block";
  166. secondary.style.display = "block";
  167. related.style.display = "block";
  168. GM_setValue("Minimalist", false);
  169. } else {
  170. end.style.display = "none";
  171. below.style.display = "none";
  172. secondary.style.display = "none";
  173. related.style.display = "none";
  174. GM_setValue("Minimalist", true);
  175. }
  176. } else if (HotKey.RecomPlay(event)) {
  177. event.preventDefault();
  178. let child = inner.childElementCount;
  179. if (child > 1) {// 子元素數量
  180. HideJudgment(chat, "Trigger_1");
  181. HideJudgment(secondary);
  182. HideJudgment(related);
  183. transform = false;
  184. } else {
  185. HideJudgment(chat, "Trigger_1");
  186. HideJudgment(related);
  187. transform = true;
  188. }
  189. } else if (HotKey.Message(event)) {
  190. event.preventDefault();
  191. HideJudgment(comments, "Trigger_2");
  192. } else if (HotKey.FunctionBar(event)) {
  193. event.preventDefault();
  194. HideJudgment(menu, "Trigger_3");
  195. } else if (HotKey.ListDesc(event)) {
  196. event.preventDefault();
  197. let playlist = document.querySelector("#page-manager > ytd-browse > ytd-playlist-header-renderer > div");
  198. HideJudgment(playlist, "Trigger_4");
  199. }
  200. })
  201. });
  202.  
  203. /* ======================= 設置 API ========================= */
  204.  
  205. /* 觸發設置 API */
  206. async function SetTrigger(element) {
  207. element.style.display = "none";
  208. return new Promise(resolve => {
  209. element.style.display === "none" ? resolve(true) : resolve(false);
  210. });
  211. }
  212.  
  213. /* 設置判斷 API */
  214. async function HideJudgment(element, gm=null) {
  215. if (element.style.display === "none" || transform) {
  216. element.style.display = "block";
  217. gm !== null ? GM_setValue(gm, false) : null
  218. } else {
  219. element.style.display = "none";
  220. gm !== null ? GM_setValue(gm, true) : null
  221. }
  222. }
  223.  
  224. /* 添加 監聽器 API (簡化版) */
  225. async function addlistener(element, type, listener, add={}) {
  226. element.addEventListener(type, listener, add);
  227. }
  228.  
  229. /* 等待元素出現 API (修改版) */
  230. async function WaitElem(selectors, callback) {
  231. const interval = setInterval(()=> {
  232. const elements = selectors.map(selector => document.getElementById(selector));
  233. Dev ? log(elements) : null;
  234. if (elements.every(element => element && Array.from(element.children).length > 0)) {
  235. clearInterval(interval);
  236. callback(elements);
  237. }
  238. }, Lookup_Delay);
  239. }
  240.  
  241. /* 開發者除錯打印 API */
  242. function log(label, type="log") {
  243. const style = {
  244. group: `padding: 5px;color: #ffffff;font-weight: bold;border-radius: 5px;background-color: #54d6f7;`,
  245. text: `padding: 3px;color: #ffffff;border-radius: 2px;background-color: #1dc52b;
  246. `
  247. }, template = {
  248. log: label=> console.log(`%c${label}`, style.text),
  249. warn: label=> console.warn(`%c${label}`, style.text),
  250. error: label=> console.error(`%c${label}`, style.text),
  251. count: label=> console.count(label),
  252. }
  253. type = typeof type === "string" && template[type] ? type : type = "log";
  254. console.groupCollapsed("%c___ 開發除錯 ___", style.group);
  255. template[type](label);
  256. console.groupEnd();
  257. }
  258. }
  259. }
  260. });
  261. /* 啟用觀察 */
  262. observer.observe(document.head, {childList: true, subtree: true});
  263.  
  264. /* ======================= 語言設置 ========================= */
  265.  
  266. function display_language(language) {
  267. let display = {
  268. "zh-TW": ["📜 設置快捷", `@ 功能失效時 [請重新整理] =>
  269.  
  270. (Shift) : 完全隱藏影片尾部推薦
  271. (Alt + 1) : 隱藏右側影片推薦
  272. (Alt + 2) : 隱藏留言區
  273. (Alt + 3) : 隱藏功能選項
  274. (Alt + 4) : 隱藏播放清單資訊
  275. (Ctrl + Z) : 使用極簡化`],
  276.  
  277. "zh-CN": ["📜 设置快捷", `@ 功能失效时 [请重新刷新] =>
  278. (Shift) : 全部隐藏视频尾部推荐
  279. (Alt + 1) : 隐藏右侧视频推荐
  280. (Alt + 2) : 隐藏评论区
  281. (Alt + 3) : 隐藏功能选项
  282. (Alt + 4) : 隐藏播放列表信息
  283. (Ctrl + Z) : 使用极简化`],
  284.  
  285. "ja": ["📜 設定ショートカット", `@ 機能が無効になった場合 [再読み込みしてください] =>
  286. (Shift) : 動画の最後のおすすめを完全に非表示にする
  287. (Alt + 1) : 右側の動画おすすめを非表示にする
  288. (Alt + 2) : コメント欄を非表示にする
  289. (Alt + 3) : 機能オプションを非表示にする
  290. (Alt + 4) : プレイリスト情報を非表示にする
  291. (Ctrl + Z) : 簡素化を使用する`],
  292.  
  293. "en-US": ["📜 Settings Shortcut", `@ When function fails [Please refresh] =>
  294. (Shift) : Fully hide video end recommendations
  295. (Alt + 1) : Hide right side video recommendations
  296. (Alt + 2) : Hide comments section
  297. (Alt + 3) : Hide function options
  298. (Alt + 4) : Hide playlist information
  299. (Ctrl + Z) : Use minimalism`],
  300.  
  301. "ko": ["📜 설정 바로 가기", `@ 기능이 실패하면 [새로 고침하세요] =>
  302. (Shift) : 비디오 추천을 완전히 숨기기
  303. (Alt + 1) : 오른쪽 비디오 추천 숨기기
  304. (Alt + 2) : 댓글 섹션 숨기기
  305. (Alt + 3) : 기능 옵션 숨기기
  306. (Alt + 4) : 재생 목록 정보 숨기기
  307. (Ctrl + Z) : 미니멀리즘 사용하기`]};
  308.  
  309. return display[language] || display["en-US"];
  310. }
  311. })();