YouTube Enchantments

Automatically likes videos of channels you're subscribed to, scrolls down on Youtube with a toggle button, and bypasses the AdBlock ban.

  1. // ==UserScript==
  2. // @name YouTube Enchantments
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.8.2
  5. // @description Automatically likes videos of channels you're subscribed to, scrolls down on Youtube with a toggle button, and bypasses the AdBlock ban.
  6. // @author JJJ
  7. // @match https://www.youtube.com/*
  8. // @exclude https://www.youtube.com/*/community
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_registerMenuCommand
  13. // @run-at document-idle
  14. // @noframes
  15. // @license MIT
  16. // ==/UserScript==
  17.  
  18. (() => {
  19. 'use strict';
  20.  
  21. // Add logger configuration
  22. const Logger = {
  23. styles: {
  24. info: 'color: #2196F3; font-weight: bold',
  25. warning: 'color: #FFC107; font-weight: bold',
  26. success: 'color: #4CAF50; font-weight: bold',
  27. error: 'color: #F44336; font-weight: bold'
  28. },
  29. prefix: '[YouTubeEnchantments]',
  30. getTimestamp() {
  31. return new Date().toISOString().split('T')[1].slice(0, -1);
  32. },
  33. info(msg) {
  34. console.log(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.info);
  35. },
  36. warning(msg) {
  37. console.warn(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.warning);
  38. },
  39. success(msg) {
  40. console.log(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.success);
  41. },
  42. error(msg) {
  43. console.error(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.error);
  44. }
  45. };
  46.  
  47. // Polyfill for Edge if necessary
  48. if (!window.Blob || !window.URL || !window.Worker) {
  49. Logger.warning('Browser compatibility features missing');
  50. return;
  51. }
  52.  
  53. // Inject the YouTube IFrame API script with error handling
  54. function injectYouTubeAPI() {
  55. return new Promise((resolve, reject) => {
  56. const script = document.createElement('script');
  57. script.src = 'https://www.youtube.com/iframe_api';
  58. script.onload = resolve;
  59. script.onerror = reject;
  60. document.head.appendChild(script);
  61. });
  62. }
  63.  
  64. // Updated constants
  65. const SELECTORS = {
  66. PLAYER: '#movie_player',
  67. SUBSCRIBE_BUTTON: '#subscribe-button > ytd-subscribe-button-renderer, ytd-reel-player-overlay-renderer #subscribe-button, tp-yt-paper-button[subscribed]',
  68. LIKE_BUTTON: 'ytd-menu-renderer button[aria-pressed][aria-label*="like"], like-button-view-model button[aria-pressed]',
  69. DISLIKE_BUTTON: 'ytd-menu-renderer button[aria-pressed][aria-label*="dislike"], dislike-button-view-model button[aria-pressed]',
  70. PLAYER_CONTAINER: '#player-container-outer',
  71. ERROR_SCREEN: '#error-screen',
  72. PLAYABILITY_ERROR: '.yt-playability-error-supported-renderers',
  73. LIVE_BADGE: '.ytp-live-badge',
  74. GAME_SECTION: 'ytd-rich-section-renderer, div#dismissible.style-scope.ytd-rich-shelf-renderer'
  75. };
  76.  
  77. const CONSTANTS = {
  78. IFRAME_ID: 'adblock-bypass-player',
  79. STORAGE_KEY: 'youtubeEnchantmentsSettings',
  80. DELAY: 300, // Increased delay for Edge
  81. MAX_TRIES: 150, // Increased max tries
  82. DUPLICATE_CHECK_INTERVAL: 7000, // Increased interval
  83. GAME_CHECK_INTERVAL: 2000 // Interval to check for game sections
  84. };
  85.  
  86. const defaultSettings = {
  87. autoLikeEnabled: true,
  88. autoLikeLiveStreams: false,
  89. likeIfNotSubscribed: false,
  90. watchThreshold: 0,
  91. checkFrequency: 5000,
  92. adBlockBypassEnabled: false,
  93. scrollSpeed: 50,
  94. removeGamesEnabled: true
  95. };
  96.  
  97. let settings = loadSettings();
  98. const autoLikedVideoIds = new Set();
  99. let isScrolling = false;
  100. let scrollInterval;
  101. let currentPageUrl = window.location.href;
  102. let tries = 0;
  103.  
  104. const worker = createWorker();
  105.  
  106. const urlUtils = {
  107. extractParams(url) {
  108. try {
  109. const params = new URL(url).searchParams;
  110. return {
  111. videoId: params.get('v'),
  112. playlistId: params.get('list'),
  113. index: params.get('index')
  114. };
  115. } catch (e) {
  116. console.error('Failed to extract URL params:', e);
  117. return {};
  118. }
  119. },
  120.  
  121. getTimestampFromUrl(url) {
  122. try {
  123. const timestamp = new URL(url).searchParams.get('t');
  124. if (timestamp) {
  125. const timeArray = timestamp.split(/h|m|s/).map(Number);
  126. const timeInSeconds = timeArray.reduce((acc, time, index) =>
  127. acc + time * Math.pow(60, 2 - index), 0);
  128. return `&start=${timeInSeconds}`;
  129. }
  130. } catch (e) {
  131. console.error('Failed to extract timestamp:', e);
  132. }
  133. return '';
  134. }
  135. };
  136.  
  137. let player;
  138.  
  139. // Updated PlayerManager
  140. const playerManager = {
  141. async initPlayer() {
  142. try {
  143. await injectYouTubeAPI();
  144. const iframe = document.getElementById(CONSTANTS.IFRAME_ID);
  145. if (iframe) {
  146. player = new YT.Player(CONSTANTS.IFRAME_ID, {
  147. events: {
  148. 'onReady': this.onPlayerReady.bind(this),
  149. 'onStateChange': this.onPlayerStateChange.bind(this),
  150. 'onError': (event) => {
  151. Logger.error(`Player error: ${event.data}`);
  152. }
  153. }
  154. });
  155. }
  156. } catch (error) {
  157. Logger.error(`Failed to initialize player: ${error}`);
  158. }
  159. },
  160.  
  161. onPlayerReady(event) {
  162. Logger.info('Player is ready');
  163. },
  164.  
  165. onPlayerStateChange(event) {
  166. if (event.data === YT.PlayerState.AD_STARTED) {
  167. Logger.info('Ad is playing, allowing ad to complete.');
  168. } else if (event.data === YT.PlayerState.ENDED || event.data === YT.PlayerState.PLAYING) {
  169. Logger.info('Video is playing, ensuring it is tracked in history.');
  170. }
  171. },
  172.  
  173. createIframe(url) {
  174. try {
  175. const { videoId, playlistId, index } = urlUtils.extractParams(url);
  176. if (!videoId) return null;
  177.  
  178. const iframe = document.createElement('iframe');
  179. const commonArgs = 'autoplay=1&modestbranding=1&enablejsapi=1&origin=' + encodeURIComponent(window.location.origin);
  180. const embedUrl = playlistId
  181. ? `https://www.youtube.com/embed/${videoId}?${commonArgs}&list=${playlistId}&index=${index}`
  182. : `https://www.youtube.com/embed/${videoId}?${commonArgs}${urlUtils.getTimestampFromUrl(url)}`;
  183.  
  184. this.setIframeAttributes(iframe, embedUrl);
  185. return iframe;
  186. } catch (error) {
  187. Logger.error(`Failed to create iframe: ${error}`);
  188. return null;
  189. }
  190. },
  191.  
  192. setIframeAttributes(iframe, url) {
  193. iframe.id = CONSTANTS.IFRAME_ID;
  194. iframe.src = url;
  195. iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share';
  196. iframe.allowFullscreen = true;
  197. iframe.style.cssText = 'height:100%; width:calc(100% - 240px); border:none; border-radius:12px; position:relative; left:240px;';
  198. },
  199.  
  200. replacePlayer(url) {
  201. const playerContainer = document.querySelector(SELECTORS.ERROR_SCREEN);
  202. if (!playerContainer) return;
  203.  
  204. let iframe = document.getElementById(CONSTANTS.IFRAME_ID);
  205. if (iframe) {
  206. this.setIframeAttributes(iframe, url);
  207. } else {
  208. iframe = this.createIframe(url);
  209. if (iframe) {
  210. playerContainer.appendChild(iframe);
  211. this.initPlayer();
  212. }
  213. }
  214. // Ensure the iframe is on top of the player container
  215. this.bringToFront(CONSTANTS.IFRAME_ID);
  216. this.addScrollListener();
  217. },
  218.  
  219. bringToFront(elementId) {
  220. const element = document.getElementById(elementId);
  221. if (element) {
  222. const maxZIndex = Math.max(
  223. ...Array.from(document.querySelectorAll('*'))
  224. .map(e => parseInt(window.getComputedStyle(e).zIndex) || 0)
  225. );
  226. element.style.zIndex = maxZIndex + 1;
  227. }
  228. },
  229.  
  230. removeDuplicates() {
  231. const iframes = document.querySelectorAll(`#${CONSTANTS.IFRAME_ID}`);
  232. if (iframes.length > 1) {
  233. Array.from(iframes).slice(1).forEach(iframe => iframe.remove());
  234. }
  235. },
  236.  
  237. addScrollListener() {
  238. window.addEventListener('scroll', this.handleScroll);
  239. },
  240.  
  241. handleScroll() {
  242. const iframe = document.getElementById(CONSTANTS.IFRAME_ID);
  243. if (!iframe) return;
  244.  
  245. const playerContainer = document.querySelector(SELECTORS.ERROR_SCREEN);
  246. if (!playerContainer) return;
  247.  
  248. const rect = playerContainer.getBoundingClientRect();
  249. if (rect.top < 0) {
  250. iframe.style.position = 'fixed';
  251. iframe.style.top = '0';
  252. iframe.style.left = '240px';
  253. iframe.style.width = 'calc(100% - 240px)';
  254. iframe.style.height = 'calc(100vh - 56px)'; // Adjust height as needed
  255. } else {
  256. iframe.style.position = 'relative';
  257. iframe.style.top = '0';
  258. iframe.style.left = '240px';
  259. iframe.style.width = 'calc(100% - 240px)';
  260. iframe.style.height = '100%';
  261. }
  262. }
  263. };
  264.  
  265. // Updated worker with error handling
  266. function createWorker() {
  267. try {
  268. const workerBlob = new Blob([`
  269. let checkInterval;
  270. self.onmessage = function(e) {
  271. try {
  272. if (e.data.type === 'startCheck') {
  273. if (checkInterval) clearInterval(checkInterval);
  274. checkInterval = setInterval(() => {
  275. self.postMessage({ type: 'check' });
  276. }, e.data.checkFrequency);
  277. } else if (e.data.type === 'stopCheck') {
  278. clearInterval(checkInterval);
  279. }
  280. } catch (error) {
  281. self.postMessage({ type: 'error', error: error.message });
  282. }
  283. };
  284. `], { type: 'text/javascript' });
  285.  
  286. const worker = new Worker(URL.createObjectURL(workerBlob));
  287. worker.onerror = function (error) {
  288. Logger.error(`Worker error: ${error}`);
  289. };
  290. return worker;
  291. } catch (error) {
  292. Logger.error(`Failed to create worker: ${error}`);
  293. return null;
  294. }
  295. }
  296.  
  297. function loadSettings() {
  298. const savedSettings = GM_getValue(CONSTANTS.STORAGE_KEY, {});
  299. return Object.keys(defaultSettings).reduce((acc, key) => {
  300. acc[key] = key in savedSettings ? savedSettings[key] : defaultSettings[key];
  301. return acc;
  302. }, {});
  303. }
  304.  
  305. function saveSettings() {
  306. GM_setValue(CONSTANTS.STORAGE_KEY, settings);
  307. }
  308.  
  309. function createSettingsMenu() {
  310. GM_registerMenuCommand('YouTube Enchantments Settings', showSettingsDialog);
  311. }
  312.  
  313. function showSettingsDialog() {
  314. let dialog = document.getElementById('youtube-enchantments-settings');
  315. if (!dialog) {
  316. dialog = createSettingsDialog();
  317. document.body.appendChild(dialog);
  318. }
  319. dialog.style.display = 'block';
  320. }
  321.  
  322. function createSettingsDialog() {
  323. const dialog = document.createElement('div');
  324. dialog.id = 'youtube-enchantments-settings';
  325.  
  326. const dialogHTML = `
  327. <div class="dpe-dialog">
  328. <h3>YouTube Enchantments</h3>
  329. ${createToggle('autoLikeEnabled', 'Auto Like', 'Automatically like videos of subscribed channels')}
  330. ${createToggle('autoLikeLiveStreams', 'Like Live Streams', 'Include live streams in auto-like feature')}
  331. ${createToggle('likeIfNotSubscribed', 'Like All Videos', 'Like videos even if not subscribed')}
  332. ${createToggle('adBlockBypassEnabled', 'AdBlock Bypass', 'Bypass AdBlock detection')}
  333. ${createToggle('removeGamesEnabled', 'Remove Games', 'Hide game sections from YouTube homepage')}
  334. <div class="dpe-slider-container" title="Percentage of video to watch before liking">
  335. <label for="watchThreshold">Watch Threshold</label>
  336. <input type="range" id="watchThreshold" min="0" max="100" step="10"
  337. value="${settings.watchThreshold}">
  338. <span id="watchThresholdValue">${settings.watchThreshold}%</span>
  339. </div>
  340. <div class="dpe-slider-container" title="Speed of auto-scroll (pixels per interval)">
  341. <label for="scrollSpeed">Scroll Speed</label>
  342. <input type="range" id="scrollSpeed" data-setting="scrollSpeed" min="10" max="100" step="5"
  343. value="${settings.scrollSpeed}">
  344. <span id="scrollSpeedValue">${settings.scrollSpeed}px</span>
  345. </div>
  346. <div class="dpe-button-container">
  347. <button id="saveSettingsButton" class="dpe-button dpe-button-save">Save</button>
  348. <button id="closeSettingsButton" class="dpe-button dpe-button-cancel">Cancel</button>
  349. </div>
  350. </div>
  351. `;
  352.  
  353. const styleSheet = `
  354. <style>
  355. .dpe-dialog {
  356. position: fixed;
  357. top: 50%;
  358. left: 50%;
  359. transform: translate(-50%, -50%);
  360. background: #030d22;
  361. border: 1px solid #2a2945;
  362. border-radius: 12px;
  363. padding: 24px;
  364. box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
  365. z-index: 9999;
  366. color: #ffffff;
  367. width: 320px;
  368. font-family: 'Roboto', Arial, sans-serif;
  369. }
  370. .dpe-dialog h3 {
  371. margin-top: 0;
  372. font-size: 1.8em;
  373. text-align: center;
  374. margin-bottom: 24px;
  375. color: #ffffff;
  376. font-weight: 700;
  377. }
  378. .dpe-toggle-container {
  379. display: flex;
  380. justify-content: space-between;
  381. align-items: center;
  382. margin-bottom: 16px;
  383. padding: 8px;
  384. border-radius: 8px;
  385. background: #15132a;
  386. transition: background-color 0.2s;
  387. }
  388. .dpe-toggle-container:hover {
  389. background: #1a1832;
  390. }
  391. .dpe-toggle-label {
  392. flex-grow: 1;
  393. color: #ffffff;
  394. font-size: 1.1em;
  395. font-weight: 600;
  396. margin-left: 12px;
  397. }
  398. .dpe-toggle {
  399. position: relative;
  400. display: inline-block;
  401. width: 46px;
  402. height: 24px;
  403. }
  404. .dpe-toggle input {
  405. position: absolute;
  406. width: 100%;
  407. height: 100%;
  408. opacity: 0;
  409. cursor: pointer;
  410. margin: 0;
  411. }
  412. .dpe-toggle-slider {
  413. position: absolute;
  414. cursor: pointer;
  415. top: 0;
  416. left: 0;
  417. right: 0;
  418. bottom: 0;
  419. background-color: #2a2945;
  420. transition: .3s;
  421. border-radius: 24px;
  422. }
  423. .dpe-toggle-slider:before {
  424. position: absolute;
  425. content: "";
  426. height: 18px;
  427. width: 18px;
  428. left: 3px;
  429. bottom: 3px;
  430. background-color: #ffffff;
  431. transition: .3s;
  432. border-radius: 50%;
  433. }
  434. .dpe-toggle input:checked + .dpe-toggle-slider {
  435. background-color: #cc0000;
  436. }
  437. .dpe-toggle input:checked + .dpe-toggle-slider:before {
  438. transform: translateX(22px);
  439. }
  440. .dpe-slider-container {
  441. margin: 24px 0;
  442. padding: 12px;
  443. background: #15132a;
  444. border-radius: 8px;
  445. }
  446. .dpe-slider-container label {
  447. display: block;
  448. margin-bottom: 8px;
  449. color: #ffffff;
  450. font-size: 1.1em;
  451. font-weight: 600;
  452. }
  453. .dpe-slider-container input[type="range"] {
  454. width: 100%;
  455. margin: 8px 0;
  456. height: 4px;
  457. background: #2a2945;
  458. border-radius: 2px;
  459. -webkit-appearance: none;
  460. }
  461. .dpe-slider-container input[type="range"]::-webkit-slider-thumb {
  462. -webkit-appearance: none;
  463. width: 16px;
  464. height: 16px;
  465. background: #cc0000;
  466. border-radius: 50%;
  467. cursor: pointer;
  468. transition: background-color 0.2s;
  469. }
  470. .dpe-slider-container input[type="range"]::-webkit-slider-thumb:hover {
  471. background: #990000;
  472. }
  473. .dpe-button-container {
  474. display: flex;
  475. justify-content: space-between;
  476. margin-top: 24px;
  477. gap: 12px;
  478. }
  479. .dpe-button {
  480. padding: 10px 20px;
  481. border: none;
  482. border-radius: 6px;
  483. cursor: pointer;
  484. font-size: 1.1em;
  485. font-weight: 600;
  486. transition: all 0.2s;
  487. flex: 1;
  488. }
  489. .dpe-button-save {
  490. background-color: #cc0000;
  491. color: white;
  492. }
  493. .dpe-button-save:hover {
  494. background-color: #990000;
  495. transform: translateY(-1px);
  496. }
  497. .dpe-button-cancel {
  498. background-color: #15132a;
  499. color: white;
  500. border: 1px solid #2a2945;
  501. }
  502. .dpe-button-cancel:hover {
  503. background-color: #1a1832;
  504. transform: translateY(-1px);
  505. }
  506. </style>
  507. `;
  508.  
  509. dialog.innerHTML = styleSheet + dialogHTML;
  510.  
  511. // Add event listeners
  512. dialog.querySelector('#saveSettingsButton').addEventListener('click', () => {
  513. saveSettings();
  514. hideSettingsDialog();
  515. });
  516.  
  517. dialog.querySelector('#closeSettingsButton').addEventListener('click', hideSettingsDialog);
  518.  
  519. dialog.querySelectorAll('.dpe-toggle input').forEach(toggle => {
  520. toggle.addEventListener('change', handleSettingChange);
  521. });
  522.  
  523. dialog.querySelector('#watchThreshold').addEventListener('input', handleSliderInput);
  524. dialog.querySelector('#scrollSpeed').addEventListener('input', handleSliderInput);
  525.  
  526. return dialog;
  527. }
  528.  
  529. function createToggle(id, label, title) {
  530. return `
  531. <div class="dpe-toggle-container" title="${title}">
  532. <label class="dpe-toggle">
  533. <input type="checkbox" data-setting="${id}" ${settings[id] ? 'checked' : ''}>
  534. <span class="dpe-toggle-slider"></span>
  535. </label>
  536. <label class="dpe-toggle-label">${label}</label>
  537. </div>
  538. `;
  539. }
  540.  
  541. function hideSettingsDialog() {
  542. const dialog = document.getElementById('youtube-enchantments-settings');
  543. if (dialog) {
  544. dialog.style.display = 'none';
  545. }
  546. }
  547.  
  548. function formatSettingName(setting) {
  549. return setting.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());
  550. }
  551.  
  552. function handleSettingChange(e) {
  553. if (e.target.dataset.setting) {
  554. if (e.target.type === 'checkbox') {
  555. toggleSetting(e.target.dataset.setting);
  556. } else if (e.target.type === 'range') {
  557. updateNumericSetting(e.target.dataset.setting, e.target.value);
  558. }
  559.  
  560. // Log the status of adBlockBypassEnabled if it is changed
  561. if (e.target.dataset.setting === 'adBlockBypassEnabled') {
  562. Logger.info(`AdBlock Ban Bypass is ${e.target.checked ? 'enabled' : 'disabled'}`);
  563. }
  564. }
  565. }
  566.  
  567.  
  568. function handleSliderInput(e) {
  569. if (e.target.type === 'range') {
  570. const value = e.target.value;
  571. if (e.target.id === 'watchThreshold') {
  572. document.getElementById('watchThresholdValue').textContent = `${value}%`;
  573. } else if (e.target.id === 'scrollSpeed') {
  574. document.getElementById('scrollSpeedValue').textContent = `${value}px`;
  575. }
  576. updateNumericSetting(e.target.dataset.setting, value);
  577. }
  578. }
  579.  
  580. function toggleSetting(settingName) {
  581. settings[settingName] = !settings[settingName];
  582. saveSettings();
  583. }
  584.  
  585. function updateNumericSetting(settingName, value) {
  586. settings[settingName] = parseInt(value, 10);
  587. saveSettings();
  588. }
  589.  
  590. function startBackgroundCheck() {
  591. worker.postMessage({ type: 'startCheck', checkFrequency: settings.checkFrequency });
  592. }
  593.  
  594. function checkAndLikeVideo() {
  595. Logger.info('Checking if video should be liked...');
  596. if (watchThresholdReached()) {
  597. Logger.info('Watch threshold reached.');
  598. if (settings.autoLikeEnabled) {
  599. Logger.info('Auto-like is enabled.');
  600. if (settings.likeIfNotSubscribed || isSubscribed()) {
  601. Logger.info('User is subscribed or likeIfNotSubscribed is enabled.');
  602. if (settings.autoLikeLiveStreams || !isLiveStream()) {
  603. Logger.info('Video is not a live stream or auto-like for live streams is enabled.');
  604. likeVideo();
  605. } else {
  606. Logger.info('Video is a live stream and auto-like for live streams is disabled.');
  607. }
  608. } else {
  609. Logger.info('User is not subscribed and likeIfNotSubscribed is disabled.');
  610. }
  611. } else {
  612. Logger.info('Auto-like is disabled.');
  613. }
  614. } else {
  615. Logger.info('Watch threshold not reached.');
  616. }
  617. }
  618.  
  619. function watchThresholdReached() {
  620. const player = document.querySelector(SELECTORS.PLAYER);
  621. if (player) {
  622. const watched = player.getCurrentTime() / player.getDuration();
  623. const watchedTarget = settings.watchThreshold / 100;
  624. if (watched < watchedTarget) {
  625. Logger.info(`Waiting until watch threshold reached (${watched.toFixed(2)}/${watchedTarget})...`);
  626. return false;
  627. }
  628. }
  629. return true;
  630. }
  631.  
  632. function isSubscribed() {
  633. const subscribeButton = document.querySelector(SELECTORS.SUBSCRIBE_BUTTON);
  634. return subscribeButton && (subscribeButton.hasAttribute('subscribe-button-invisible') || subscribeButton.hasAttribute('subscribed'));
  635. }
  636.  
  637. function isLiveStream() {
  638. const liveBadge = document.querySelector(SELECTORS.LIVE_BADGE);
  639. return liveBadge && window.getComputedStyle(liveBadge).display !== 'none';
  640. }
  641.  
  642. function likeVideo() {
  643. Logger.info('Attempting to like the video...');
  644. const likeButton = document.querySelector(SELECTORS.LIKE_BUTTON);
  645. const dislikeButton = document.querySelector(SELECTORS.DISLIKE_BUTTON);
  646. const videoId = getVideoId();
  647.  
  648. if (!likeButton || !dislikeButton || !videoId) {
  649. Logger.info('Like button, dislike button, or video ID not found.');
  650. return;
  651. }
  652.  
  653. if (!isButtonPressed(likeButton) && !isButtonPressed(dislikeButton) && !autoLikedVideoIds.has(videoId)) {
  654. Logger.info('Liking the video...');
  655. likeButton.click();
  656. if (isButtonPressed(likeButton)) {
  657. Logger.success('Video liked successfully.');
  658. autoLikedVideoIds.add(videoId);
  659. } else {
  660. Logger.warning('Failed to like the video.');
  661. }
  662. } else {
  663. Logger.info('Video already liked or disliked, or already auto-liked.');
  664. }
  665. }
  666.  
  667. function isButtonPressed(button) {
  668. return button.classList.contains('style-default-active') || button.getAttribute('aria-pressed') === 'true';
  669. }
  670.  
  671. function getVideoId() {
  672. const watchFlexyElem = document.querySelector('#page-manager > ytd-watch-flexy');
  673. if (watchFlexyElem && watchFlexyElem.hasAttribute('video-id')) {
  674. return watchFlexyElem.getAttribute('video-id');
  675. }
  676. const urlParams = new URLSearchParams(window.location.search);
  677. return urlParams.get('v');
  678. }
  679.  
  680. function handleAdBlockError() {
  681. if (!settings.adBlockBypassEnabled) {
  682. Logger.info('AdBlock bypass is disabled.');
  683. return; // Do nothing if the AdBlock bypass is disabled
  684. }
  685.  
  686. const playabilityError = document.querySelector(SELECTORS.PLAYABILITY_ERROR);
  687. if (playabilityError) {
  688. playabilityError.remove();
  689. playerManager.replacePlayer(window.location.href);
  690. } else if (tries < CONSTANTS.MAX_TRIES) {
  691. tries++;
  692. setTimeout(handleAdBlockError, CONSTANTS.DELAY);
  693. }
  694. }
  695.  
  696.  
  697. function handleKeyPress(event) {
  698. switch (event.key) {
  699. case 'F2':
  700. toggleSettingsDialog();
  701. break;
  702. case 'PageDown':
  703. toggleScrolling();
  704. break;
  705. case 'PageUp':
  706. handlePageUp();
  707. break;
  708. }
  709. }
  710.  
  711. function toggleSettingsDialog() {
  712. const dialog = document.getElementById('youtube-enchantments-settings');
  713. if (dialog && dialog.style.display === 'block') {
  714. hideSettingsDialog();
  715. } else {
  716. showSettingsDialog();
  717. }
  718. }
  719.  
  720. function toggleScrolling() {
  721. if (isScrolling) {
  722. clearInterval(scrollInterval);
  723. isScrolling = false;
  724. } else {
  725. isScrolling = true;
  726. scrollInterval = setInterval(() => window.scrollBy(0, settings.scrollSpeed), 20);
  727. }
  728. }
  729.  
  730. function handlePageUp() {
  731. if (isScrolling) {
  732. clearInterval(scrollInterval);
  733. isScrolling = false;
  734. } else {
  735. window.scrollTo(0, 0);
  736. }
  737. }
  738.  
  739. function setupEventListeners() {
  740. window.addEventListener('beforeunload', () => {
  741. currentPageUrl = window.location.href;
  742. });
  743.  
  744. document.addEventListener('yt-navigate-finish', () => {
  745. Logger.info('yt-navigate-finish event triggered');
  746. const newUrl = window.location.href;
  747. if (newUrl !== currentPageUrl) {
  748. Logger.info(`URL changed: ${newUrl}`);
  749. if (newUrl.endsWith('.com/')) {
  750. const iframe = document.getElementById(CONSTANTS.IFRAME_ID);
  751. if (iframe) {
  752. Logger.info('Removing iframe');
  753. iframe.remove();
  754. }
  755. } else {
  756. Logger.info('Handling ad block error');
  757. handleAdBlockError();
  758. }
  759. currentPageUrl = newUrl;
  760. }
  761. });
  762.  
  763. document.addEventListener('keydown', (event) => {
  764. Logger.info(`Key pressed: ${event.key}`);
  765. handleKeyPress(event);
  766. });
  767.  
  768. const observer = new MutationObserver((mutations) => {
  769. for (const mutation of mutations) {
  770. if (mutation.type === 'childList') {
  771. for (const node of mutation.addedNodes) {
  772. if (node.nodeType === Node.ELEMENT_NODE &&
  773. node.matches(SELECTORS.PLAYABILITY_ERROR)) {
  774. Logger.info('Playability error detected');
  775. handleAdBlockError();
  776. return;
  777. }
  778. }
  779. }
  780. }
  781. });
  782. observer.observe(document.body, { childList: true, subtree: true });
  783.  
  784. setInterval(() => {
  785. Logger.info('Checking for duplicate players');
  786. playerManager.removeDuplicates();
  787. }, CONSTANTS.DUPLICATE_CHECK_INTERVAL);
  788.  
  789. // Add interval to check and hide game sections
  790. setInterval(hideGameSections, CONSTANTS.GAME_CHECK_INTERVAL);
  791. }
  792.  
  793. function hideGameSections() {
  794. if (!settings.removeGamesEnabled) return;
  795.  
  796. const allSections = document.querySelectorAll(SELECTORS.GAME_SECTION);
  797. if (allSections.length > 0) {
  798. allSections.forEach(section => {
  799. // Check if this is a game section using DOM traversal
  800. if (isGameSection(section)) {
  801. section.style.display = 'none';
  802. Logger.success('Game section hidden');
  803. }
  804. });
  805. }
  806. }
  807.  
  808. function isGameSection(section) {
  809. // Check for dismissible rich shelf renderer elements
  810. const dismissibleElements = section.querySelectorAll('div#dismissible.style-scope.ytd-rich-shelf-renderer');
  811. if (dismissibleElements.length > 0) {
  812. return true;
  813. }
  814.  
  815. // Check if this is a games/playables section using various indicators
  816.  
  817. // Check for "Jugables de YouTube" in the title
  818. const titleElement = section.querySelector('#title-text span');
  819. if (titleElement && titleElement.textContent.includes('Jugables')) {
  820. return true;
  821. }
  822.  
  823. // Check for playables links
  824. const playablesLinks = section.querySelectorAll('a[href="/playables"], a[href*="/playables"]');
  825. if (playablesLinks.length > 0) {
  826. return true;
  827. }
  828.  
  829. // Check for gaming links
  830. const gamingLinks = section.querySelectorAll('a[href*="gaming"]');
  831. if (gamingLinks.length > 0) {
  832. return true;
  833. }
  834.  
  835. // Check for game-related text in aria-labels
  836. const richShelfElements = section.querySelectorAll('ytd-rich-shelf-renderer');
  837. for (const element of richShelfElements) {
  838. const ariaLabel = element.getAttribute('aria-label');
  839. if (ariaLabel && (ariaLabel.toLowerCase().includes('game') || ariaLabel.toLowerCase().includes('juego'))) {
  840. return true;
  841. }
  842. }
  843.  
  844. // Check for mini-game-card elements
  845. const miniGameCards = section.querySelectorAll('ytd-mini-game-card-view-model');
  846. if (miniGameCards.length > 0) {
  847. return true;
  848. }
  849.  
  850. // Check for content with specific game-related patterns
  851. const gameGenres = ['Arcade', 'Carreras', 'Deportes', 'Acción', 'Puzles', 'Música'];
  852. const genreSpans = section.querySelectorAll('.yt-mini-game-card-view-model__genre');
  853. for (const span of genreSpans) {
  854. if (gameGenres.some(genre => span.textContent.includes(genre))) {
  855. return true;
  856. }
  857. }
  858.  
  859. return false;
  860. }
  861.  
  862. // Updated main initialization function
  863. async function initScript() {
  864. try {
  865. Logger.info('Initializing script for Edge compatibility');
  866. createSettingsMenu();
  867. setupEventListeners();
  868.  
  869. const worker = createWorker();
  870. if (worker) {
  871. startBackgroundCheck(worker);
  872. } else {
  873. Logger.warning('Worker creation failed, falling back to interval');
  874. setInterval(checkAndLikeVideo, settings.checkFrequency);
  875. }
  876.  
  877. // Initial check for game sections
  878. hideGameSections();
  879.  
  880. // Check browser compatibility
  881. const userAgent = navigator.userAgent;
  882. if (userAgent.includes("Edg/")) {
  883. Logger.info('Edge detected, applying specific optimizations');
  884. CONSTANTS.DELAY = 300; // Specific adjustment for Edge
  885. CONSTANTS.MAX_TRIES = 150;
  886. }
  887.  
  888. } catch (error) {
  889. Logger.error(`Script initialization failed: ${error}`);
  890. }
  891. }
  892.  
  893. initScript();
  894. })();

QingJ © 2025

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