您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically likes videos of channels you're subscribed to, scrolls down on Youtube with a toggle button, and bypasses the AdBlock ban.
// ==UserScript== // @name YouTube Enchantments // @namespace http://tampermonkey.net/ // @version 0.8.5 // @description Automatically likes videos of channels you're subscribed to, scrolls down on Youtube with a toggle button, and bypasses the AdBlock ban. // @author JJJ // @match https://www.youtube.com/* // @exclude https://www.youtube.com/*/community // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @run-at document-idle // @noframes // @license MIT // ==/UserScript== (() => { 'use strict'; // Add logger configuration const Logger = { enabled: true, styles: { info: 'color: #2196F3; font-weight: bold', warning: 'color: #FFC107; font-weight: bold', success: 'color: #4CAF50; font-weight: bold', error: 'color: #F44336; font-weight: bold' }, prefix: '[YouTubeEnchantments]', getTimestamp() { return new Date().toISOString().split('T')[1].slice(0, -1); }, info(msg) { if (!this.enabled) return; console.log(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.info); }, warning(msg) { if (!this.enabled) return; console.warn(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.warning); }, success(msg) { if (!this.enabled) return; console.log(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.success); }, error(msg) { if (!this.enabled) return; console.error(`%c${this.prefix} ${this.getTimestamp()} - ${msg}`, this.styles.error); } }; // Basic browser feature warning (no early return to keep functionality) if (!window.URL) { Logger.warning('URL API not available; some features may not work as expected.'); } // Inject the YouTube IFrame API script with error handling function injectYouTubeAPI() { return new Promise((resolve, reject) => { try { // If API already present, resolve immediately if (window.YT && window.YT.Player) return resolve(); const existing = Array.from(document.scripts).find(s => s.src && s.src.includes('https://www.youtube.com/iframe_api')); if (existing) { existing.addEventListener('load', () => resolve()); existing.addEventListener('error', reject); return; } const script = document.createElement('script'); script.src = 'https://www.youtube.com/iframe_api'; script.onload = () => resolve(); script.onerror = (e) => reject(e); document.head.appendChild(script); } catch (e) { reject(e); } }); } // Constants - Optimized selectors const SELECTORS = { PLAYER: '#movie_player', SUBSCRIBE_BUTTON: '#subscribe-button > ytd-subscribe-button-renderer, ytd-reel-player-overlay-renderer #subscribe-button, tp-yt-paper-button[subscribed]', LIKE_BUTTON: [ 'like-button-view-model button', 'ytd-menu-renderer button[aria-label*="like" i]', 'button[aria-label*="like" i]', 'ytd-toggle-button-renderer[aria-pressed] button', 'ytd-reel-player-overlay-renderer ytd-like-button-view-model button', 'ytd-reel-video-renderer ytd-like-button-view-model button' ].join(','), DISLIKE_BUTTON: [ 'dislike-button-view-model button', 'ytd-menu-renderer button[aria-label*="dislike" i]', 'button[aria-label*="dislike" i]', 'ytd-toggle-button-renderer[aria-pressed] button[aria-label*="dislike" i]' ].join(','), PLAYER_CONTAINER: '#player-container-outer', ERROR_SCREEN: '#error-screen', PLAYABILITY_ERROR: '.yt-playability-error-supported-renderers', LIVE_BADGE: '.ytp-live-badge', GAME_SECTION: 'ytd-rich-section-renderer, div#dismissible.style-scope.ytd-rich-shelf-renderer' }; const CONSTANTS = { IFRAME_ID: 'adblock-bypass-player', STORAGE_KEY: 'youtubeEnchantmentsSettings', DELAY: 300, MAX_TRIES: 150, DUPLICATE_CHECK_INTERVAL: 7000, GAME_CHECK_INTERVAL: 2000, MIN_CHECK_FREQUENCY: 1000, MAX_CHECK_FREQUENCY: 30000 }; // Optimized settings with better defaults const defaultSettings = { autoLikeEnabled: true, autoLikeLiveStreams: false, likeIfNotSubscribed: false, watchThreshold: 0, checkFrequency: 3000, adBlockBypassEnabled: false, scrollSpeed: 50, removeGamesEnabled: true, loggingEnabled: true }; let settings = loadSettings(); const autoLikedVideoIds = new Set(); let isScrolling = false; let scrollInterval; let currentPageUrl = window.location.href; let tries = 0; // Scheduler/observer references let checkTimer = null; let duplicateCleanupInterval = null; let gameHideInterval = null; let adBlockObserver = null; let likeReadyObserver = null; // Keep Logger toggle in sync with settings Logger.enabled = !!settings.loggingEnabled; const urlUtils = { extractParams(url) { try { const params = new URL(url).searchParams; return { videoId: params.get('v'), playlistId: params.get('list'), index: params.get('index') }; } catch (e) { console.error('Failed to extract URL params:', e); return {}; } }, getTimestampFromUrl(url) { try { const timestamp = new URL(url).searchParams.get('t'); if (timestamp) { const timeArray = timestamp.split(/h|m|s/).map(Number); const timeInSeconds = timeArray.reduce((acc, time, index) => acc + time * Math.pow(60, 2 - index), 0); return `&start=${timeInSeconds}`; } } catch (e) { console.error('Failed to extract timestamp:', e); } return ''; } }; let player; // Updated PlayerManager const playerManager = { async initPlayer() { try { await injectYouTubeAPI(); const iframe = document.getElementById(CONSTANTS.IFRAME_ID); if (iframe) { player = new YT.Player(CONSTANTS.IFRAME_ID, { events: { 'onReady': this.onPlayerReady.bind(this), 'onStateChange': this.onPlayerStateChange.bind(this), 'onError': (event) => { Logger.error(`Player error: ${event.data}`); } } }); } } catch (error) { Logger.error(`Failed to initialize player: ${error}`); } }, onPlayerReady(event) { Logger.info('Player is ready'); }, onPlayerStateChange(event) { if (event.data === YT.PlayerState.AD_STARTED) { Logger.info('Ad is playing, allowing ad to complete.'); } else if (event.data === YT.PlayerState.ENDED || event.data === YT.PlayerState.PLAYING) { Logger.info('Video is playing, ensuring it is tracked in history.'); } }, createIframe(url) { try { const { videoId, playlistId, index } = urlUtils.extractParams(url); if (!videoId) return null; const iframe = document.createElement('iframe'); const commonArgs = 'autoplay=1&modestbranding=1&enablejsapi=1&origin=' + encodeURIComponent(window.location.origin); const embedUrl = playlistId ? `https://www.youtube.com/embed/${videoId}?${commonArgs}&list=${playlistId}&index=${index}` : `https://www.youtube.com/embed/${videoId}?${commonArgs}${urlUtils.getTimestampFromUrl(url)}`; this.setIframeAttributes(iframe, embedUrl); return iframe; } catch (error) { Logger.error(`Failed to create iframe: ${error}`); return null; } }, setIframeAttributes(iframe, url) { iframe.id = CONSTANTS.IFRAME_ID; iframe.src = url; iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share'; iframe.allowFullscreen = true; iframe.style.cssText = 'height:100%; width:calc(100% - 240px); border:none; border-radius:12px; position:relative; left:240px;'; }, replacePlayer(url) { const playerContainer = document.querySelector(SELECTORS.ERROR_SCREEN); if (!playerContainer) return; let iframe = document.getElementById(CONSTANTS.IFRAME_ID); if (iframe) { this.setIframeAttributes(iframe, url); } else { iframe = this.createIframe(url); if (iframe) { playerContainer.appendChild(iframe); this.initPlayer(); } } // Ensure the iframe is on top of the player container this.bringToFront(CONSTANTS.IFRAME_ID); this.addScrollListener(); }, bringToFront(elementId) { const element = document.getElementById(elementId); if (element) { const maxZIndex = Math.max( ...Array.from(document.querySelectorAll('*')) .map(e => parseInt(window.getComputedStyle(e).zIndex) || 0) ); element.style.zIndex = maxZIndex + 1; } }, removeDuplicates() { const iframes = document.querySelectorAll(`#${CONSTANTS.IFRAME_ID}`); if (iframes.length > 1) { Array.from(iframes).slice(1).forEach(iframe => iframe.remove()); } }, addScrollListener() { window.addEventListener('scroll', this.handleScroll); }, handleScroll() { const iframe = document.getElementById(CONSTANTS.IFRAME_ID); if (!iframe) return; const playerContainer = document.querySelector(SELECTORS.ERROR_SCREEN); if (!playerContainer) return; const rect = playerContainer.getBoundingClientRect(); if (rect.top < 0) { iframe.style.position = 'fixed'; iframe.style.top = '0'; iframe.style.left = '240px'; iframe.style.width = 'calc(100% - 240px)'; iframe.style.height = 'calc(100vh - 56px)'; // Adjust height as needed } else { iframe.style.position = 'relative'; iframe.style.top = '0'; iframe.style.left = '240px'; iframe.style.width = 'calc(100% - 240px)'; iframe.style.height = '100%'; } } }; // Throttle helper to avoid rapid actions function throttle(fn, wait) { let last = 0; let timeout = null; return function (...args) { const now = Date.now(); const remaining = wait - (now - last); const context = this; if (remaining <= 0) { if (timeout) { clearTimeout(timeout); timeout = null; } last = now; fn.apply(context, args); } else if (!timeout) { timeout = setTimeout(() => { last = Date.now(); timeout = null; fn.apply(context, args); }, remaining); } }; } // Optimized settings management function loadSettings() { const saved = GM_getValue(CONSTANTS.STORAGE_KEY, {}); return { ...defaultSettings, ...saved }; } const saveSettings = () => GM_setValue(CONSTANTS.STORAGE_KEY, settings); function createSettingsMenu() { GM_registerMenuCommand('YouTube Enchantments Settings', showSettingsDialog); } function showSettingsDialog() { let dialog = document.getElementById('youtube-enchantments-settings'); if (!dialog) { dialog = createSettingsDialog(); document.body.appendChild(dialog); } dialog.style.display = 'block'; } function createSettingsDialog() { const wrapper = document.createElement('div'); wrapper.id = 'youtube-enchantments-settings'; // Styles (Trusted Types safe) const styleEl = document.createElement('style'); styleEl.textContent = ` .dpe-dialog { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #030d22; border: 1px solid #2a2945; border-radius: 12px; padding: 24px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); z-index: 9999; color: #ffffff; width: 320px; font-family: 'Roboto', Arial, sans-serif; } .dpe-dialog h3 { margin-top: 0; font-size: 1.8em; text-align: center; margin-bottom: 24px; color: #ffffff; font-weight: 700; } .dpe-toggle-container { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; padding: 8px; border-radius: 8px; background: #15132a; transition: background-color 0.2s; } .dpe-toggle-container:hover { background: #1a1832; } .dpe-toggle-label { flex-grow: 1; color: #ffffff; font-size: 1.1em; font-weight: 600; margin-left: 12px; } .dpe-toggle { position: relative; display: inline-block; width: 46px; height: 24px; } .dpe-toggle input { position: absolute; width: 100%; height: 100%; opacity: 0; cursor: pointer; margin: 0; } .dpe-toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #2a2945; transition: .3s; border-radius: 24px; } .dpe-toggle-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background-color: #ffffff; transition: .3s; border-radius: 50%; } .dpe-toggle input:checked + .dpe-toggle-slider { background-color: #cc0000; } .dpe-toggle input:checked + .dpe-toggle-slider:before { transform: translateX(22px); } .dpe-slider-container { margin: 24px 0; padding: 12px; background: #15132a; border-radius: 8px; } .dpe-slider-container label { display: block; margin-bottom: 8px; color: #ffffff; font-size: 1.1em; font-weight: 600; } .dpe-slider-container input[type="range"] { width: 100%; margin: 8px 0; height: 4px; background: #2a2945; border-radius: 2px; -webkit-appearance: none; } .dpe-slider-container input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; background: #cc0000; border-radius: 50%; cursor: pointer; transition: background-color 0.2s; } .dpe-slider-container input[type="range"]::-webkit-slider-thumb:hover { background: #990000; } .dpe-button-container { display: flex; justify-content: space-between; margin-top: 24px; gap: 12px; } .dpe-button { padding: 10px 20px; border: none; border-radius: 6px; cursor: pointer; font-size: 1.1em; font-weight: 600; transition: all 0.2s; flex: 1; } .dpe-button-save { background-color: #cc0000; color: white; } .dpe-button-save:hover { background-color: #990000; transform: translateY(-1px); } .dpe-button-cancel { background-color: #15132a; color: white; border: 1px solid #2a2945; } .dpe-button-cancel:hover { background-color: #1a1832; transform: translateY(-1px); } `; const container = document.createElement('div'); container.className = 'dpe-dialog'; const title = document.createElement('h3'); title.textContent = 'YouTube Enchantments'; container.appendChild(title); // Toggles container.appendChild(createToggle('autoLikeEnabled', 'Auto Like', 'Automatically like videos of subscribed channels')); container.appendChild(createToggle('autoLikeLiveStreams', 'Like Live Streams', 'Include live streams in auto-like feature')); container.appendChild(createToggle('likeIfNotSubscribed', 'Like All Videos', 'Like videos even if not subscribed')); container.appendChild(createToggle('adBlockBypassEnabled', 'AdBlock Bypass', 'Bypass AdBlock detection')); container.appendChild(createToggle('removeGamesEnabled', 'Remove Games', 'Hide game sections from YouTube homepage')); container.appendChild(createToggle('loggingEnabled', 'Logging', 'Enable or disable console logging')); // Watch Threshold slider const wt = document.createElement('div'); wt.className = 'dpe-slider-container'; wt.title = 'Percentage of video to watch before liking'; const wtLabel = document.createElement('label'); wtLabel.setAttribute('for', 'watchThreshold'); wtLabel.textContent = 'Watch Threshold'; const wtInput = document.createElement('input'); wtInput.type = 'range'; wtInput.id = 'watchThreshold'; wtInput.min = '0'; wtInput.max = '100'; wtInput.step = '10'; wtInput.setAttribute('data-setting', 'watchThreshold'); wtInput.value = String(settings.watchThreshold); const wtValue = document.createElement('span'); wtValue.id = 'watchThresholdValue'; wtValue.textContent = `${settings.watchThreshold}%`; wt.appendChild(wtLabel); wt.appendChild(wtInput); wt.appendChild(wtValue); container.appendChild(wt); // Scroll Speed slider const ss = document.createElement('div'); ss.className = 'dpe-slider-container'; ss.title = 'Speed of auto-scroll (pixels per interval)'; const ssLabel = document.createElement('label'); ssLabel.setAttribute('for', 'scrollSpeed'); ssLabel.textContent = 'Scroll Speed'; const ssInput = document.createElement('input'); ssInput.type = 'range'; ssInput.id = 'scrollSpeed'; ssInput.min = '10'; ssInput.max = '100'; ssInput.step = '5'; ssInput.setAttribute('data-setting', 'scrollSpeed'); ssInput.value = String(settings.scrollSpeed); const ssValue = document.createElement('span'); ssValue.id = 'scrollSpeedValue'; ssValue.textContent = `${settings.scrollSpeed}px`; ss.appendChild(ssLabel); ss.appendChild(ssInput); ss.appendChild(ssValue); container.appendChild(ss); // Check Frequency slider const cf = document.createElement('div'); cf.className = 'dpe-slider-container'; cf.title = 'Interval for auto-like checks (milliseconds)'; const cfLabel = document.createElement('label'); cfLabel.setAttribute('for', 'checkFrequency'); cfLabel.textContent = 'Check Frequency (ms)'; const cfInput = document.createElement('input'); cfInput.type = 'range'; cfInput.id = 'checkFrequency'; cfInput.min = String(CONSTANTS.MIN_CHECK_FREQUENCY); cfInput.max = String(CONSTANTS.MAX_CHECK_FREQUENCY); cfInput.step = '500'; cfInput.setAttribute('data-setting', 'checkFrequency'); cfInput.value = String(settings.checkFrequency); const cfValue = document.createElement('span'); cfValue.id = 'checkFrequencyValue'; cfValue.textContent = `${settings.checkFrequency}ms`; cf.appendChild(cfLabel); cf.appendChild(cfInput); cf.appendChild(cfValue); container.appendChild(cf); // Buttons const btns = document.createElement('div'); btns.className = 'dpe-button-container'; const saveBtn = document.createElement('button'); saveBtn.id = 'saveSettingsButton'; saveBtn.className = 'dpe-button dpe-button-save'; saveBtn.textContent = 'Save'; const cancelBtn = document.createElement('button'); cancelBtn.id = 'closeSettingsButton'; cancelBtn.className = 'dpe-button dpe-button-cancel'; cancelBtn.textContent = 'Cancel'; btns.appendChild(saveBtn); btns.appendChild(cancelBtn); container.appendChild(btns); // Compose wrapper.appendChild(styleEl); wrapper.appendChild(container); // Listeners saveBtn.addEventListener('click', () => { saveSettings(); hideSettingsDialog(); }); cancelBtn.addEventListener('click', hideSettingsDialog); wrapper.querySelectorAll('.dpe-toggle input').forEach(toggle => { toggle.addEventListener('change', handleSettingChange); }); wtInput.addEventListener('input', handleSliderInput); ssInput.addEventListener('input', handleSliderInput); cfInput.addEventListener('input', handleSliderInput); return wrapper; } function createToggle(id, label, title) { const container = document.createElement('div'); container.className = 'dpe-toggle-container'; container.title = title; const toggleLabel = document.createElement('label'); toggleLabel.className = 'dpe-toggle'; const input = document.createElement('input'); input.type = 'checkbox'; input.setAttribute('data-setting', id); if (settings[id]) input.checked = true; const slider = document.createElement('span'); slider.className = 'dpe-toggle-slider'; toggleLabel.appendChild(input); toggleLabel.appendChild(slider); const textLabel = document.createElement('label'); textLabel.className = 'dpe-toggle-label'; textLabel.textContent = label; container.appendChild(toggleLabel); container.appendChild(textLabel); return container; } function hideSettingsDialog() { const dialog = document.getElementById('youtube-enchantments-settings'); if (dialog) { dialog.style.display = 'none'; } } function formatSettingName(setting) { return setting.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase()); } function handleSettingChange(e) { if (e.target.dataset.setting) { if (e.target.type === 'checkbox') { toggleSetting(e.target.dataset.setting); } else if (e.target.type === 'range') { updateNumericSetting(e.target.dataset.setting, e.target.value); } // Log the status of adBlockBypassEnabled if it is changed if (e.target.dataset.setting === 'adBlockBypassEnabled') { Logger.info(`AdBlock Ban Bypass is ${e.target.checked ? 'enabled' : 'disabled'}`); } if (e.target.dataset.setting === 'loggingEnabled') { Logger.enabled = !!settings.loggingEnabled; Logger.info(`Logging ${Logger.enabled ? 'enabled' : 'disabled'}`); } if (e.target.dataset.setting === 'checkFrequency') { // Apply new frequency immediately restartBackgroundCheck(); } } } function handleSliderInput(e) { if (e.target.type === 'range') { const value = e.target.value; if (e.target.id === 'watchThreshold') { document.getElementById('watchThresholdValue').textContent = `${value}%`; updateNumericSetting('watchThreshold', value); } else if (e.target.id === 'scrollSpeed') { document.getElementById('scrollSpeedValue').textContent = `${value}px`; updateNumericSetting('scrollSpeed', value); } else if (e.target.id === 'checkFrequency') { document.getElementById('checkFrequencyValue').textContent = `${value}ms`; updateNumericSetting('checkFrequency', value); restartBackgroundCheck(); } } } function toggleSetting(settingName) { settings[settingName] = !settings[settingName]; saveSettings(); if (settingName === 'loggingEnabled') { Logger.enabled = !!settings.loggingEnabled; } } function updateNumericSetting(settingName, value) { let v = parseInt(value, 10); if (settingName === 'checkFrequency') { if (!Number.isFinite(v)) v = defaultSettings.checkFrequency; v = Math.min(CONSTANTS.MAX_CHECK_FREQUENCY, Math.max(CONSTANTS.MIN_CHECK_FREQUENCY, v)); } settings[settingName] = v; saveSettings(); } function startBackgroundCheck() { restartBackgroundCheck(); } function restartBackgroundCheck() { if (checkTimer) clearInterval(checkTimer); const freq = Math.min(CONSTANTS.MAX_CHECK_FREQUENCY, Math.max(CONSTANTS.MIN_CHECK_FREQUENCY, settings.checkFrequency || defaultSettings.checkFrequency)); const throttled = throttle(checkAndLikeVideo, Math.max(750, Math.floor(freq / 2))); checkTimer = setInterval(throttled, freq); Logger.info(`Background check started (every ${freq}ms)`); } let isChecking = false; function checkAndLikeVideo() { if (isChecking) return; // prevent reentrancy isChecking = true; Logger.info('Checking if video should be liked...'); if (watchThresholdReached()) { Logger.info('Watch threshold reached.'); if (settings.autoLikeEnabled) { Logger.info('Auto-like is enabled.'); if (settings.likeIfNotSubscribed || isSubscribed()) { Logger.info('User is subscribed or likeIfNotSubscribed is enabled.'); if (settings.autoLikeLiveStreams || !isLiveStream()) { Logger.info('Video is not a live stream or auto-like for live streams is enabled.'); likeVideo(); } else { Logger.info('Video is a live stream and auto-like for live streams is disabled.'); } } else { Logger.info('User is not subscribed and likeIfNotSubscribed is disabled.'); } } else { Logger.info('Auto-like is disabled.'); } } else { Logger.info('Watch threshold not reached.'); } isChecking = false; } function watchThresholdReached() { const player = document.querySelector(SELECTORS.PLAYER); if (player && typeof player.getCurrentTime === 'function' && typeof player.getDuration === 'function') { const currentTime = player.getCurrentTime(); const duration = player.getDuration(); if (duration > 0) { const watched = currentTime / duration; const watchedTarget = settings.watchThreshold / 100; if (watched < watchedTarget) { Logger.info(`Waiting until watch threshold reached (${(watched * 100).toFixed(1)}%/${settings.watchThreshold}%)...`); return false; } } } return true; } // Optimized subscription detection function isSubscribed() { const subscribeButton = document.querySelector(SELECTORS.SUBSCRIBE_BUTTON); if (!subscribeButton) { Logger.info('Subscribe button not found'); return false; } const isSubbed = subscribeButton.hasAttribute('subscribe-button-invisible') || subscribeButton.hasAttribute('subscribed') || /subscrib/i.test(subscribeButton.textContent); Logger.info(`Subscribe button found: true, Is subscribed: ${isSubbed}`); return isSubbed; } function isLiveStream() { try { const liveBadge = document.querySelector(SELECTORS.LIVE_BADGE); if (liveBadge && window.getComputedStyle(liveBadge).display !== 'none') return true; // Shorts live is rare; fallback false return false; } catch (_) { return false; } } function likeVideo() { Logger.info('Attempting to like the video...'); const likeButton = document.querySelector(SELECTORS.LIKE_BUTTON); const dislikeButton = document.querySelector(SELECTORS.DISLIKE_BUTTON); const videoId = getVideoId(); Logger.info(`Like button found: ${!!likeButton}`); Logger.info(`Dislike button found: ${!!dislikeButton}`); Logger.info(`Video ID: ${videoId}`); if (!likeButton || !dislikeButton || !videoId) { Logger.info('Like button, dislike button, or video ID not found.'); return; } const likePressed = isButtonPressed(likeButton); const dislikePressed = isButtonPressed(dislikeButton); const alreadyAutoLiked = autoLikedVideoIds.has(videoId); Logger.info(`Like button pressed: ${likePressed}`); Logger.info(`Dislike button pressed: ${dislikePressed}`); Logger.info(`Already auto-liked: ${alreadyAutoLiked}`); if (!likePressed && !dislikePressed && !alreadyAutoLiked) { Logger.info('Liking the video...'); likeButton.click(); // Check again after a short delay setTimeout(() => { if (isButtonPressed(likeButton)) { Logger.success('Video liked successfully.'); autoLikedVideoIds.add(videoId); } else { Logger.warning('Failed to like the video.'); } }, 500); } else { Logger.info('Video already liked or disliked, or already auto-liked.'); } } function isButtonPressed(button) { if (!button) return false; const pressed = button.classList.contains('style-default-active') || button.getAttribute('aria-pressed') === 'true'; const toggled = button.closest('ytd-toggle-button-renderer')?.getAttribute('aria-pressed') === 'true'; return pressed || toggled; } function getVideoId() { // Watch page const watchFlexyElem = document.querySelector('#page-manager > ytd-watch-flexy'); if (watchFlexyElem && watchFlexyElem.hasAttribute('video-id')) { return watchFlexyElem.getAttribute('video-id'); } // Shorts URL pattern const path = window.location.pathname; const shortsMatch = path.match(/^\/shorts\/([\w-]{5,})/); if (shortsMatch) return shortsMatch[1]; // Fallback to query param const urlParams = new URLSearchParams(window.location.search); return urlParams.get('v'); } function handleAdBlockError() { if (!settings.adBlockBypassEnabled) { Logger.info('AdBlock bypass disabled'); return; } const playabilityError = document.querySelector(SELECTORS.PLAYABILITY_ERROR); if (playabilityError) { playabilityError.remove(); playerManager.replacePlayer(window.location.href); } else if (tries < CONSTANTS.MAX_TRIES) { tries++; setTimeout(handleAdBlockError, CONSTANTS.DELAY); } } function redirectToVideosPage() { const currentUrl = window.location.href; // Handle new format @username channels if (currentUrl.includes('/@')) { if (currentUrl.endsWith('/featured') || currentUrl.includes('/featured?')) { const videosUrl = currentUrl.replace(/\/featured(\?.*)?$/, '/videos'); Logger.info(`Redirecting to videos page: ${videosUrl}`); window.location.replace(videosUrl); return true; } else if (currentUrl.match(/\/@[^\/]+\/?(\?.*)?$/)) { const videosUrl = currentUrl.replace(/\/?(\?.*)?$/, '/videos'); Logger.info(`Redirecting to videos page: ${videosUrl}`); window.location.replace(videosUrl); return true; } } // Handle legacy channel URLs if (currentUrl.includes('/channel/')) { if (currentUrl.endsWith('/featured') || currentUrl.includes('/featured?')) { const videosUrl = currentUrl.replace(/\/featured(\?.*)?$/, '/videos'); Logger.info(`Redirecting to videos page: ${videosUrl}`); window.location.replace(videosUrl); return true; } else if (currentUrl.match(/\/channel\/[^\/]+\/?(\?.*)?$/)) { const videosUrl = currentUrl.replace(/\/?(\?.*)?$/, '/videos'); Logger.info(`Redirecting to videos page: ${videosUrl}`); window.location.replace(videosUrl); return true; } } return false; } // Remove redundant functions const toggleSettingsDialog = () => { const dialog = document.getElementById('youtube-enchantments-settings'); if (dialog && dialog.style.display === 'block') { hideSettingsDialog(); } else { showSettingsDialog(); } }; const toggleScrolling = () => { if (isScrolling) { clearInterval(scrollInterval); isScrolling = false; } else { isScrolling = true; scrollInterval = setInterval(() => window.scrollBy(0, settings.scrollSpeed), 20); } }; const handlePageUp = () => { if (isScrolling) { clearInterval(scrollInterval); isScrolling = false; } else { window.scrollTo(0, 0); } }; // Observe like button readiness to reduce polling pressure function observeLikeButtonReady() { if (likeReadyObserver) likeReadyObserver.disconnect(); likeReadyObserver = new MutationObserver(() => { const btn = document.querySelector(SELECTORS.LIKE_BUTTON); if (btn) { Logger.info('Like button detected by observer'); // Trigger a check once when like button shows up checkAndLikeVideo(); if (likeReadyObserver) likeReadyObserver.disconnect(); } }); likeReadyObserver.observe(document.body, { childList: true, subtree: true }); } // Optimized event handling function setupEventListeners() { // Page navigation window.addEventListener('beforeunload', () => { currentPageUrl = window.location.href; cleanup(); }); document.addEventListener('yt-navigate-finish', () => { Logger.info('Page navigation detected'); const newUrl = window.location.href; if (newUrl !== currentPageUrl) { Logger.info(`URL changed: ${newUrl}`); if (redirectToVideosPage()) return; if (newUrl.endsWith('.com/')) { const iframe = document.getElementById(CONSTANTS.IFRAME_ID); if (iframe) { Logger.info('Removing iframe'); iframe.remove(); } } else { Logger.info('Handling potential ad block error'); handleAdBlockError(); } currentPageUrl = newUrl; // Stop auto-scroll on navigation if (isScrolling && scrollInterval) { clearInterval(scrollInterval); isScrolling = false; } // Restart background checks and observers on navigation restartBackgroundCheck(); observeLikeButtonReady(); } }); window.addEventListener('popstate', () => { Logger.info('popstate detected'); const newUrl = window.location.href; if (newUrl !== currentPageUrl) { currentPageUrl = newUrl; restartBackgroundCheck(); observeLikeButtonReady(); } }); // Keyboard shortcuts (capture early to avoid site handlers swallowing keys) document.addEventListener('keydown', (event) => { const tag = (event.target && event.target.tagName) ? event.target.tagName.toLowerCase() : ''; const isEditable = tag === 'input' || tag === 'textarea' || (event.target && event.target.isContentEditable); switch (event.key) { case 'F2': // Avoid triggering while typing inside inputs/textareas if (!isEditable) { event.preventDefault(); event.stopPropagation(); Logger.info('F2 pressed - toggling settings dialog'); toggleSettingsDialog(); } break; case 'PageDown': if (!isEditable) { event.preventDefault(); toggleScrolling(); } break; case 'PageUp': if (!isEditable) { event.preventDefault(); handlePageUp(); } break; } }, true); // DOM observer for ad block errors adBlockObserver = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === 'childList') { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE && node.matches(SELECTORS.PLAYABILITY_ERROR)) { Logger.info('Playability error detected'); handleAdBlockError(); return; } } } } }); adBlockObserver.observe(document.body, { childList: true, subtree: true }); // Periodic tasks duplicateCleanupInterval = setInterval(() => playerManager.removeDuplicates(), CONSTANTS.DUPLICATE_CHECK_INTERVAL); gameHideInterval = setInterval(hideGameSections, CONSTANTS.GAME_CHECK_INTERVAL); // Initial like button observation observeLikeButtonReady(); } function hideGameSections() { if (!settings.removeGamesEnabled) return; const allSections = document.querySelectorAll(SELECTORS.GAME_SECTION); if (allSections.length > 0) { allSections.forEach(section => { // Check if this is a game section using DOM traversal if (isGameSection(section)) { section.style.display = 'none'; Logger.success('Game section hidden'); } }); } } // Optimized game section detection function isGameSection(section) { // Quick checks first if (section.querySelectorAll('div#dismissible.style-scope.ytd-rich-shelf-renderer').length > 0) return true; if (section.querySelectorAll('ytd-mini-game-card-view-model').length > 0) return true; if (section.querySelectorAll('a[href*="/playables"], a[href*="gaming"]').length > 0) return true; // Text-based checks const titleElement = section.querySelector('#title-text span'); if (titleElement && /game|jugable/i.test(titleElement.textContent)) return true; // Aria-label checks const richShelfElements = section.querySelectorAll('ytd-rich-shelf-renderer'); for (const element of richShelfElements) { const ariaLabel = element.getAttribute('aria-label'); if (ariaLabel && /game|juego/i.test(ariaLabel)) return true; } // Genre checks const gameGenres = ['Arcade', 'Racing', 'Sports', 'Action', 'Puzzles', 'Music', 'Carreras', 'Deportes', 'Acción', 'Puzles', 'Música']; const genreSpans = section.querySelectorAll('.yt-mini-game-card-view-model__genre'); return Array.from(genreSpans).some(span => gameGenres.some(genre => span.textContent.includes(genre)) ); } // Optimized initialization async function initScript() { try { Logger.info('Initializing YouTube Enchantments'); // Setup core functionality createSettingsMenu(); setupEventListeners(); redirectToVideosPage(); // Initialize auto-like system (interval-based) startBackgroundCheck(); // Initialize game section removal hideGameSections(); Logger.info('Script initialization complete'); } catch (error) { Logger.error(`Initialization failed: ${error}`); } } function cleanup() { try { if (checkTimer) { clearInterval(checkTimer); checkTimer = null; } if (duplicateCleanupInterval) { clearInterval(duplicateCleanupInterval); duplicateCleanupInterval = null; } if (gameHideInterval) { clearInterval(gameHideInterval); gameHideInterval = null; } if (adBlockObserver) { adBlockObserver.disconnect(); adBlockObserver = null; } if (likeReadyObserver) { likeReadyObserver.disconnect(); likeReadyObserver = null; } if (isScrolling && scrollInterval) { clearInterval(scrollInterval); isScrolling = false; } } catch (e) { /* no-op */ } } initScript(); })();