您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Create a custom YouTube playlist and play videos automatically in sequence with countdown and pause option. client download link https://www.icloud.com/shortcuts/86fd7faf174845a8b07090a703a196bf
// ==UserScript== // @name YouTube AutoPlaylist Manager // @namespace http://tampermonkey.net/ // @version 1.3.13 // @description Create a custom YouTube playlist and play videos automatically in sequence with countdown and pause option. client download link https://www.icloud.com/shortcuts/86fd7faf174845a8b07090a703a196bf // @author looz38 // @license CC BY-NC 4.0 // @match *://www.youtube.com/* // @grant none // ==/UserScript== (function () { "use strict"; const PLAYLIST_KEY = "Custom_playlist"; // LocalStorage key for playlist // JSONBin Configuration const JSONBIN_API_URL = "https://api.jsonbin.io/v3/b"; // JSONBin API URL const collectionId = "6773978cad19ca34f8e37887"; const JSONBIN_API_KEY = "$2a$10$ZNuWfqMh1EHdAOran5uwH.EhsrZkFvD7bnobdc6/6m0jPJaebLaVK"; // Replace with your JSONBin API Key // Update MutationObserver to include the new button const observer = new MutationObserver(() => { addItemToVideos(); playNextButton(); showPlaylistButton(); }); observer.observe(document.body, { childList: true, subtree: true }); // Utility: Get playlist from localStorage function getPlaylist() { return JSON.parse(localStorage.getItem(PLAYLIST_KEY) || "[]"); } // Utility: Save playlist to localStorage function savePlaylist(playlist) { localStorage.setItem(PLAYLIST_KEY, JSON.stringify(playlist)); } // Utility: Remove the first video from the playlist function popFromPlaylist() { const playlist = getPlaylist(); if (playlist.length > 0) { const nextVideo = playlist.shift(); savePlaylist(playlist); return nextVideo; } return null; } // Utility: Add a video to the playlist and remove the "Add to Playlist" button function addToPlaylist(videoId, title, videoType, button) { if (!videoId) { alert("Error: Video ID is missing!"); return; } title = title || `Video (${videoId})`; const playlist = getPlaylist(); // Check for duplicates const isDuplicate = playlist.some((video) => video.videoId === videoId); if (isDuplicate) { alert("This video is already in the playlist!"); return; } playlist.push({ videoId, title, videoType }); savePlaylist(playlist); if (button) { button.remove(); } } function extractVideoId(link) { try { const url = new URL(link.href, window.location.origin); if (url.pathname.includes("/shorts/")) { return url.pathname.split("/shorts/")[1]; } else { return url.searchParams.get("v"); } } catch (error) { console.error("Invalid link:", link.href, error); return null; // Return null if unable to parse } } function extractTitle(link) { try { // Check if it is a Shorts video const isShorts = new URL( link.href, window.location.origin ).pathname.includes("/shorts/"); if (isShorts) { // For Shorts video, find the parent title element const parentRenderer = link.closest( "ytd-rich-item-renderer, ytm-shorts-lockup-view-model-v2" ); const titleElement = parentRenderer?.querySelector( 'h3 a[title], h3 span[role="text"]' ); return titleElement?.textContent.trim() || "Untitled Video"; } else { // For regular videos, continue using the existing logic const titleElement = link .closest("ytd-rich-item-renderer, ytd-video-renderer") ?.querySelector("#video-title"); return titleElement?.textContent.trim() || "Untitled Video"; } } catch (error) { console.error("Error extracting title:", link.href, error); return "Untitled Video"; } } // Add "Add to Playlist" button to each video thumbnail function addItemToVideos() { const videoLinks = document.querySelectorAll( '#thumbnail, a[href*="/shorts/"]' ); videoLinks.forEach((link) => { if (!link.dataset.playlistButtonAdded) { link.dataset.playlistButtonAdded = true; // Avoid adding duplicate buttons const videoId = extractVideoId(link); const title = extractTitle(link); const videoType = link.href.includes("/shorts/") ? "shorts" : "regular"; // Create "Add to Playlist" button const button = document.createElement("button"); button.textContent = "Add to Playlist"; button.style.cssText = ` position: absolute; bottom: 5px; left: 5px; z-index: 999; padding: 5px; font-size: 10px; background-color: #FF00005C; color: white; border: none; cursor: pointer; `; button.onclick = (e) => { e.preventDefault(); addToPlaylist(videoId, title, videoType, button); // Pass the videoType }; // Append button to the video thumbnail link.parentElement.style.position = "relative"; link.parentElement.appendChild(button); } }); } // Add "Play Playlist" button near the search box function playNextButton() { const searchBox = document.querySelector( ".ytSearchboxComponentSearchButton" ); if (searchBox && !document.querySelector("#play-playlist-button")) { const buttonContainer = document.createElement("div"); buttonContainer.id = "button-container"; buttonContainer.style.cssText = ` display: flex; gap: 4px; position: absolute; top: 28%; left: 12%; `; const playNextButton = document.createElement("button"); playNextButton.id = "play-playlist-button"; playNextButton.textContent = "Next"; playNextButton.style.cssText = ` padding: 5px 10px; background-color: #FF0000; color: white; border: none; cursor: pointer; border-radius: 7px; width: 68px; `; playNextButton.onclick = () => { const nextVideo = popFromPlaylist(); if (nextVideo) { window.open( `https://www.youtube.com/watch?v=${nextVideo.videoId}`, "_blank" ); } else { alert("The playlist is empty!"); } }; const addCurrentVideoButton = document.createElement("button"); addCurrentVideoButton.id = "add-current-video-button"; addCurrentVideoButton.textContent = "Append"; addCurrentVideoButton.style.cssText = ` padding: 5px 10px; background-color: #28a745; color: white; border: none; cursor: pointer; border-radius: 7px; width: 68px; `; addCurrentVideoButton.onclick = () => { const videoId = new URLSearchParams(window.location.search).get("v"); const title = document .querySelector( "h1.style-scope.ytd-watch-metadata yt-formatted-string" ) ?.textContent.trim() || "Untitled Video"; const videoType = window.location.href.includes("/shorts/") ? "shorts" : "regular"; addToPlaylist(videoId, title, videoType); }; buttonContainer.appendChild(playNextButton); buttonContainer.appendChild(addCurrentVideoButton); const masthead = document.querySelector("ytd-masthead"); if (masthead) { masthead.appendChild(buttonContainer); } } } // Fetch the latest playlist from JSONBin async function fetchLatestPlaylistFromJSONBin() { try { const binId = localStorage.getItem("jsonBinId"); if (!binId) { alert("No playlist found to sync!"); return null; } const response = await fetch(`${JSONBIN_API_URL}/${binId}/latest`, { headers: { "X-Access-Key": JSONBIN_API_KEY, }, }); if (!response.ok) { throw new Error(`Failed to fetch playlist: ${response.statusText}`); } const data = await response.json(); return data.record; } catch (error) { console.error("Error fetching playlist from JSONBin:", error); return null; } } // Add "View Playlist" button near the search box function showPlaylistButton() { const searchBox = document.querySelector( ".ytSearchboxComponentSearchButton" ); if (searchBox && !document.querySelector("#view-playlist-button")) { const buttonContainer = document.querySelector("#button-container"); const viewPlaylistButton = document.createElement("button"); viewPlaylistButton.id = "view-playlist-button"; viewPlaylistButton.textContent = "List"; viewPlaylistButton.style.cssText = ` padding: 5px 10px; background-color: #007BFF; color: white; border: none; cursor: pointer; border-radius: 7px; width: 68px; `; viewPlaylistButton.onclick = showPlaylistUI; const syncPlaylistButton = document.createElement("button"); syncPlaylistButton.id = "sync-playlist-button"; syncPlaylistButton.textContent = "Sync"; syncPlaylistButton.style.cssText = ` padding: 5px 10px; background-color: #17a2b8; color: white; border: none; cursor: pointer; border-radius: 7px; width: 68px; `; syncPlaylistButton.onclick = async () => { const latestPlaylist = await fetchLatestPlaylistFromJSONBin(); if (latestPlaylist) { savePlaylist(latestPlaylist); alert("Playlist synced successfully!"); } else { alert("Failed to sync playlist!"); } }; buttonContainer.appendChild(viewPlaylistButton); buttonContainer.appendChild(syncPlaylistButton); } } // Function to show the playlist UI (floating div with video list) function showPlaylistUI() { // Check if the UI is already added if (document.querySelector("#custom-playlist-ui")) return; // Create the UI container for the playlist const playlistContainer = document.createElement("div"); playlistContainer.id = "custom-playlist-ui"; playlistContainer.style.cssText = ` position: fixed; top: 6%; left: 10px; width: 320px; max-height: 80%; overflow-y: auto; background-color: white; border: 1px solid #ccc; border-radius: 5px; padding: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); z-index: 9999; font-family: Arial, sans-serif; font-size: 14px; `; playlistContainer.style.scrollbarWidth = "thick"; // For Firefox playlistContainer.style.scrollbarColor = "#888 #ccc"; // For Firefox // Prevent page scrolling when the playlist UI is open document.body.style.overflow = "hidden"; // Add a title to the container const title = document.createElement("h3"); title.textContent = "Playlist"; title.style.cssText = "margin-top: 0; text-align: center;"; playlistContainer.appendChild(title); // Add the playlist items const playlist = getPlaylist(); const list = document.createElement("ul"); list.style.cssText = "list-style-type: none; padding: 0; margin: 0;"; list.id = "playlist-items"; playlist.forEach((item, index) => { const listItem = document.createElement("li"); listItem.style.cssText = ` display: flex; justify-content: space-between; align-items: center; padding: 5px 0; border-bottom: 1px solid #eee; cursor: pointer; `; listItem.draggable = true; listItem.dataset.index = index; listItem.textContent = `${index + 1}. ${item.title}`; if (item.videoType === "shorts") { listItem.style.color = "blue"; } // Play the selected video on click listItem.onclick = () => { const baseUrl = item.videoType === "shorts" ? "https://www.youtube.com/shorts/" : "https://www.youtube.com/watch?v="; window.location.href = baseUrl + item.videoId; // Remove the video from the playlist const updatedPlaylist = getPlaylist(); updatedPlaylist.splice(index, 1); savePlaylist(updatedPlaylist); // Refresh the UI playlistContainer.remove(); showPlaylistUI(); }; // Add delete button to each item const deleteButton = document.createElement("button"); deleteButton.textContent = "Delete"; deleteButton.style.cssText = ` margin-left: 10px; padding: 2px 5px; background-color: #FF0000; color: white; border: none; cursor: pointer; border-radius: 3px; `; deleteButton.onclick = (e) => { e.stopPropagation(); // Prevent triggering the play action const updatedPlaylist = getPlaylist(); updatedPlaylist.splice(index, 1); savePlaylist(updatedPlaylist); // Refresh the UI playlistContainer.remove(); showPlaylistUI(); }; listItem.appendChild(deleteButton); list.appendChild(listItem); }); playlistContainer.appendChild(list); // Add QR Code button const qrButton = document.createElement("button"); qrButton.textContent = "Generate QR Code"; qrButton.style.cssText = ` display: block; margin: 10px auto; padding: 5px 10px; background-color: #007BFF; color: white; border: none; cursor: pointer; border-radius: 3px; `; qrButton.onclick = () => generateQRCode(playlist); playlistContainer.appendChild(qrButton); // Append the container to the body document.body.appendChild(playlistContainer); // Close the UI when clicking outside function handleClickOutside(event) { if (!playlistContainer.contains(event.target)) { playlistContainer.remove(); document.removeEventListener("click", handleClickOutside); document.body.style.overflow = ""; // Restore page scrolling } } // Add event listener to detect outside clicks setTimeout(() => { document.addEventListener("click", handleClickOutside); }, 100); // Add a 100ms delay to prevent immediate closure // Add drag-and-drop functionality const playlistItems = document.getElementById("playlist-items"); let draggedItem = null; playlistItems.addEventListener("dragstart", (e) => { draggedItem = e.target; e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/html", e.target.innerHTML); }); playlistItems.addEventListener("dragover", (e) => { e.preventDefault(); e.dataTransfer.dropEffect = "move"; }); playlistItems.addEventListener("drop", (e) => { e.stopPropagation(); if (draggedItem && draggedItem !== e.target) { const fromIndex = parseInt(draggedItem.dataset.index, 10); const toIndex = parseInt(e.target.dataset.index, 10); const updatedPlaylist = getPlaylist(); const [movedItem] = updatedPlaylist.splice(fromIndex, 1); updatedPlaylist.splice(toIndex, 0, movedItem); savePlaylist(updatedPlaylist); // Refresh the UI playlistContainer.remove(); showPlaylistUI(); } draggedItem = null; }); } // Generate QR Code for playlist JSON URL async function generateQRCode(playlist) { const playlistUrl = await uploadPlaylistToJSONBin(playlist); if (!playlistUrl) { return; // Abort if upload fails } // Create the QR Code UI const qrContainer = document.createElement("div"); qrContainer.style.cssText = ` position: fixed; top: 30%; left: 50%; transform: translateX(-50%); background-color: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); z-index: 9999; font-family: Arial, sans-serif; `; // Create the QR code image const qrImage = document.createElement("img"); qrImage.src = `https://api.qrserver.com/v1/create-qr-code/?data=${encodeURIComponent( playlistUrl )}&size=300x300`; qrContainer.appendChild(qrImage); // Add a close button for the QR Code popup const closeQrButton = document.createElement("button"); closeQrButton.textContent = "Close"; closeQrButton.style.cssText = ` display: block; margin: 10px auto; padding: 5px 10px; background-color: #FF0000; color: white; border: none; cursor: pointer; border-radius: 3px; `; closeQrButton.onclick = () => qrContainer.remove(); qrContainer.appendChild(closeQrButton); // Append the QR Code container to the body document.body.appendChild(qrContainer); } // Automatically play the next video when one ends with countdown function autoPlayNextVideo() { const videoElement = document.querySelector("video"); if (videoElement) { videoElement.addEventListener("ended", () => { const nextVideo = popFromPlaylist(); if (nextVideo) { startCountdown(nextVideo); // Start countdown before autoplaying } }); } } // Start a countdown before automatically playing the next video function startCountdown(nextVideo) { const countdownContainer = document.createElement("div"); countdownContainer.id = "countdown-container"; countdownContainer.style.cssText = ` position: fixed; bottom: 10px; left: 10px; padding: 10px; background-color: rgba(0, 0, 0, 0.5); color: white; font-size: 20px; border-radius: 5px; z-index: 9999; `; document.body.appendChild(countdownContainer); let countdownTime = 8; // Set countdown time to 8 seconds let isCountdownPaused = false; // Flag to track pause state let timeoutId; // Add "Pause" button to stop the countdown const pauseButton = document.createElement("button"); pauseButton.textContent = "Pause AutoPlay"; pauseButton.style.cssText = ` margin-top: 10px; padding: 5px 10px; background-color: #FF0000; color: white; border: none; cursor: pointer; `; pauseButton.onclick = () => { isCountdownPaused = !isCountdownPaused; pauseButton.textContent = isCountdownPaused ? "Resume AutoPlay" : "Pause AutoPlay"; }; // Function to update countdown function updateCountdown() { if (!isCountdownPaused) { countdownContainer.textContent = `Next video in: ${countdownTime}s`; // Append the pause button to countdown container countdownContainer.appendChild(pauseButton); countdownTime--; if (countdownTime < 0) { clearTimeout(timeoutId); // Decide the correct URL based on videoType let baseUrl; if (nextVideo.videoType === "shorts") { baseUrl = "https://www.youtube.com/shorts/"; } else { baseUrl = "https://www.youtube.com/watch?v="; } // Navigate to the next video window.location.href = baseUrl + nextVideo.videoId; } else { timeoutId = setTimeout(updateCountdown, 1000); // Update countdown every second } } } // Initialize countdown updateCountdown(); // Ensure the countdown container remains visible countdownContainer.style.display = "block"; } // Initialize on video page if (window.location.href.includes("watch")) { autoPlayNextVideo(); } // Upload playlist to JSONBin and generate a sharable URL async function uploadPlaylistToJSONBin(playlist) { if (!playlist || playlist.length === 0) { alert("The playlist is empty!"); return null; } try { let binId = localStorage.getItem("jsonBinId"); const method = binId ? "PUT" : "POST"; const url = binId ? `${JSONBIN_API_URL}/${binId}` : JSONBIN_API_URL; const response = await fetch(url, { method, headers: { "Content-Type": "application/json", "X-Access-Key": JSONBIN_API_KEY, "X-Bin-Name": "playlist", "X-Collection-Id": collectionId, }, body: JSON.stringify(playlist), }); if (!response.ok) { throw new Error(`Failed to upload playlist: ${response.statusText}`); } const data = await response.json(); if (!binId && data.metadata) { binId = data.metadata.id; localStorage.setItem("jsonBinId", binId); } return `${JSONBIN_API_URL}/${binId}/latest`; // Public URL for JSONBin } catch (error) { console.error("Error uploading playlist to JSONBin:", error); alert("Failed to upload playlist to server!"); return null; } } console.log("[YouTube Playlist Manager] Script initialized."); })();