Catch all video links from a YouTube channel videos page as you scroll
Size
10.9 KB
Version
1.0.1
Created
Apr 5, 2026
Updated
11 days ago
1// ==UserScript==
2// @name YouTube Video Link Catcher
3// @description Catch all video links from a YouTube channel videos page as you scroll
4// @version 1.0.1
5// @match https://*.youtube.com/*
6// @icon https://www.youtube.com/s/desktop/1afc1cab/img/favicon_32x32.png
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 let isCatching = false;
12 let caughtLinks = new Set();
13 let catchInterval = null;
14 let uiContainer = null;
15
16 function createUI() {
17 // Remove existing UI if present
18 const existingUI = document.getElementById('video-catcher-ui');
19 if (existingUI) {
20 existingUI.remove();
21 }
22
23 // Create main container
24 uiContainer = document.createElement('div');
25 uiContainer.id = 'video-catcher-ui';
26 uiContainer.style.cssText = `
27 position: fixed;
28 top: 80px;
29 right: 20px;
30 background: #0f0f0f;
31 border: 1px solid #3d3d3d;
32 border-radius: 12px;
33 padding: 16px;
34 z-index: 10000;
35 font-family: 'Roboto', 'Arial', sans-serif;
36 min-width: 200px;
37 box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
38 `;
39
40 // Create title
41 const title = document.createElement('div');
42 title.textContent = 'Video Link Catcher';
43 title.style.cssText = `
44 color: #ffffff;
45 font-size: 16px;
46 font-weight: 500;
47 margin-bottom: 12px;
48 `;
49 uiContainer.appendChild(title);
50
51 // Create counter display
52 const counter = document.createElement('div');
53 counter.id = 'video-catcher-counter';
54 counter.textContent = 'Links caught: 0';
55 counter.style.cssText = `
56 color: #aaaaaa;
57 font-size: 14px;
58 margin-bottom: 12px;
59 `;
60 uiContainer.appendChild(counter);
61
62 // Create button container
63 const buttonContainer = document.createElement('div');
64 buttonContainer.style.cssText = `
65 display: flex;
66 gap: 8px;
67 `;
68
69 // Create start button
70 const startButton = document.createElement('button');
71 startButton.id = 'video-catcher-start';
72 startButton.textContent = 'Start Catching';
73 startButton.style.cssText = `
74 background: #3ea6ff;
75 color: #0f0f0f;
76 border: none;
77 border-radius: 18px;
78 padding: 8px 16px;
79 font-size: 14px;
80 font-weight: 500;
81 cursor: pointer;
82 transition: background 0.2s;
83 `;
84 startButton.onmouseover = () => startButton.style.background = '#65c8ff';
85 startButton.onmouseout = () => startButton.style.background = '#3ea6ff';
86 startButton.onclick = startCatching;
87 buttonContainer.appendChild(startButton);
88
89 // Create stop button (initially hidden)
90 const stopButton = document.createElement('button');
91 stopButton.id = 'video-catcher-stop';
92 stopButton.textContent = 'Stop & Show';
93 stopButton.style.cssText = `
94 background: #ff4e45;
95 color: #ffffff;
96 border: none;
97 border-radius: 18px;
98 padding: 8px 16px;
99 font-size: 14px;
100 font-weight: 500;
101 cursor: pointer;
102 transition: background 0.2s;
103 display: none;
104 `;
105 stopButton.onmouseover = () => stopButton.style.background = '#ff7a73';
106 stopButton.onmouseout = () => stopButton.style.background = '#ff4e45';
107 stopButton.onclick = stopCatching;
108 buttonContainer.appendChild(stopButton);
109
110 uiContainer.appendChild(buttonContainer);
111
112 // Create results container (initially hidden)
113 const resultsContainer = document.createElement('div');
114 resultsContainer.id = 'video-catcher-results';
115 resultsContainer.style.cssText = `
116 margin-top: 12px;
117 display: none;
118 `;
119 uiContainer.appendChild(resultsContainer);
120
121 document.body.appendChild(uiContainer);
122 }
123
124 function updateCounter() {
125 const counter = document.getElementById('video-catcher-counter');
126 if (counter) {
127 counter.textContent = `Links caught: ${caughtLinks.size}`;
128 }
129 }
130
131 function extractVideoLinks() {
132 // Find all video links in the grid
133 const videoLinks = document.querySelectorAll('ytd-rich-item-renderer a[href*="/watch?v="]');
134
135 videoLinks.forEach(link => {
136 const href = link.getAttribute('href');
137 if (href) {
138 // Extract full URL
139 const fullUrl = 'https://www.youtube.com' + href;
140 caughtLinks.add(fullUrl);
141 }
142 });
143
144 updateCounter();
145 }
146
147 function startCatching() {
148 isCatching = true;
149 caughtLinks.clear();
150
151 // Update UI
152 const startButton = document.getElementById('video-catcher-start');
153 const stopButton = document.getElementById('video-catcher-stop');
154 const resultsContainer = document.getElementById('video-catcher-results');
155
156 if (startButton) startButton.style.display = 'none';
157 if (stopButton) stopButton.style.display = 'block';
158 if (resultsContainer) resultsContainer.style.display = 'none';
159
160 // Initial extraction
161 extractVideoLinks();
162
163 // Start interval for catching links every 100ms
164 catchInterval = setInterval(() => {
165 extractVideoLinks();
166 }, 100);
167
168 console.log('Video link catching started');
169 }
170
171 function stopCatching() {
172 isCatching = false;
173
174 // Clear interval
175 if (catchInterval) {
176 clearInterval(catchInterval);
177 catchInterval = null;
178 }
179
180 // Update UI
181 const startButton = document.getElementById('video-catcher-start');
182 const stopButton = document.getElementById('video-catcher-stop');
183
184 if (startButton) startButton.style.display = 'block';
185 if (stopButton) stopButton.style.display = 'none';
186
187 // Display results
188 displayResults();
189
190 console.log('Video link catching stopped. Total links:', caughtLinks.size);
191 }
192
193 function displayResults() {
194 const resultsContainer = document.getElementById('video-catcher-results');
195 if (!resultsContainer) return;
196
197 resultsContainer.style.display = 'block';
198 resultsContainer.innerHTML = '';
199
200 if (caughtLinks.size === 0) {
201 const noResults = document.createElement('div');
202 noResults.textContent = 'No video links found.';
203 noResults.style.cssText = `
204 color: #aaaaaa;
205 font-size: 14px;
206 padding: 8px 0;
207 `;
208 resultsContainer.appendChild(noResults);
209 return;
210 }
211
212 // Create textarea with links
213 const textarea = document.createElement('textarea');
214 textarea.readOnly = true;
215 textarea.value = Array.from(caughtLinks).join('\n\n');
216 textarea.style.cssText = `
217 width: 280px;
218 height: 200px;
219 background: #1a1a1a;
220 color: #ffffff;
221 border: 1px solid #3d3d3d;
222 border-radius: 8px;
223 padding: 8px;
224 font-family: 'Roboto Mono', monospace;
225 font-size: 12px;
226 resize: vertical;
227 box-sizing: border-box;
228 `;
229 resultsContainer.appendChild(textarea);
230
231 // Create copy button
232 const copyButton = document.createElement('button');
233 copyButton.textContent = 'Copy to Clipboard';
234 copyButton.style.cssText = `
235 width: 100%;
236 margin-top: 8px;
237 background: #2ba640;
238 color: #ffffff;
239 border: none;
240 border-radius: 18px;
241 padding: 8px 16px;
242 font-size: 14px;
243 font-weight: 500;
244 cursor: pointer;
245 transition: background 0.2s;
246 `;
247 copyButton.onmouseover = () => copyButton.style.background = '#4cc65f';
248 copyButton.onmouseout = () => copyButton.style.background = '#2ba640';
249 copyButton.onclick = async () => {
250 try {
251 await GM.setClipboard(textarea.value);
252 const originalText = copyButton.textContent;
253 copyButton.textContent = 'Copied!';
254 setTimeout(() => {
255 copyButton.textContent = originalText;
256 }, 1500);
257 } catch (error) {
258 console.error('Failed to copy to clipboard:', error);
259 }
260 };
261 resultsContainer.appendChild(copyButton);
262
263 // Create close button
264 const closeButton = document.createElement('button');
265 closeButton.textContent = 'Close Results';
266 closeButton.style.cssText = `
267 width: 100%;
268 margin-top: 8px;
269 background: #3d3d3d;
270 color: #ffffff;
271 border: none;
272 border-radius: 18px;
273 padding: 8px 16px;
274 font-size: 14px;
275 font-weight: 500;
276 cursor: pointer;
277 transition: background 0.2s;
278 `;
279 closeButton.onmouseover = () => closeButton.style.background = '#5a5a5a';
280 closeButton.onmouseout = () => closeButton.style.background = '#3d3d3d';
281 closeButton.onclick = () => {
282 resultsContainer.style.display = 'none';
283 };
284 resultsContainer.appendChild(closeButton);
285 }
286
287 function init() {
288 // Check if we're on a channel videos page
289 const isVideosPage = window.location.pathname.includes('/videos') ||
290 window.location.pathname.includes('/videos');
291
292 if (isVideosPage) {
293 createUI();
294 console.log('YouTube Video Link Catcher initialized');
295 }
296
297 // Watch for URL changes (YouTube is SPA)
298 let lastUrl = location.href;
299 const urlObserver = new MutationObserver(() => {
300 if (location.href !== lastUrl) {
301 lastUrl = location.href;
302 // Stop catching if active
303 if (isCatching) {
304 stopCatching();
305 }
306 // Check if new page is videos page
307 const isNewVideosPage = location.pathname.includes('/videos');
308 if (isNewVideosPage) {
309 setTimeout(createUI, 1000);
310 } else {
311 const existingUI = document.getElementById('video-catcher-ui');
312 if (existingUI) {
313 existingUI.remove();
314 }
315 }
316 }
317 });
318
319 urlObserver.observe(document.body, {
320 childList: true,
321 subtree: true
322 });
323 }
324
325 // Wait for page to load
326 if (document.readyState === 'loading') {
327 document.addEventListener('DOMContentLoaded', init);
328 } else {
329 init();
330 }
331})();