Spaces:
Paused
Paused
| // ==UserScript== | |
| // @name YouTube Download Manager | |
| // @namespace https://vietcat.dev/ | |
| // @version 1.3 | |
| // @description Download YouTube Premium videos via custom Hugging Face API with CSP-safe DOM handling. | |
| // @author vietcat | |
| // @match https://www.youtube.com/watch* | |
| // @grant GM_xmlhttpRequest | |
| // @grant GM_addStyle | |
| // @connect vietcat-remotedownloader.hf.space | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| const API_BASE = 'https://vietcat-remotedownloader.hf.space'; | |
| const state = { | |
| tasks: [], | |
| cookies: '' | |
| }; | |
| function createUI() { | |
| const container = document.createElement('div'); | |
| container.id = 'yt-download-popup'; | |
| const box = document.createElement('div'); | |
| box.style.background = '#202020'; | |
| box.style.color = 'white'; | |
| box.style.padding = '10px'; | |
| box.style.borderRadius = '8px'; | |
| box.style.width = '300px'; | |
| const cookieToggleBtn = document.createElement('button'); | |
| cookieToggleBtn.textContent = '🛠️ Cookie Settings'; | |
| cookieToggleBtn.style.marginBottom = '6px'; | |
| cookieToggleBtn.style.display = 'block'; | |
| const cookieInput = document.createElement('textarea'); | |
| cookieInput.placeholder = 'Paste your Netscape-format cookies here'; | |
| cookieInput.style.width = '100%'; | |
| cookieInput.style.height = '100px'; | |
| cookieInput.style.display = 'none'; | |
| cookieInput.addEventListener('input', () => { | |
| state.cookies = cookieInput.value.trim(); | |
| updateDownloadButtonState(); | |
| }); | |
| cookieToggleBtn.addEventListener('click', () => { | |
| cookieInput.style.display = cookieInput.style.display === 'none' ? 'block' : 'none'; | |
| }); | |
| const button = document.createElement('button'); | |
| button.id = 'yt-start-download'; | |
| button.textContent = '📥 Download Video'; | |
| button.style.padding = '6px 12px'; | |
| button.style.background = '#ff0000'; | |
| button.style.color = 'white'; | |
| button.style.border = 'none'; | |
| button.style.borderRadius = '5px'; | |
| button.style.cursor = 'pointer'; | |
| button.disabled = true; | |
| const list = document.createElement('div'); | |
| list.id = 'yt-download-list'; | |
| list.style.marginTop = '10px'; | |
| box.appendChild(cookieToggleBtn); | |
| box.appendChild(cookieInput); | |
| box.appendChild(button); | |
| box.appendChild(list); | |
| container.appendChild(box); | |
| document.body.appendChild(container); | |
| GM_addStyle(` | |
| #yt-download-popup { | |
| position: fixed; | |
| top: 10px; | |
| left: 10px; | |
| z-index: 99999; | |
| } | |
| #yt-download-list .item { | |
| background: #333; | |
| padding: 8px; | |
| border-radius: 6px; | |
| margin-top: 6px; | |
| font-size: 12px; | |
| } | |
| #yt-download-list .item a { | |
| color: #00ffcc; | |
| text-decoration: none; | |
| display: inline-block; | |
| margin-top: 4px; | |
| } | |
| #yt-download-list .item button { | |
| margin-top: 4px; | |
| background: transparent; | |
| color: white; | |
| border: 1px solid #555; | |
| border-radius: 3px; | |
| padding: 2px 6px; | |
| cursor: pointer; | |
| } | |
| `); | |
| button.addEventListener('click', startDownload); | |
| } | |
| function updateDownloadButtonState() { | |
| const btn = document.getElementById('yt-start-download'); | |
| btn.disabled = !state.cookies; | |
| } | |
| function updateList() { | |
| const list = document.getElementById('yt-download-list'); | |
| while (list.firstChild) list.removeChild(list.firstChild); | |
| state.tasks.forEach(task => { | |
| const el = document.createElement('div'); | |
| el.className = 'item'; | |
| const title = document.createElement('div'); | |
| const strong = document.createElement('strong'); | |
| strong.textContent = task.shortUrl; | |
| title.appendChild(strong); | |
| el.appendChild(title); | |
| const status = document.createElement('div'); | |
| status.textContent = `Status: ${task.status}`; | |
| el.appendChild(status); | |
| if (task.status === 'done') { | |
| const a = document.createElement('a'); | |
| a.href = task.downloadUrl; | |
| a.target = '_blank'; | |
| a.download = ''; | |
| a.textContent = '⬇️ Download'; | |
| el.appendChild(a); | |
| } | |
| const remove = document.createElement('button'); | |
| remove.textContent = '❌ Xoá'; | |
| remove.addEventListener('click', () => { | |
| state.tasks = state.tasks.filter(t => t.id !== task.id); | |
| updateList(); | |
| }); | |
| el.appendChild(remove); | |
| list.appendChild(el); | |
| }); | |
| } | |
| function startDownload() { | |
| const url = location.href; | |
| const shortUrl = url.split('?')[0].replace('https://www.', '').replace('youtube.com/watch', 'yt'); | |
| const taskId = Date.now().toString(); | |
| state.tasks.push({ id: taskId, shortUrl, status: 'downloading...', downloadUrl: '' }); | |
| updateList(); | |
| const encodedCookie = btoa(state.cookies); | |
| GM_xmlhttpRequest({ | |
| method: 'POST', | |
| url: `${API_BASE}/download`, | |
| headers: { 'Content-Type': 'application/json' }, | |
| data: JSON.stringify({ url, cookie: encodedCookie }), | |
| onload: (response) => { | |
| try { | |
| const json = JSON.parse(response.responseText); | |
| const task = state.tasks.find(t => t.id === taskId); | |
| if (json.download_url) { | |
| task.status = 'done'; | |
| task.downloadUrl = `${API_BASE}${json.download_url}`; | |
| } else { | |
| task.status = 'error'; | |
| } | |
| } catch (e) { | |
| const task = state.tasks.find(t => t.id === taskId); | |
| if (task) task.status = 'failed'; | |
| } | |
| updateList(); | |
| }, | |
| onerror: () => { | |
| const task = state.tasks.find(t => t.id === taskId); | |
| if (task) task.status = 'error'; | |
| updateList(); | |
| } | |
| }); | |
| } | |
| const waitForReady = setInterval(() => { | |
| if (document.body && !document.getElementById('yt-download-popup')) { | |
| clearInterval(waitForReady); | |
| createUI(); | |
| } | |
| }, 1000); | |
| })(); | |