YouTube Video Link Catcher

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})();