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