YouTube Web Tweaks Lite

This script is based on YouTube Web Tweaks (except it keeps most stuff including shorts player)

  1. // ==UserScript==
  2. // @name YouTube Web Tweaks Lite
  3. // @version 1.5.6
  4. // @description This script is based on YouTube Web Tweaks (except it keeps most stuff including shorts player)
  5. // @author Magma_Craft
  6. // @license MIT
  7. // @match *://www.youtube.com/*
  8. // @namespace https://gf.qytechs.cn/en/users/933798
  9. // @icon https://www.youtube.com/favicon.ico
  10. // @unwrap
  11. // @run-at document-start
  12. // @unwrap
  13. // @grant none
  14. // ==/UserScript==
  15. // Enable strict mode to catch common coding mistakes
  16. "use strict";
  17. // Define the flags to assign to the EXPERIMENT_FLAGS object
  18. const flagsToAssign = {
  19. desktop_delay_player_resizing: false,
  20. web_animated_actions: false,
  21. web_animated_like: false,
  22. web_animated_like_lazy_load: false,
  23. smartimation_background: false,
  24. kevlar_refresh_on_theme_change: false,
  25. // Disable cinematics (aka ambient lighting)
  26. kevlar_measure_ambient_mode_idle: false,
  27. kevlar_watch_cinematics_invisible: false,
  28. web_cinematic_theater_mode: false,
  29. web_cinematic_fullscreen: false,
  30. enable_cinematic_blur_desktop_loading: false,
  31. kevlar_watch_cinematics: false,
  32. web_cinematic_masthead: false,
  33. web_watch_cinematics_preferred_reduced_motion_default_disabled: false
  34. };
  35. const updateFlags = () => {
  36. // Check if the EXPERIMENT_FLAGS object exists in the window.yt.config_ property chain
  37. const expFlags = window?.yt?.config_?.EXPERIMENT_FLAGS;
  38. // If EXPERIMENT_FLAGS is not found, exit the function
  39. if (!expFlags) return;
  40. // Assign the defined flags to the EXPERIMENT_FLAGS object
  41. Object.assign(expFlags, flagsToAssign);
  42. };
  43. // Create a MutationObserver that calls the updateFlags function when changes occur in the document's subtree
  44. const mutationObserver = new MutationObserver(updateFlags);
  45. mutationObserver.observe(document, { subtree: true, childList: true });
  46. (function() {
  47. let css = `
  48. /* Remove filter categories on search results and playlists to make the UI less usable on low-entry machines */
  49. ytd-item-section-renderer.style-scope.ytd-section-list-renderer[page-subtype="playlist"] > #header.ytd-item-section-renderer > ytd-feed-filter-chip-bar-renderer {
  50. display: none !important;
  51. }
  52. div#chip-bar.style-scope.ytd-search-header-renderer > yt-chip-cloud-renderer.style-scope.ytd-search-header-renderer > div#container.style-scope.yt-chip-cloud-renderer {
  53. display: none !important;
  54. }
  55. /* Remove (almost) all annoyances (excludes 'YT TV and Premium' banners) */
  56. ytd-action-companion-ad-renderer, ytd-display-ad-renderer, ytd-video-masthead-ad-advertiser-info-renderer, ytd-video-masthead-ad-primary-video-renderer, ytd-in-feed-ad-layout-renderer, ytd-ad-slot-renderer, yt-about-this-ad-renderer, yt-mealbar-promo-renderer, ytd-ad-slot-renderer, ytd-in-feed-ad-layout-renderer, .ytd-video-masthead-ad-v3-renderer, div#root.style-scope.ytd-display-ad-renderer.yt-simple-endpoint, div#sparkles-container.style-scope.ytd-promoted-sparkles-web-renderer, div#main-container.style-scope.ytd-promoted-video-renderer, div#player-ads.style-scope.ytd-watch-flexy, ad-slot-renderer, ytm-promoted-sparkles-web-renderer, masthead-ad, #masthead-ad, ytd-video-quality-promo-renderer {
  57. display: none !important
  58. }`;
  59. if (typeof GM_addStyle !== "undefined") {
  60. GM_addStyle(css);
  61. } else {
  62. let styleNode = document.createElement("style");
  63. styleNode.appendChild(document.createTextNode(css));
  64. (document.querySelector("head") || document.documentElement).appendChild(styleNode);
  65. }
  66. })();
  67. // Auto skip ads and force disable autopause (special thanks to CY Fung for the ads skip script code
  68. (() => {
  69. let popupState = 0;
  70. let popupElement = null;
  71. const rate = 1;
  72. const Promise = (async () => { })().constructor;
  73. const PromiseExternal = ((resolve_, reject_) => {
  74. const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject };
  75. return class PromiseExternal extends Promise {
  76. constructor(cb = h) {
  77. super(cb);
  78. if (cb === h) {
  79. /** @type {(value: any) => void} */
  80. this.resolve = resolve_;
  81. /** @type {(reason?: any) => void} */
  82. this.reject = reject_;
  83. }
  84. }
  85. };
  86. })();
  87. const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0);
  88. let vload = null;
  89. const fastSeekFn = HTMLVideoElement.prototype.fastSeek || null;
  90. const addEventListenerFn = HTMLElement.prototype.addEventListener;
  91. if (!addEventListenerFn) return;
  92. const removeEventListenerFn = HTMLElement.prototype.removeEventListener;
  93. if (!removeEventListenerFn) return;
  94. const ytPremiumPopupSelector = 'yt-mealbar-promo-renderer.style-scope.ytd-popup-container:not([hidden])';
  95. const DEBUG = 0;
  96. const rand = (a, b) => a + Math.random() * (b - a);
  97. const log = DEBUG ? console.log.bind(console) : () => 0;
  98. //$0.$['dismiss-button'].click()
  99. const ytPremiumPopupClose = function () {
  100. const popup = document.querySelector(ytPremiumPopupSelector);
  101. if (popup instanceof HTMLElement) {
  102. if (HTMLElement.prototype.closest.call(popup, '[hidden]')) return;
  103. const cnt = insp(popup);
  104. const btn = cnt.$ ? cnt.$['dismiss-button'] : 0;
  105. if (btn instanceof HTMLElement && HTMLElement.prototype.closest.call(btn, '[hidden]')) return;
  106. btn && btn.click();
  107. }
  108. }
  109. //div.video-ads.ytp-ad-module
  110. const clickSkip = function () {
  111. // ytp-ad-skip-button
  112. const isAdsContainerContainsButton = document.querySelector('.video-ads.ytp-ad-module button');
  113. if (isAdsContainerContainsButton) {
  114. const btnFilter = e => HTMLElement.prototype.matches.call(e, ".ytp-ad-overlay-close-button, .ytp-ad-skip-button-modern, .ytp-ad-skip-button") && !HTMLElement.prototype.closest.call(e, '[hidden]');
  115. const btns = [...document.querySelectorAll('.video-ads.ytp-ad-module button[class*="ytp-ad-"]')].filter(btnFilter);
  116. console.log('# of ads skip btns', btns.length);
  117. if (btns.length !== 1) return;
  118. const btn = btns[0];
  119. if (btn instanceof HTMLElement) {
  120. btn.click();
  121. }
  122. }
  123. };
  124. const adsEndHandlerHolder = function (evt) {
  125. adsEndHandler && adsEndHandler(evt);
  126. }
  127. let adsEndHandler = null;
  128. const videoPlayingHandler = async function (evt) {
  129. try {
  130. if (!evt || !evt.target || !evt.isTrusted || !(evt instanceof Event)) return;
  131. const video = evt.target;
  132. const checkPopup = popupState === 1;
  133. popupState = 0;
  134. const popupElementValue = popupElement;
  135. popupElement = null;
  136. if (video.duration < 0.8) return;
  137. await vload.then();
  138. if (!video.isConnected) return;
  139. const ytplayer = HTMLElement.prototype.closest.call(video, 'ytd-player, ytmusic-player');
  140. if (!ytplayer || !ytplayer.is) return;
  141. const ytplayerCnt = insp(ytplayer);
  142. const player_ = await (ytplayerCnt.player_ || ytplayer.player_ || ytplayerCnt.playerApi || ytplayer.playerApi || 0);
  143. if (!player_) return;
  144. if (typeof ytplayerCnt.getPlayer === 'function' && !ytplayerCnt.getPlayer()) {
  145. await new Promise(r => setTimeout(r, 40));
  146. }
  147. const playerController = await ytplayerCnt.getPlayer() || player_;
  148. if (!video.isConnected) return;
  149. if ('getPresentingPlayerType' in playerController && 'getDuration' in playerController) {
  150. const ppType = await playerController.getPresentingPlayerType();
  151. log('m02a', ppType);
  152. if (ppType === 1 || typeof ppType !== 'number') return; // ads shall be ppType === 2
  153. // const progressState = player_.getProgressState();
  154. // log('m02b', progressState);
  155. // if(!progressState) return;
  156. // const q = progressState.duration;
  157. // if (popupState === 1) console.debug('m05b:ytPremiumPopup', document.querySelector(ytPremiumPopupSelector))
  158. const q = video.duration;
  159. const ytDuration = await playerController.getDuration();
  160. log('m02c', q, ytDuration, Math.abs(ytDuration - q));
  161. if (q > 0.8 && ytDuration > 2.5 && Math.abs(ytDuration - q) > 1.4) {
  162. try {
  163. log('m02s', 'fastSeek', q);
  164. video.muted = true;
  165. const w = Math.round(rand(582, 637) * rate);
  166. const sq = q - w / 1000;
  167. adsEndHandler = null;
  168. const expired = Date.now() + 968;
  169. removeEventListenerFn.call(video, 'ended', adsEndHandlerHolder, false);
  170. removeEventListenerFn.call(video, 'suspend', adsEndHandlerHolder, false);
  171. removeEventListenerFn.call(video, 'durationchange', adsEndHandlerHolder, false);
  172. addEventListenerFn.call(video, 'ended', adsEndHandlerHolder, false);
  173. addEventListenerFn.call(video, 'suspend', adsEndHandlerHolder, false);
  174. addEventListenerFn.call(video, 'durationchange', adsEndHandlerHolder, false);
  175. adsEndHandler = async function (evt) {
  176. adsEndHandler = null;
  177. removeEventListenerFn.call(video, 'ended', adsEndHandlerHolder, false);
  178. removeEventListenerFn.call(video, 'suspend', adsEndHandlerHolder, false);
  179. removeEventListenerFn.call(video, 'durationchange', adsEndHandlerHolder, false);
  180. if (Date.now() < expired) {
  181. const delay = Math.round(rand(92, 117));
  182. await new Promise(r => setTimeout(r, delay));
  183. Promise.resolve().then(() => {
  184. clickSkip();
  185. }).catch(console.warn);
  186. checkPopup && Promise.resolve().then(() => {
  187. const currentPopup = document.querySelector(ytPremiumPopupSelector);
  188. if (popupElementValue ? currentPopup === popupElementValue : currentPopup) {
  189. ytPremiumPopupClose();
  190. }
  191. }).catch(console.warn);
  192. }
  193. };
  194. if (fastSeekFn) fastSeekFn.call(video, sq);
  195. else video.currentTime = sq;
  196. } catch (e) {
  197. console.warn(e);
  198. }
  199. }
  200. }
  201. } catch (e) {
  202. console.warn(e);
  203. }
  204. };
  205. document.addEventListener('loadedmetadata', async function (evt) {
  206. try {
  207. if (!evt || !evt.target || !evt.isTrusted || !(evt instanceof Event)) return;
  208. const video = evt.target;
  209. if (video.nodeName !== "VIDEO") return;
  210. if (video.duration < 0.8) return;
  211. if (!video.matches('.video-stream.html5-main-video')) return;
  212. popupState = 0;
  213. vload = new PromiseExternal();
  214. popupElement = document.querySelector(ytPremiumPopupSelector);
  215. removeEventListenerFn.call(video, 'playing', videoPlayingHandler, { passive: true, capture: false });
  216. addEventListenerFn.call(video, 'playing', videoPlayingHandler, { passive: true, capture: false });
  217. popupState = 1;
  218. let trial = 6;
  219. await new Promise(resolve => {
  220. let io = new IntersectionObserver(entries => {
  221. if (trial-- <= 0 || (entries && entries.length >= 1 && video.matches('ytd-player video, ytmusic-player video'))) {
  222. resolve();
  223. io.disconnect();
  224. io = null;
  225. }
  226. });
  227. io.observe(video);
  228. });
  229. vload.resolve();
  230. } catch (e) {
  231. console.warn(e);
  232. }
  233. }, true);
  234. })();
  235. Object.defineProperties(document, { /*'hidden': {value: false},*/ 'webkitHidden': {value: false}, 'visibilityState': {value: 'visible'}, 'webkitVisibilityState': {value: 'visible'} });
  236. setInterval(function(){
  237. document.dispatchEvent( new KeyboardEvent( 'keyup', { bubbles: true, cancelable: true, keyCode: 143, which: 143 } ) );
  238. }, 60000);

QingJ © 2025

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