Adds a Play All button and oldest-to-newest sorting for YouTube playlists
Size
6.2 KB
Version
1.0.1
Created
Oct 22, 2025
Updated
about 17 hours ago
1// ==UserScript==
2// @name YouTube Playlist Enhancer - Play All & Sort
3// @description Adds a Play All button and oldest-to-newest sorting for YouTube playlists
4// @version 1.0.1
5// @match https://www.youtube.com/*
6// @match https://m.youtube.com/*
7// @icon https://robomonkey.io/icon.png?adc3438f5fbb5315
8// ==/UserScript==
9(function() {
10 'use strict';
11
12 console.log('YouTube Playlist Enhancer loaded');
13
14 // Debounce function to prevent excessive calls
15 function debounce(func, wait) {
16 let timeout;
17 return function executedFunction(...args) {
18 const later = () => {
19 clearTimeout(timeout);
20 func(...args);
21 };
22 clearTimeout(timeout);
23 timeout = setTimeout(later, wait);
24 };
25 }
26
27 // Check if we're on a playlist page
28 function isPlaylistPage() {
29 return window.location.href.includes('/playlist?list=');
30 }
31
32 // Add Play All button
33 function addPlayAllButton() {
34 if (!isPlaylistPage()) return;
35
36 // Check if button already exists
37 if (document.querySelector('#rm-play-all-button')) return;
38
39 // Find the playlist header actions area
40 const playlistHeader = document.querySelector('ytd-playlist-header-renderer #top-level-buttons-computed');
41 if (!playlistHeader) {
42 console.log('Playlist header not found yet');
43 return;
44 }
45
46 console.log('Adding Play All button');
47
48 // Create Play All button
49 const playAllButton = document.createElement('button');
50 playAllButton.id = 'rm-play-all-button';
51 playAllButton.className = 'yt-spec-button-shape-next yt-spec-button-shape-next--tonal yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m';
52 playAllButton.innerHTML = `
53 <div class="yt-spec-button-shape-next__button-text-content">
54 <span class="yt-core-attributed-string yt-core-attributed-string--white-space-no-wrap">▶ Play All</span>
55 </div>
56 `;
57 playAllButton.style.marginLeft = '8px';
58
59 playAllButton.addEventListener('click', async () => {
60 console.log('Play All button clicked');
61 const firstVideo = document.querySelector('ytd-playlist-video-renderer a#video-title');
62 if (firstVideo) {
63 const videoUrl = firstVideo.href;
64 // Add playlist parameter to play all videos
65 const playlistId = new URLSearchParams(window.location.search).get('list');
66 window.location.href = `${videoUrl}&list=${playlistId}`;
67 }
68 });
69
70 playlistHeader.appendChild(playAllButton);
71 }
72
73 // Add Sort button (Oldest to Newest)
74 function addSortButton() {
75 if (!isPlaylistPage()) return;
76
77 // Check if button already exists
78 if (document.querySelector('#rm-sort-button')) return;
79
80 // Find the playlist header actions area
81 const playlistHeader = document.querySelector('ytd-playlist-header-renderer #top-level-buttons-computed');
82 if (!playlistHeader) {
83 console.log('Playlist header not found yet');
84 return;
85 }
86
87 console.log('Adding Sort button');
88
89 // Create Sort button
90 const sortButton = document.createElement('button');
91 sortButton.id = 'rm-sort-button';
92 sortButton.className = 'yt-spec-button-shape-next yt-spec-button-shape-next--tonal yt-spec-button-shape-next--mono yt-spec-button-shape-next--size-m';
93 sortButton.innerHTML = `
94 <div class="yt-spec-button-shape-next__button-text-content">
95 <span class="yt-core-attributed-string yt-core-attributed-string--white-space-no-wrap">↑ Oldest First</span>
96 </div>
97 `;
98 sortButton.style.marginLeft = '8px';
99
100 sortButton.addEventListener('click', async () => {
101 console.log('Sort button clicked');
102
103 // Click the sort dropdown
104 const sortDropdown = document.querySelector('ytd-playlist-header-renderer paper-menu-button button[aria-label*="sort"], ytd-playlist-header-renderer yt-sort-filter-sub-menu-renderer button');
105 if (sortDropdown) {
106 sortDropdown.click();
107
108 // Wait for menu to appear and click "Date added (oldest)" option
109 setTimeout(() => {
110 const oldestOption = Array.from(document.querySelectorAll('tp-yt-paper-listbox ytd-menu-service-item-renderer, tp-yt-paper-listbox yt-formatted-string'))
111 .find(el => el.textContent.includes('oldest') || el.textContent.includes('Oldest'));
112
113 if (oldestOption) {
114 oldestOption.click();
115 console.log('Clicked oldest first option');
116 } else {
117 console.log('Oldest option not found');
118 }
119 }, 300);
120 } else {
121 console.log('Sort dropdown not found');
122 }
123 });
124
125 playlistHeader.appendChild(sortButton);
126 }
127
128 // Initialize buttons
129 function init() {
130 if (!isPlaylistPage()) return;
131
132 console.log('Initializing YouTube Playlist Enhancer');
133 addPlayAllButton();
134 addSortButton();
135 }
136
137 // Watch for URL changes (YouTube is a SPA)
138 let lastUrl = location.href;
139 const urlObserver = new MutationObserver(debounce(() => {
140 const currentUrl = location.href;
141 if (currentUrl !== lastUrl) {
142 lastUrl = currentUrl;
143 console.log('URL changed to:', currentUrl);
144 setTimeout(init, 1000);
145 }
146 }, 500));
147
148 // Watch for DOM changes to detect when playlist loads
149 const contentObserver = new MutationObserver(debounce(() => {
150 init();
151 }, 500));
152
153 // Start observing
154 if (document.body) {
155 urlObserver.observe(document.body, { childList: true, subtree: true });
156 contentObserver.observe(document.body, { childList: true, subtree: true });
157 }
158
159 // Initial load
160 if (document.readyState === 'loading') {
161 document.addEventListener('DOMContentLoaded', () => {
162 setTimeout(init, 2000);
163 });
164 } else {
165 setTimeout(init, 2000);
166 }
167
168})();