您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Create "Play all" videos playlist, accessible from channel page
// ==UserScript== // @name All vids playlist // @namespace http://tampermonkey.net/ // @version 1.0.4 // @description Create "Play all" videos playlist, accessible from channel page // @author // @match https://www.youtube.com/* // @license MIT // @grant none // @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTMgMTBIMTRWMTJIM1YxME0zIDZIMTRWOEgzVjZNMyAxNEgxMFYxNkgzVjE0TTE2IDEzVjIxTDIyIDE3TDE2IDEzWiIgLz48L3N2Zz4= // ==/UserScript== (function() { 'use strict'; // URL pattern: any channel's /videos page const pattern = /^https:\/\/www\.youtube\.com\/.*\/videos$/; let buttonExists = false; // Observers const config = { attributes: true, childList: true, characterData: true, subtree: true }; const observerMain = new MutationObserver(onMutationsMain); const observerWait = new MutationObserver(onMutationsWait); // Start observing for URL changes, etc. observerMain.observe(document, config); /** * This gets called on *any* DOM mutation. We only do something * if the URL matches a channel's /videos page. */ function onMutationsMain() { const url = window.location.href; if (pattern.test(url) && !buttonExists) { // We are on /videos but haven't placed our button yet observerMain.disconnect(); // Temporarily stop observerWait.observe(document, config); // Wait for chips to load } else if (!pattern.test(url)) { // If we leave /videos, remove the button removeMyButton(); } } /** * Called by observerWait once new mutations appear; we look * for the built‐in chips (#chips) to exist. Then add our custom chip. */ function onMutationsWait() { const chipsContainer = document.querySelector('#chips'); const realChip = chipsContainer && chipsContainer.querySelector( 'yt-chip-cloud-chip-renderer[chip-style="STYLE_DEFAULT"]' ); const myButton = document.getElementById('mybutton'); // If the standard chips are there and we don't already have our chip if (realChip && !myButton) { observerWait.disconnect(); addCustomChip(); // Resume the main observer so we keep track of page navigation observerMain.observe(document, config); } } /** * Builds the "Play all" chip and appends it into #chips. */ /* function addCustomChip() { const chipsContainer = document.querySelector('#chips'); if (!chipsContainer) return; // Create our custom chip const playAllChip = createPlayAllChip(); // Add click event to redirect to the "all videos" playlist playAllChip.addEventListener('click', onPlayAllClicked); // Append to the container chipsContainer.appendChild(playAllChip); buttonExists = true; } */ /** * If we leave the /videos page, remove our chip. */ function removeMyButton() { const btns = document.querySelectorAll('#mybutton'); btns.forEach(btn => btn.remove()); buttonExists = false; } /** * On "Play all" click, redirect to a playlist with all channel videos. */ function onPlayAllClicked() { const linkElement = document.querySelector('link[rel="canonical"]'); if (!linkElement) return; // e.g. https://www.youtube.com/channel/UCxxxxxx/videos => lastPart = UCxxxxxx const hrefValue = linkElement.getAttribute('href'); const lastPart = hrefValue.split('/').pop(); // Ensure the second character is 'U' => e.g. "Cxxxxxx" -> "UCxxxxx" const newLastPart = replaceAt(1, 'U', lastPart); // Build the playlist URL const newUrl = 'https://www.youtube.com/playlist?list=' + newLastPart; window.location.href = newUrl; } /** * Helper that directly builds a minimal <yt-chip-cloud-chip-renderer> * with the text "Play all" in red & bold, matching the structure YouTube uses. */ function createPlayAllChip() { // 1) Outer <yt-chip-cloud-chip-renderer> const chip = document.createElement('yt-chip-cloud-chip-renderer'); chip.className = 'style-scope ytd-feed-filter-chip-bar-renderer'; chip.setAttribute('modern', ''); chip.setAttribute('aria-selected', 'false'); chip.setAttribute('role', 'tab'); chip.setAttribute('tabindex', '0'); chip.setAttribute('chip-style', 'STYLE_DEFAULT'); chip.setAttribute('chip-shape-data', '{"text":"Play all","state":2,"type":0}'); chip.id = 'mybutton'; chip.setAttribute('title', 'Play every video in this channel'); // Optional styling chip.style.color = 'red'; chip.style.fontWeight = 'bolder'; // 2) <div id="chip-shape-container"><chip-shape></chip-shape></div> const shapeContainer = document.createElement('div'); shapeContainer.id = 'chip-shape-container'; shapeContainer.className = 'style-scope yt-chip-cloud-chip-renderer'; const chipShape = document.createElement('chip-shape'); chipShape.className = 'style-scope yt-chip-cloud-chip-renderer'; shapeContainer.appendChild(chipShape); chip.appendChild(shapeContainer); // 3) <div id="chip-container"><yt-formatted-string id="text">...</yt-formatted-string></div> const chipContainer = document.createElement('div'); chipContainer.id = 'chip-container'; chipContainer.className = 'style-scope yt-chip-cloud-chip-renderer'; chip.appendChild(chipContainer); // We'll insert the text in a moment (in a separate function). return chip; } /** * After we append the chip to the page, YouTube may have auto‐injected * <yt-icon> or messed with the text. So we forcibly: * - Remove any <yt-icon> if we don't want it * - Remove is-empty from <yt-formatted-string> * - Re‐set the text content to "Play all" */ function finalizeChipContent(chip) { // Remove the leading icon if YouTube injected it const icon = chip.querySelector('yt-icon.leading-icon'); if (icon) { icon.remove(); } // Check if we have #chip-container const chipContainer = chip.querySelector('#chip-container'); if (!chipContainer) return; // If there's already a <yt-formatted-string> element, great; use it let formattedString = chip.querySelector('yt-formatted-string#text'); if (!formattedString) { // Otherwise, create one formattedString = document.createElement('yt-formatted-string'); formattedString.id = 'text'; formattedString.className = 'style-scope yt-chip-cloud-chip-renderer'; formattedString.setAttribute('ellipsis-truncate', ''); formattedString.setAttribute('ellipsis-truncate-styling', ''); formattedString.setAttribute('title', 'Play all'); chipContainer.appendChild(formattedString); } // YouTube might set is-empty="..." formattedString.removeAttribute('is-empty'); // Insert a <yt-attributed-string> child with our text // (remove any existing children first, just in case) formattedString.innerHTML = 'Play All'; formattedString.style.color = 'red'; formattedString.style.fontWeight = 'bolder'; const attrString = document.createElement('yt-attributed-string'); attrString.className = 'style-scope yt-formatted-string'; attrString.textContent = 'Play all'; // The visible label formattedString.appendChild(attrString); } /** * Called by your code once #chips is present, to insert the new chip. */ function addCustomChip() { const chipsContainer = document.querySelector('#chips'); if (!chipsContainer) return; const playAllChip = createPlayAllChip(); // Append first chipsContainer.appendChild(playAllChip); // Then forcibly fix the content: finalizeChipContent(playAllChip); // Now attach the click event: playAllChip.addEventListener('click', onPlayAllClicked); buttonExists = true; } /** * Helper to replace text in a string at index. */ function replaceAt(index, replacement, string) { return string.substr(0, index) + replacement + string.substr(index + replacement.length); } })();