### Setup Firefox Dev Environment for Remove YouTube Suggestions Source: https://github.com/lawrencehook/remove-youtube-suggestions/blob/main/README.md These commands outline the steps to set up a development environment for the Firefox version of the 'Remove YouTube Suggestions' browser extension. It involves cloning the repository, navigating to the source directory, installing the 'web-ext' tool globally, and running the extension locally for testing. ```bash git clone https://github.com/lawrencehook/remove-youtube-suggestions.git cd remove-youtube-suggestions/src npm install --global web-ext web-ext run ``` -------------------------------- ### Extension Lifecycle and Storage Events (JavaScript) Source: https://context7.com/lawrencehook/remove-youtube-suggestions/llms.txt Manages the extension's lifecycle events, including installation, uninstallation, and updating the browser action icon based on storage changes. It sets an uninstall URL for feedback and opens a welcome page on installation. It also listens for changes in 'global_enable' storage item to toggle icons. ```javascript // Background events (src/background/events.js) if (typeof browser === 'undefined') { browser = typeof chrome !== 'undefined' ? chrome : null; } // Set uninstall URL for feedback const uninstallUrl = "http://lawrencehook.com/rys/👋"; browser.runtime.setUninstallURL(uninstallUrl); // Open welcome page on installation browser.runtime.onInstalled.addListener(object => { const url = "http://lawrencehook.com/rys/welcome"; if (object.reason === browser.runtime.OnInstalledReason.INSTALL) { browser.tabs.create({ url }, tab => {}); } }); // Update icon based on enabled state const inactiveIcons = { path: { 16: "/images/16_dark.png", 32: "/images/32_dark.png", 64: "/images/64_dark.png", 128: "/images/128_dark.png", }}; const activeIcons = { path: { 16: "/images/16.png", 32: "/images/32.png", 64: "/images/64.png", 128: "/images/128.png", }}; browser.storage.onChanged.addListener((changes, area) => { const changedItems = Object.keys(changes); for (const item of changedItems) { if (item === 'global_enable') { let icons; if (changes[item].newValue === false) icons = inactiveIcons; if (changes[item].newValue === true) icons = activeIcons; browser.browserAction?.setIcon(icons); browser.action?.setIcon(icons); } } }); ``` -------------------------------- ### DOM Query and Utility Functions (JavaScript) Source: https://context7.com/lawrencehook/remove-youtube-suggestions/llms.txt Provides a set of utility functions for simplifying DOM querying and manipulation. Includes functions for getting a single element (`qs`), multiple elements (`qsa`), and ensuring unique values in an array (`uniq`). These are essential for interacting with the web page structure. ```javascript // Utility functions (src/shared/utils.js:2) function uniq(array) { return Array.from(new Set(array)) } function qs(query, root=document) { return root.querySelector(query) } function qsa(query, root=document) { return Array.from(root.querySelectorAll(query)) } // Usage examples const video = qs('video'); const allLinks = qsa('a[href^="/watch"]'); const sidebarVideos = qsa('ytd-compact-video-renderer', qs('#secondary')); ``` -------------------------------- ### Dynamically Render Options UI (JavaScript) Source: https://context7.com/lawrencehook/remove-youtube-suggestions/llms.txt Populates the options UI by dynamically generating elements from a settings configuration. It renders sections, options within sections, and creates a sidebar navigation based on unique tags. Dependencies include `TEMPLATE_SECTION`, `TEMPLATE_OPTION`, `TEMPLATE_SIDEBAR_SECTION`, `OPTIONS_LIST`, `SIDEBAR`, `sectionNameToUrl`, `HTML.setAttribute`, `cache`, and `updateSetting`. ```javascript // Populate options UI (src/options/main.js:54) function populateOptions(SECTIONS, headerSettings, SETTING_VALUES) { OPTIONS_LIST.innerHTML = ''; SIDEBAR.innerHTML = ''; let allTags = []; // Render each section and its options SECTIONS.forEach(section => { const { name, tags, options } = section; tags.split(',').forEach(tag => allTags.push(tag.trim())); const sectionNode = TEMPLATE_SECTION.cloneNode(true); sectionNode.id = name; sectionNode.classList.remove('removed'); sectionNode.setAttribute('tags', tags); const label = sectionNode.querySelector('.section_label > a'); label.innerText = name; label.setAttribute('href', sectionNameToUrl(name)); options.forEach(option => { const { id, name, tags, defaultValue, effects, display } = option; if (display === false) return; const optionNode = TEMPLATE_OPTION.cloneNode(true); optionNode.classList.remove('removed'); optionNode.id = id; optionNode.setAttribute('name', name); const svg = optionNode.querySelector('svg'); const value = id in SETTING_VALUES ? SETTING_VALUES[id] : defaultValue; HTML.setAttribute(id, value); cache[id] = value; svg.toggleAttribute('active', value); // Handle option clicks with cascading effects optionNode.addEventListener('click', e => { const value = svg.toggleAttribute('active'); updateSetting(id, value, { manual: true }); if (effects && value in effects) { Object.entries(effects[value]).forEach(([id, value]) => { const svg = document.querySelector(`div#${id} svg`); svg?.toggleAttribute('active', value); updateSetting(id, value); }); } }); sectionNode.append(optionNode); }); OPTIONS_LIST.append(sectionNode); }); // Create sidebar navigation const uniqueTags = Array.from(new Set(allTags)); uniqueTags.forEach(tag => { const sidebarSection = TEMPLATE_SIDEBAR_SECTION.cloneNode(true); sidebarSection.removeAttribute('hidden'); sidebarSection.setAttribute('tag', tag); sidebarSection.innerText = tag; sidebarSection.addEventListener('click', sidebarSectionListener); SIDEBAR.append(sidebarSection); }); } ``` -------------------------------- ### Apply Dynamic Settings Loop in JavaScript Source: https://context7.com/lawrencehook/remove-youtube-suggestions/llms.txt This JavaScript function implements a continuous loop to dynamically apply settings as YouTube's SPA updates. It handles scheduled enabling/disabling of settings and detects URL changes to refresh content. The loop is managed by `requestRunDynamicSettings` which uses `setTimeout` for adaptive timing. ```javascript // Main dynamic settings loop (src/content-script/main.js:84) function runDynamicSettings() { if (isRunning) return; isRunning = true; dynamicIters += 1; const on = cache['global_enable'] === true; // Handle scheduled enabling/disabling const scheduleEnabled = cache['schedule']; if (scheduleEnabled && (!lastScheduleCheck || scheduleCheckTimeElapsed)) { lastScheduleCheck = Date.now(); const scheduleIsActive = checkSchedule(cache['scheduleTimes'], cache['scheduleDays']); const scheduleChange = (scheduleIsActive && !on) || (!scheduleIsActive && on); if (scheduleChange) { updateSetting('global_enable', !on); } } if (!on) { frameRequested = false; isRunning = false; requestRunDynamicSettings(); return; } // Detect URL changes in YouTube SPA if (url !== location.href) { handleNewPage(); } // Apply all dynamic settings... frameRequested = false; isRunning = false; requestRunDynamicSettings(); } // Schedule next run with adaptive timing function requestRunDynamicSettings() { if (frameRequested || isRunning) return; frameRequested = true; setTimeout(() => runDynamicSettings(), Math.min(100, 50 + 10 * dynamicIters)); } ``` -------------------------------- ### JavaScript: Manage Browser Storage for Extension Settings Source: https://context7.com/lawrencehook/remove-youtube-suggestions/llms.txt Handles loading, updating, and synchronizing browser extension settings using the `browser.storage.local` API. It merges loaded settings with defaults, persists changes, and listens for cross-tab storage updates to maintain a consistent UI state. This is crucial for real-time configuration updates. ```javascript // Load settings from browser storage (src/content-script/main.js:62) browser.storage.local.get(settings => { if (!settings) return; // Merge with defaults Object.entries({ ...DEFAULT_SETTINGS, ...settings}).forEach(([ id, value ]) => { HTML.setAttribute(id, value); cache[id] = value; }); // Initialize any missing settings const init = {}; Object.entries(DEFAULT_SETTINGS).forEach(([ id, value]) => { if (!(id in settings)) init[id] = value; }); browser.storage.local.set(init); runDynamicSettings(); }); // Update a setting and persist to storage (src/content-script/main.js:680) function updateSetting(id, value) { HTML.setAttribute(id, value); cache[id] = value; browser.storage.local.set({ [id]: value }); } // Listen for settings changes from other tabs (src/content-script/main.js:44) browser.storage.onChanged.addListener(function logStorageChange(changes, area) { if (area !== 'local') return; Object.entries(changes).forEach(([id, { oldValue, newValue }]) => { if (oldValue === newValue) return; HTML.setAttribute(id, newValue); cache[id] = newValue; if (id.includes('schedule')) { lastScheduleCheck = null; } }); }); ``` -------------------------------- ### JavaScript: Define Extension Settings Structure Source: https://context7.com/lawrencehook/remove-youtube-suggestions/llms.txt Defines the structure for the browser extension's settings, including sections, options with names, tags, IDs, and default values. This configuration is used to manage user preferences for hiding various YouTube elements. It outlines how to access these default settings programmatically. ```javascript const SECTIONS = [ { name: "General", tags: "General", options: [ { name: "Hide all Shorts", tags: "Homepage, Subscriptions, Video Player, Search", id: "remove_all_shorts", defaultValue: false, }, { name: "Hide video thumbnails", id: "remove_video_thumbnails", defaultValue: false, } ] }, { name: "Basic", tags: "Basic", options: [ { name: "Hide all homepage suggestions", tags: "Homepage", id: "remove_homepage", defaultValue: true }, { name: "Hide sidebar suggestions", tags: "Video Player", id: "remove_sidebar", defaultValue: true } ] } ]; // Accessing default settings const DEFAULT_SETTINGS = SECTIONS.reduce((acc, fieldset) => { fieldset.options.forEach(option => acc[option.id] = option.defaultValue); return acc; }, { ...OTHER_SETTINGS }); ``` -------------------------------- ### Real-time Search Filtering for YouTube Settings (JavaScript) Source: https://context7.com/lawrencehook/remove-youtube-suggestions/llms.txt Implements real-time search filtering for settings options within the YouTube interface. It takes user input, hides irrelevant sections and options, and provides a dynamic way to navigate settings. Dependencies include DOM manipulation and event handling. ```javascript // Search functionality (src/options/main.js:267) function onSearchInput(e) { const { target } = e; const { value } = target; const sidebarSections = qsa('.sidebar_section'); const sections = qsa('.section_container:not(#template_section)'); // Reset all visibility sidebarSections.forEach(s => s.removeAttribute('selected')); sections.forEach(section => { section.classList.remove('removed'); const options = Array.from(section.querySelectorAll('div.option')); options.forEach(option => option.classList.remove('removed')); }); if (value === '') return; const searchTerms = value.toLowerCase().split(' '); sections.forEach(section => { const sectionMatch = searchTerms.find(term => { return section.id.toLowerCase().includes(term); }); if (sectionMatch) return; const options = Array.from(section.querySelectorAll('div.option')); let optionFound = false; options.forEach(option => { const optionMatch = searchTerms.find(term => { return option.id.toLowerCase().includes(term) || option.getAttribute('name').toLowerCase().includes(term); }); if (optionMatch) { optionFound = true; return; } option.classList.add('removed'); }); if (!optionFound) { section.classList.add('removed'); } }); } // Usage: Attached to search input const searchBar = document.getElementById('search_bar'); searchBar.addEventListener('input', onSearchInput); ``` -------------------------------- ### Time Formatting Utilities (JavaScript) Source: https://context7.com/lawrencehook/remove-youtube-suggestions/llms.txt Offers functions to format time values into human-readable strings. `parseTimeRemaining` converts milliseconds into days, hours, minutes, and seconds. `formatDateMessageShort` formats a date object into a short, localized time string. These are useful for displaying timestamps and durations. ```javascript // Time parsing utilities (src/shared/utils.js:108) function parseTimeRemaining(ms) { const seconds = Math.floor((ms / (1000)) % 60); const minutes = Math.floor((ms / (60 * 1000)) % 60); const hours = Math.floor((ms / (60 * 60 * 1000)) % 24); const days = Math.floor(ms / (60 * 60 * 24 * 1000) % 7); return { days, hours, minutes, seconds }; } function formatDateMessageShort(date) { if (!date) return 'unknown date'; return date.toLocaleDateString('en-us', { hour: "numeric", minute: "numeric", }).match(/[0-9]{1,2}:[0-9]{1,2}.*/)[0].toLowerCase(); } // Example usage const nextChange = new Date(Date.now() + 3600000); const { hours, minutes, seconds } = parseTimeRemaining(3600000); console.log(`${hours} hours, ${minutes} minutes, ${seconds} seconds`); // Output: "1 hours, 0 minutes, 0 seconds" console.log(formatDateMessageShort(nextChange)); // Output: "3:45 pm" ``` -------------------------------- ### Calculate Next Schedule Change (JavaScript) Source: https://context7.com/lawrencehook/remove-youtube-suggestions/llms.txt Calculates the next time a schedule will trigger an enable/disable action. It iterates minute by minute until the schedule status changes, returning the next triggering date. Dependencies include a `checkSchedule` function and `formatDateMessage` for output. ```javascript // Find next schedule change (src/shared/utils.js:65) function nextScheduleChange(times, days) { if (!times || !days) return; const current = checkSchedule(times, days); const SEC_IN_WEEK = 10_080; let testDate = new Date(); testDate.setSeconds(0); let next = checkSchedule(times, days, testDate); let i = 0; // Iterate minute by minute until schedule status changes while (current === next && i < SEC_IN_WEEK) { i += 1; testDate = new Date(testDate.getTime() + 60_000); next = checkSchedule(times, days, testDate); } if (i > SEC_IN_WEEK) return; return testDate; } // Usage example const nextChange = nextScheduleChange('9:00a-5:00p,6:00p-11:00p', 'mo,tu,we,th,fr'); console.log(formatDateMessage(nextChange)); // Outputs: "Monday, December 10 at 5:00 PM" function formatDateMessage(date) { if (!date) return 'unknown date'; return date.toLocaleDateString('en-us', { weekday:"long", month:"long", hour12: true, day:"numeric", hour: "numeric", minute: "numeric", }); } ``` -------------------------------- ### JavaScript: Redirect YouTube Homepage to Subscriptions, Watch Later, or Library Source: https://context7.com/lawrencehook/remove-youtube-suggestions/llms.txt This script redirects the YouTube homepage to alternative views such as 'Subscriptions', 'Watch Later', or 'Library' based on user preferences. It includes throttling to prevent excessive redirects and also handles redirecting YouTube Shorts to the standard watch page. ```javascript // Homepage redirect handling (src/content-script/main.js:616) const REDIRECT_URLS = { "redirect_off": false, "redirect_to_subs": 'https://www.youtube.com/feed/subscriptions', "redirect_to_wl": 'https://www.youtube.com/playlist/?list=WL', "redirect_to_library": 'https://www.youtube.com/feed/library', }; function handleNewPage() { const on = cache['global_enable'] === true; url = location.href; onHomepage = homepageRegex.test(url); // Homepage redirects with throttling if ( on && onHomepage && !cache['redirect_off'] && (!lastRedirect || Date.now() - lastRedirect > redirectInterval) ) { if (cache['redirect_to_subs']) { const button = qs('a#endpoint[href="/feed/subscriptions"]'); button?.click(); lastRedirect = Date.now(); } if (cache['redirect_to_wl']) { location.replace(REDIRECT_URLS['redirect_to_wl']); lastRedirect = Date.now(); } if (cache['redirect_to_library']) { const button = qs('a#endpoint[href="/feed/library"]'); button?.click(); lastRedirect = Date.now(); } } // Redirect shorts to normal video player if (on && onShorts && cache['normalize_shorts']) { const newUrl = url.replace('shorts', 'watch'); location.replace(newUrl); } } ``` -------------------------------- ### Identify and Hide YouTube Shorts in JavaScript Source: https://context7.com/lawrencehook/remove-youtube-suggestions/llms.txt This JavaScript code identifies YouTube Shorts content across different page layouts using specific CSS selectors for badges and shelves. It marks identified Shorts with custom attributes ('is_short', 'marked_as_short') to facilitate CSS-based hiding. It includes logic for sidebar videos, grid videos, rich items, shelves, and search results, with mobile support. ```javascript // Hide all shorts functionality (src/content-script/main.js:146) if (cache['remove_all_shorts']) { // Find shorts by their badge overlay const shortsBadgeSelector = 'ytd-thumbnail-overlay-time-status-renderer[overlay-style="SHORTS"]'; const shortBadges = qsa(shortsBadgeSelector); shortBadges?.forEach(badge => { // Mark sidebar videos const sidebarVid = badge.closest('ytd-compact-video-renderer'); sidebarVid?.setAttribute('is_short', ''); // Mark grid videos (old layout) const gridVideo = badge.closest('ytd-grid-video-renderer'); gridVideo?.setAttribute('is_short', ''); // Mark rich items (new layout) const updatedGridVideo = badge.closest('ytd-rich-item-renderer'); updatedGridVideo?.setAttribute('is_short', ''); }); // Mark entire shorts shelves const shortsShelfSelector = '*[is-shorts]'; const shortsShelves = qsa(shortsShelfSelector); shortsShelves?.forEach(shelf => { const shelfContainer = shelf.closest('ytd-rich-section-renderer'); shelfContainer?.setAttribute('is_short', ''); }); } // Hide shorts in search results (src/content-script/main.js:233) if (onResultsPage) { const shortResults = qsa('a[href^="/shorts/"]:not([marked_as_short])'); shortResults.forEach(sr => { sr.setAttribute('marked_as_short', true); const result = sr.closest('ytd-video-renderer'); result?.setAttribute('is_short', true); // Mobile support const mobileResult = sr.closest('ytm-video-with-context-renderer'); mobileResult?.setAttribute('is_short', true); }); } ``` -------------------------------- ### Parse and Validate Time-Based Schedules (JavaScript) Source: https://context7.com/lawrencehook/remove-youtube-suggestions/llms.txt This JavaScript utility parses and validates time-based schedules for enabling or disabling features automatically. It checks if the current time falls within specified time ranges and on specified days of the week. Key functions include `checkSchedule` for overall validation and `parseScheduleTime` for converting schedule strings to Date objects. It handles AM/PM notation and requires a `Date` object for the current time, defaulting to the system's current time. ```javascript // Schedule checking utility (src/shared/utils.js:19) const DAYS = ['su','mo','tu','we','th','fr','sa']; function checkSchedule(times, days, now=new Date()) { if (!times || !days) return false; const currDay = DAYS[now.getDay()]; const daysArray = days.split(',').map(d => d.toLowerCase().trim()); if (!daysArray.includes(currDay)) return false; const timesArray = times.split(',').map(t => t.toLowerCase().trim()); const matchFound = timesArray.some(time => { const [ startString, endString ] = time.split('-'); const startTime = parseScheduleTime(startString.trim(), now); const endTime = parseScheduleTime(endString.trim(), now); return now > startTime && now < endTime; }); return matchFound; } function parseScheduleTime(time, now) { if (!time || !now) return; const period = time.slice(-1); const [ hours, minutes ] = time.slice(0, -1).split(':'); const hoursNum = Number(hours); let trueHours; if (hoursNum === 12 && period === 'a') { trueHours = 0; } else if (hoursNum === 12 && period === 'p') { trueHours = 12; } else if (period === 'p') { trueHours = hoursNum + 12; } else { trueHours = hoursNum; } const timeInDay = new Date(now).setHours(trueHours, minutes, 0, 0); return timeInDay; } // Example usage const isActive = checkSchedule('9:00a-5:00p', 'mo,tu,we,th,fr'); // Returns true if current time is Monday-Friday between 9am-5pm ``` -------------------------------- ### JavaScript: Skip and Speed Through YouTube Ads Source: https://context7.com/lawrencehook/remove-youtube-suggestions/llms.txt This script automatically skips YouTube ads by clicking skip buttons or, if ads are unskippable, speeds up playback to 10x and mutes the audio. It restores normal playback settings after the ad has passed. It relies on DOM manipulation and YouTube's internal class names for ad elements. ```javascript if (cache['auto_skip_ads'] === true) { // Close overlay ads qsa('.ytp-ad-overlay-close-button')?.forEach(e => { if (e && e.offsetParent) { e.click(); } }); // Click skip button when available const skipButtons = qsa('.ytp-ad-skip-button') .concat(qsa('.ytp-ad-skip-button-modern')) .concat(qsa('.ytp-skip-ad-button')) .concat(qsa(CSS.escape("button#skip-button:2"))); const skippableAd = skipButtons?.some(button => button.offsetParent); if (skippableAd) { skipButtons?.forEach(e => { if (e && e.offsetParent) { e.click(); } }); } else { // Speed through unskippable ads at 10x speed let adSelectors = [ '.ytp-ad-player-overlay-instream-info', '.ytp-ad-button-icon' ]; let adElements = adSelectors.flatMap(selector => qsa(selector)); const adActive = adElements.some(elt => elt && window.getComputedStyle(elt).display !== 'none'); const video = qs('video'); if (adActive) { if (!hyper) { hyper = true; } video.playbackRate = 10; video.muted = true; } else { // Restore normal playback after ad if (hyper) { let playbackRate = 1; let muted = false; try { const playbackRateObject = window.sessionStorage['yt-player-playback-rate']; const volumeObject = window.sessionStorage['yt-player-volume']; playbackRate = Number(JSON.parse(playbackRateObject).data); muted = JSON.parse(JSON.parse(volumeObject).data).muted; } catch (error) { console.log(error); } video.playbackRate = playbackRate !== undefined ? playbackRate : 1; video.muted = muted !== undefined ? muted : false; hyper = false; } } } } ``` -------------------------------- ### Filter YouTube Subscription Feed Content Types (JavaScript) Source: https://context7.com/lawrencehook/remove-youtube-suggestions/llms.txt This JavaScript code filters various video types on the YouTube subscriptions feed, including shorts, live streams, VODs, and premieres. It achieves this by adding specific attributes to video elements based on detected badges and metadata. Dependencies include helper functions like `qsa` (query selector all) and `qs` (query selector). ```javascript // Subscriptions page filtering (src/content-script/main.js:178) if (onSubs) { const badgeSelector = 'ytd-badge-supported-renderer'; const upcomingBadgeSelector = 'ytd-thumbnail-overlay-time-status-renderer[overlay-style="UPCOMING"]'; const shortsBadgeSelector = 'ytd-thumbnail-overlay-time-status-renderer[overlay-style="SHORTS"]'; const addBadgeTextToVideo = badge => { const badgeText = badge.innerText.trim().split(' ')[0].trim().toLowerCase(); if (badgeText) { const gridVideo = badge.closest('ytd-grid-video-renderer'); const updatedGridVideo = badge.closest('ytd-rich-item-renderer'); gridVideo?.setAttribute('badge-text', badgeText); updatedGridVideo?.setAttribute('badge-text', badgeText); } }; // Mark live and premiere videos const badges = qsa(badgeSelector); badges.forEach(addBadgeTextToVideo); // Mark upcoming videos const upcomingBadges = qsa(upcomingBadgeSelector); upcomingBadges.forEach(addBadgeTextToVideo); // Mark shorts const shortBadges = qsa(shortsBadgeSelector); shortBadges.forEach(badge => { const video = badge.closest('ytd-grid-video-renderer'); const updatedGridVideo = badge.closest('ytd-rich-item-renderer'); video?.setAttribute('is_sub_short', ''); updatedGridVideo?.setAttribute('is_sub_short', ''); }); // Mark VODs (Video On Demand from streams) const vodSelector = '#metadata-line span'; const vodSpans = qsa(vodSelector).filter(span => span.innerText.includes('Streamed')); vodSpans.forEach(span => { const video = span.closest('ytd-grid-video-renderer'); const updatedGridVideo = span.closest('ytd-rich-item-renderer'); video?.setAttribute('is_vod', ''); updatedGridVideo?.setAttribute('is_vod', ''); }); // Reduce empty space by adjusting grid layout const subsRows = qsa('ytd-rich-grid-row'); subsRows.forEach(row => { const contents = qs('#contents', row); if (!contents) return; const items = qsa('ytd-rich-item-renderer', contents); if (!items) return; const activeItems = items.filter(item => item.offsetParent); activeItems.forEach(item => item.style.setProperty('--ytd-rich-grid-items-per-row', activeItems.length)); row.setAttribute('empty', activeItems.length === 0); }); } ``` -------------------------------- ### JavaScript: Disable YouTube Autoplay for Videos and Playlists Source: https://context7.com/lawrencehook/remove-youtube-suggestions/llms.txt This script disables YouTube's autoplay feature for both individual videos and playlists. It interacts with the YouTube UI elements for toggling autoplay on desktop and mobile, and injects a script to control playlist autoplay behavior. ```javascript // Disable video autoplay (src/content-script/main.js:254) if (cache['disable_autoplay'] === true) { // Desktop autoplay toggle const autoplayButton = qsa('.ytp-autonav-toggle-button[aria-checked=true]'); autoplayButton?.forEach(e => { if (e && e.offsetParent) { e.click(); } }); // Mobile autoplay toggle const mAutoplayButton = qsa('.ytm-autonav-toggle-button-container[aria-pressed=true]'); mAutoplayButton?.forEach(e => { if (e && e.offsetParent) { e.click(); } }); } // Disable playlist autoplay via injected script (src/content-script/main.js:564) if (cache['disable_playlist_autoplay']) { const existingScript = document.querySelector('script[id="disable_playlist_autoplay"]') if (existingScript) return; const script = document.createElement("script"); script.id = 'disable_playlist_autoplay'; script.type = "text/javascript"; script.innerText = " (function() { let pm; function f() { if (!pm) pm = document.querySelector('yt-playlist-manager'); if (pm) pm.canAutoAdvance_ = false; } f(); setInterval(f, 100); })()\n"; document.body?.appendChild(script); } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.