Netflix Enchantments

Enhancements for Netflix video player: skip intro, skip outro, and more.

  1. // ==UserScript==
  2. // @name Netflix Enchantments
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.4.4
  5. // @description Enhancements for Netflix video player: skip intro, skip outro, and more.
  6. // @author JJJ
  7. // @match https://www.netflix.com/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=netflix.com
  9. // @require https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @grant GM_registerMenuCommand
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16. (function () {
  17. 'use strict';
  18.  
  19. const CONFIG = {
  20. enableSkipRecap: GM_getValue('enableSkipRecap', true),
  21. enableSkipIntro: GM_getValue('enableSkipIntro', true),
  22. enableSkipOutro: GM_getValue('enableSkipOutro', true),
  23. cancelFullscreen: GM_getValue('cancelFullscreen', false),
  24. hideGames: GM_getValue('hideGames', true),
  25. };
  26.  
  27. const SELECTORS = {
  28. skipRecapButton: '.button-primary.watch-video--skip-content-button.medium.hasLabel.ltr-1mjzmhv[data-uia="player-skip-recap"]',
  29. skipIntroButton: '.button-primary.watch-video--skip-content-button.medium.hasLabel.ltr-1mjzmhv[data-uia="player-skip-intro"]',
  30. skipOutroButton: '.color-primary.hasLabel.hasIcon.ltr-1jtux27',
  31. fullscreenView: '.watch-video--player-view',
  32. gamesSection: '.lolomoRow[data-list-context*="games"]'
  33. };
  34.  
  35. function createSettingsDialog() {
  36. const dialogHTML = `
  37. <div id="netflixEnchantmentsDialog" class="dpe-dialog">
  38. <h3>Netflix Enchantments</h3>
  39. ${createToggle('enableSkipRecap', 'Skip Recap', 'Automatically skip episode recaps')}
  40. ${createToggle('enableSkipIntro', 'Skip Intro', 'Automatically skip the intro of episodes')}
  41. ${createToggle('enableSkipOutro', 'Skip Outro', 'Automatically skip the outro of episodes')}
  42. ${createToggle('cancelFullscreen', 'Cancel Fullscreen', 'Automatically exit fullscreen mode')}
  43. ${createToggle('hideGames', 'Hide Games', 'Hide the games section')}
  44. <div class="dpe-button-container">
  45. <button id="saveSettingsButton" class="dpe-button dpe-button-save">Save</button>
  46. <button id="cancelSettingsButton" class="dpe-button dpe-button-cancel">Cancel</button>
  47. </div>
  48. </div>
  49. `;
  50.  
  51. const styleSheet = `
  52. <style>
  53. .dpe-dialog {
  54. position: fixed;
  55. top: 50%;
  56. left: 50%;
  57. transform: translate(-50%, -50%);
  58. background: rgba(0, 0, 0, 0.8);
  59. border: 1px solid #444;
  60. border-radius: 8px;
  61. padding: 20px;
  62. box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
  63. z-index: 9999;
  64. color: white;
  65. width: 300px;
  66. font-family: Arial, sans-serif;
  67. }
  68. .dpe-dialog h3 {
  69. margin-top: 0;
  70. font-size: 1.4em;
  71. text-align: center;
  72. margin-bottom: 20px;
  73. }
  74. .dpe-checkbox-container {
  75. display: flex;
  76. align-items: center;
  77. margin-bottom: 15px;
  78. }
  79. .dpe-checkbox-container input[type="checkbox"] {
  80. margin-right: 10px;
  81. }
  82. .dpe-button-container {
  83. display: flex;
  84. justify-content: space-between;
  85. margin-top: 20px;
  86. }
  87. .dpe-button {
  88. padding: 8px 16px;
  89. border: none;
  90. border-radius: 4px;
  91. cursor: pointer;
  92. font-size: 1em;
  93. transition: background-color 0.3s;
  94. }
  95. .dpe-button-save {
  96. background-color: #0078d4;
  97. color: white;
  98. }
  99. .dpe-button-save:hover {
  100. background-color: #005a9e;
  101. }
  102. .dpe-button-cancel {
  103. background-color: #d41a1a;
  104. color: white;
  105. }
  106. .dpe-button-cancel:hover {
  107. background-color: #a61515;
  108. }
  109. .dpe-toggle-container {
  110. display: flex;
  111. justify-content: space-between;
  112. align-items: center;
  113. margin-bottom: 15px;
  114. }
  115. .dpe-toggle-label {
  116. flex-grow: 1;
  117. }
  118. .dpe-toggle {
  119. position: relative;
  120. display: inline-block;
  121. width: 50px;
  122. height: 24px;
  123. }
  124. .dpe-toggle input {
  125. position: absolute;
  126. width: 100%;
  127. height: 100%;
  128. opacity: 0;
  129. cursor: pointer;
  130. margin: 0;
  131. }
  132. .dpe-toggle-slider {
  133. position: absolute;
  134. cursor: pointer;
  135. top: 0;
  136. left: 0;
  137. right: 0;
  138. bottom: 0;
  139. background-color: #ccc;
  140. transition: .4s;
  141. border-radius: 24px;
  142. }
  143. .dpe-toggle-slider:before {
  144. position: absolute;
  145. content: "";
  146. height: 16px;
  147. width: 16px;
  148. left: 4px;
  149. bottom: 4px;
  150. background-color: white;
  151. transition: .4s;
  152. border-radius: 50%;
  153. }
  154. .dpe-toggle input:checked + .dpe-toggle-slider {
  155. background-color: #0078d4;
  156. }
  157. .dpe-toggle input:checked + .dpe-toggle-slider:before {
  158. transform: translateX(26px);
  159. }
  160. </style>
  161. `;
  162.  
  163. const dialogWrapper = document.createElement('div');
  164. dialogWrapper.innerHTML = styleSheet + dialogHTML;
  165. document.body.appendChild(dialogWrapper);
  166.  
  167. // Add event listeners to toggles
  168. document.querySelectorAll('.dpe-toggle input').forEach(toggle => {
  169. toggle.addEventListener('change', (event) => {
  170. const { id, checked } = event.target;
  171. CONFIG[id] = checked; // Update the CONFIG object with the new value
  172. });
  173. });
  174.  
  175. // Add event listeners to buttons
  176. document.getElementById('saveSettingsButton').addEventListener('click', saveAndCloseDialog);
  177. document.getElementById('cancelSettingsButton').addEventListener('click', closeDialog);
  178. }
  179.  
  180. function createToggle(id, label, title) {
  181. return `
  182. <div class="dpe-toggle-container" title="${title}">
  183. <label class="dpe-toggle">
  184. <input type="checkbox" id="${id}" ${CONFIG[id] ? 'checked' : ''}>
  185. <span class="dpe-toggle-slider"></span>
  186. </label>
  187. <label for="${id}" class="dpe-toggle-label">${label}</label>
  188. </div>
  189. `;
  190. }
  191.  
  192. function saveAndCloseDialog() {
  193. Object.keys(CONFIG).forEach(key => {
  194. CONFIG[key] = document.getElementById(key).checked;
  195. GM_setValue(key, CONFIG[key]);
  196. });
  197. closeDialog();
  198. }
  199.  
  200. function closeDialog() {
  201. const dialog = document.getElementById('netflixEnchantmentsDialog');
  202. if (dialog) {
  203. dialog.remove();
  204. if (document.fullscreenElement) {
  205. document.exitFullscreen();
  206. }
  207. }
  208. }
  209.  
  210. function clickButton(selector) {
  211. const button = document.querySelector(selector);
  212. if (button) {
  213. button.click();
  214. }
  215. }
  216.  
  217. function enterFullscreen() {
  218. if (!document.fullscreenElement) {
  219. document.documentElement.requestFullscreen();
  220. }
  221. }
  222.  
  223. function exitFullscreen() {
  224. if (document.fullscreenElement) {
  225. document.exitFullscreen();
  226. }
  227. }
  228.  
  229. function hideGamesSection() {
  230. const gamesSections = document.querySelectorAll(SELECTORS.gamesSection);
  231. gamesSections.forEach(section => {
  232. if (section && CONFIG.hideGames) {
  233. section.style.display = 'none';
  234. }
  235. });
  236. }
  237.  
  238. function handleSkipActions() {
  239. try {
  240. if (CONFIG.enableSkipRecap) {
  241. clickButton(SELECTORS.skipRecapButton);
  242. }
  243.  
  244. if (CONFIG.enableSkipIntro) {
  245. clickButton(SELECTORS.skipIntroButton);
  246. }
  247.  
  248. if (CONFIG.enableSkipOutro) {
  249. clickButton(SELECTORS.skipOutroButton);
  250. }
  251.  
  252. if (document.querySelector(SELECTORS.fullscreenView)) {
  253. enterFullscreen();
  254. }
  255.  
  256. if (CONFIG.cancelFullscreen && document.fullscreenElement) {
  257. exitFullscreen();
  258. }
  259.  
  260. if (CONFIG.hideGames) {
  261. hideGamesSection();
  262. }
  263. } catch (error) {
  264. console.error('An error occurred:', error);
  265. }
  266. }
  267.  
  268. const observer = new MutationObserver(handleSkipActions);
  269. observer.observe(document.body, { childList: true, subtree: true });
  270.  
  271. GM_registerMenuCommand('Netflix Enchantments Settings', createSettingsDialog);
  272.  
  273. let isSettingsDialogOpen = false;
  274.  
  275. function toggleSettingsDialog() {
  276. const dialog = document.getElementById('netflixEnchantmentsDialog');
  277. if (dialog) {
  278. dialog.remove();
  279. } else {
  280. createSettingsDialog();
  281. }
  282. }
  283.  
  284. document.addEventListener('keyup', (event) => {
  285. if (event.key === 'F2') {
  286. toggleSettingsDialog();
  287. } else if (event.key === 'Escape') {
  288. exitFullscreen();
  289. closeDialog();
  290. }
  291. });
  292.  
  293. })();

QingJ © 2025

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