Spaces:
Running
Running
Update index.html
Browse files- index.html +70 -149
index.html
CHANGED
|
@@ -3,38 +3,34 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>Universal
|
| 7 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 8 |
<style>
|
| 9 |
:root { --primary: #fe2c55; --secondary: #25f4ee; --bg-dark: #161823; --bg-card: #1f1f2e; --bg-hover: #2a2a3e; --text-primary: #ffffff; --text-secondary: #a0a0b0; --border: #2a2a3e; --success: #00ff88; --warning: #ffaa00; --error: #ff3366; --tube5s-accent: #bd93f9; }
|
| 10 |
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 11 |
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: linear-gradient(135deg, #0f0f1e 0%, #1a1a2e 100%); color: var(--text-primary); min-height: 100vh; }
|
| 12 |
-
.container { max-width:
|
| 13 |
header { background: rgba(31, 31, 46, 0.8); backdrop-filter: blur(10px); border-bottom: 1px solid var(--border); padding: 20px 0; position: sticky; top: 0; z-index: 100; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); }
|
| 14 |
-
.header-content { display: flex; justify-content: space-between; align-items: center; max-width:
|
| 15 |
.logo { display: flex; align-items: center; gap: 15px; }
|
| 16 |
.logo-icon { width: 50px; height: 50px; background: linear-gradient(135deg, #fe2c55 0%, #25f4ee 100%); border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 24px; animation: pulse 2s infinite; }
|
| 17 |
h1 { font-size: 1.8rem; font-weight: 700; background: linear-gradient(135deg, #fe2c55 0%, #25f4ee 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
| 18 |
@keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } }
|
| 19 |
-
|
| 20 |
-
.main-content { display: grid; grid-template-columns: 1fr 1fr; gap: 30px; margin-top: 30px; }
|
| 21 |
.card { background: rgba(31, 31, 46, 0.6); backdrop-filter: blur(10px); border: 1px solid var(--border); border-radius: 20px; padding: 30px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); }
|
| 22 |
.card-header { display: flex; align-items: center; gap: 15px; margin-bottom: 25px; }
|
| 23 |
.card-icon { width: 45px; height: 45px; background: linear-gradient(135deg, #fe2c55 0%, #25f4ee 100%); border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 20px; }
|
| 24 |
.card-title { font-size: 1.3rem; font-weight: 600; }
|
| 25 |
-
|
| 26 |
.input-section { margin-bottom: 25px; }
|
| 27 |
.input-label { display: block; margin-bottom: 10px; color: var(--text-secondary); font-weight: 500; }
|
| 28 |
.links-textarea { width: 100%; min-height: 200px; padding: 15px; background: var(--bg-dark); border: 2px solid var(--border); border-radius: 12px; color: var(--text-primary); font-size: 14px; resize: vertical; transition: border-color 0.3s; }
|
| 29 |
.links-textarea:focus { outline: none; border-color: var(--primary); }
|
| 30 |
-
|
| 31 |
.url-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
|
| 32 |
.url-chip { background: var(--bg-hover); padding: 6px 12px; border-radius: 20px; font-size: 12px; color: var(--text-secondary); display: flex; align-items: center; gap: 8px; animation: fadeIn 0.3s; border: 1px solid transparent; }
|
| 33 |
.url-chip.type-tube5s { border-color: var(--tube5s-accent); color: var(--tube5s-accent); background: rgba(189, 147, 249, 0.1); }
|
| 34 |
@keyframes fadeIn { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } }
|
| 35 |
.url-chip .remove { cursor: pointer; color: var(--error); transition: transform 0.2s; }
|
| 36 |
.url-chip .remove:hover { transform: scale(1.2); }
|
| 37 |
-
|
| 38 |
.action-buttons { display: flex; gap: 15px; margin-top: 20px; }
|
| 39 |
.btn { flex: 1; padding: 14px 24px; border: none; border-radius: 10px; font-size: 1rem; font-weight: 600; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; justify-content: center; gap: 10px; }
|
| 40 |
.btn-primary { background: linear-gradient(135deg, #fe2c55 0%, #25f4ee 100%); color: white; }
|
|
@@ -42,49 +38,27 @@
|
|
| 42 |
.btn-secondary { background: var(--bg-hover); color: var(--text-primary); }
|
| 43 |
.btn-secondary:hover:not(:disabled) { background: #3a3a4e; }
|
| 44 |
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
| 45 |
-
|
| 46 |
.progress-section { margin-top: 30px; display: none; }
|
| 47 |
.progress-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
|
| 48 |
.progress-bar { width: 100%; height: 8px; background: var(--bg-dark); border-radius: 10px; overflow: hidden; margin-bottom: 20px; }
|
| 49 |
.progress-fill { height: 100%; background: linear-gradient(135deg, #fe2c55 0%, #25f4ee 100%); border-radius: 10px; transition: width 0.3s ease; width: 0%; }
|
| 50 |
-
|
| 51 |
.downloads-list { max-height: 400px; overflow-y: auto; margin-top: 20px; }
|
| 52 |
.download-item { background: var(--bg-dark); border: 1px solid var(--border); border-radius: 12px; padding: 15px; margin-bottom: 15px; display: flex; align-items: center; gap: 15px; transition: all 0.3s; }
|
| 53 |
.download-item:hover { border-color: var(--primary); transform: translateX(5px); }
|
| 54 |
.download-status { width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
| 55 |
.status-pending { background: rgba(255, 170, 0, 0.2); color: var(--warning); }
|
| 56 |
.status-resolving { background: rgba(189, 147, 249, 0.2); color: var(--tube5s-accent); animation: spin 1s linear infinite; }
|
| 57 |
-
.status-downloading { background: rgba(37, 244, 238, 0.2); color: var(--secondary);
|
| 58 |
.status-success { background: rgba(0, 255, 136, 0.2); color: var(--success); }
|
| 59 |
.status-error { background: rgba(255, 51, 102, 0.2); color: var(--error); }
|
| 60 |
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
| 61 |
.download-info { flex: 1; }
|
| 62 |
.download-url { font-size: 0.9rem; color: var(--text-secondary); margin-bottom: 5px; word-break: break-all; }
|
| 63 |
.file-name { color: var(--primary); font-weight: 600; margin-top: 5px; }
|
| 64 |
-
|
| 65 |
-
.
|
| 66 |
-
.tube5s-info { background: rgba(189, 147, 249, 0.1); border: 1px solid var(--tube5s-accent); }
|
| 67 |
-
.tube5s-info h3 { color: var(--tube5s-accent); margin-bottom: 10px; }
|
| 68 |
-
.method-info { background: rgba(37, 244, 238, 0.1); border: 1px solid var(--secondary); }
|
| 69 |
-
.method-info h3 { color: var(--secondary); margin-bottom: 10px; }
|
| 70 |
-
|
| 71 |
-
.important-notice {
|
| 72 |
-
color: var(--error);
|
| 73 |
-
border: 1px solid var(--error);
|
| 74 |
-
background: rgba(255, 51, 102, 0.1);
|
| 75 |
-
padding: 15px;
|
| 76 |
-
border-radius: 12px;
|
| 77 |
-
margin-top: 20px;
|
| 78 |
-
}
|
| 79 |
-
.important-notice strong {
|
| 80 |
-
font-weight: bold;
|
| 81 |
-
color: var(--warning);
|
| 82 |
-
}
|
| 83 |
-
|
| 84 |
.notification { position: fixed; bottom: 20px; right: 20px; background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 15px 20px; display: flex; align-items: center; gap: 15px; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3); transform: translateX(400px); transition: transform 0.3s; z-index: 1000; }
|
| 85 |
.notification.show { transform: translateX(0); }
|
| 86 |
-
|
| 87 |
-
@media (max-width: 968px) { .main-content { grid-template-columns: 1fr; } }
|
| 88 |
</style>
|
| 89 |
</head>
|
| 90 |
<body>
|
|
@@ -98,163 +72,110 @@
|
|
| 98 |
</header>
|
| 99 |
|
| 100 |
<div class="container">
|
| 101 |
-
<div class="
|
| 102 |
-
<div class="card">
|
| 103 |
-
<div class="card-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
</div>
|
| 107 |
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
</div>
|
| 127 |
</div>
|
| 128 |
-
<div class="progress-bar"><div class="progress-fill" id="progressFill"></div></div>
|
| 129 |
-
<div class="downloads-list" id="downloadsList"></div>
|
| 130 |
</div>
|
|
|
|
|
|
|
| 131 |
</div>
|
| 132 |
|
| 133 |
-
<div class="
|
| 134 |
-
<
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
<
|
| 140 |
-
<h3><i class="fas fa-exclamation-triangle"></i> MUITO IMPORTANTE!</h3>
|
| 141 |
-
<p>Para evitar erros, <strong>não abra o arquivo clicando duas vezes</strong>. Você precisa de um servidor local.</p>
|
| 142 |
-
<ol>
|
| 143 |
-
<li><strong>Instale o Python</strong> (se não tiver): <a href="https://www.python.org/downloads/" style="color: var(--primary);">python.org</a></li>
|
| 144 |
-
<li><strong>Abra o terminal/cmd</strong> na pasta onde salvou o arquivo.</li>
|
| 145 |
-
<li><strong>Execute o comando:</strong> <code>python -m http.server</code></li>
|
| 146 |
-
<li><strong>Abra no navegador:</strong> <a href="http://localhost:8000" target="_blank" style="color: var(--primary);">http://localhost:8000</a></li>
|
| 147 |
-
</ol>
|
| 148 |
-
</div>
|
| 149 |
-
|
| 150 |
-
<div class="tube5s-info">
|
| 151 |
-
<h3><i class="fas fa-bolt"></i> Modo Tube5s</h3>
|
| 152 |
-
<p>Lê a URL corrompida, corrige, segue o redirecionamento com um User-Agent de navegador real e baixa o vídeo final.</p>
|
| 153 |
-
</div>
|
| 154 |
-
<div class="method-info">
|
| 155 |
-
<h3><i class="fas fa-rocket"></i> Modo SnapDouyin</h3>
|
| 156 |
-
<p>Para links do Douyin, acessa a API para encontrar a maior qualidade disponível (sem marca d'água).</p>
|
| 157 |
-
</div>
|
| 158 |
</div>
|
| 159 |
</div>
|
| 160 |
</div>
|
| 161 |
|
| 162 |
-
<div class="notification" id="notification">
|
| 163 |
-
<div class="notification-icon" id="notifIcon"><i class="fas fa-check"></i></div>
|
| 164 |
-
<div class="notification-content">
|
| 165 |
-
<div id="notificationText" style="font-weight: 600;"></div>
|
| 166 |
-
</div>
|
| 167 |
-
</div>
|
| 168 |
|
| 169 |
<script>
|
| 170 |
document.addEventListener('DOMContentLoaded', () => {
|
| 171 |
-
let downloadQueue = [];
|
| 172 |
-
|
| 173 |
-
const linksInput = document.getElementById('linksInput');
|
| 174 |
-
const urlChips = document.getElementById('urlChips');
|
| 175 |
const startBtn = document.getElementById('startBtn'); const clearBtn = document.getElementById('clearBtn');
|
| 176 |
const progressSection = document.getElementById('progressSection'); const downloadsList = document.getElementById('downloadsList');
|
| 177 |
-
const progressFill = document.getElementById('progressFill');
|
| 178 |
-
const
|
| 179 |
-
|
| 180 |
-
function getLinkType(url) {
|
| 181 |
-
|
| 182 |
-
if (url.includes('douyin.com') || url.includes('v.douyin.com')) return 'douyin';
|
| 183 |
-
return 'unknown';
|
| 184 |
-
}
|
| 185 |
-
function showNotification(msg, type) {
|
| 186 |
-
notificationText.textContent = msg; notification.className = `notification ${type}`;
|
| 187 |
-
notifIcon.innerHTML = type === 'success' ? '<i class="fas fa-check" style="color:var(--success)"></i>' : '<i class="fas fa-times" style="color:var(--error)"></i>';
|
| 188 |
-
notification.style.transform = 'translateX(0)'; setTimeout(() => { notification.style.transform = 'translateX(400px)'; }, 4000);
|
| 189 |
-
}
|
| 190 |
function setStatus(index, status, text) {
|
| 191 |
const el = document.getElementById(`status-${index}`); const filename = document.getElementById(`filename-${index}`);
|
| 192 |
-
if (!el) return;if (filename) filename.textContent = text;
|
| 193 |
el.classList.remove('status-pending', 'status-resolving', 'status-downloading', 'status-success', 'status-error');
|
| 194 |
-
|
|
|
|
| 195 |
}
|
| 196 |
-
|
| 197 |
async function resolveTube5S(originalUrl) {
|
| 198 |
try {
|
| 199 |
let cleanUrl = originalUrl.replace('https://https//', 'https://').replace('http://https//', 'https://');
|
| 200 |
-
console.log(`[Tube5s] Tentando URL limpa: ${cleanUrl}`);
|
| 201 |
-
|
| 202 |
-
// --- REFORÇO: User-Agent de navegador real ---
|
| 203 |
const userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36";
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
redirect: 'follow',
|
| 208 |
-
headers: {
|
| 209 |
-
'User-Agent': userAgent
|
| 210 |
-
}
|
| 211 |
-
});
|
| 212 |
-
|
| 213 |
-
// --- REFORÇO: Melhor detecção de erros ---
|
| 214 |
-
if (response.type === 'opaque') {
|
| 215 |
-
console.error("Resposta Opaca. Muita chance de ser CORS. Verifique como está rodando o HTML.");
|
| 216 |
-
throw new Error("Erro de CORS ou Rede. Você está rodando via servidor local (python -m http.server)?");
|
| 217 |
-
}
|
| 218 |
-
if (!response.ok) {
|
| 219 |
-
let errorText = `Erro HTTP: ${response.status} ${response.statusText}`;
|
| 220 |
-
try { errorText += ` - ${(await response.text()).substring(0, 100)}`; } catch(e) {}
|
| 221 |
-
throw new Error(errorText);
|
| 222 |
-
}
|
| 223 |
-
|
| 224 |
const finalUrl = response.url;
|
| 225 |
-
if (finalUrl === cleanUrl) throw new Error('
|
| 226 |
-
|
| 227 |
return { success: true, downloadUrl: finalUrl, quality: 'Native (V3)', size: 0, type: 'tube5s' };
|
| 228 |
-
} catch (error) {
|
| 229 |
-
console.error('[Tube5s] Falha:', error);
|
| 230 |
-
return { success: false, error: error.message };
|
| 231 |
-
}
|
| 232 |
}
|
| 233 |
-
|
| 234 |
-
async function resolveDouyin(url) { /* Função Douglasin sem alterações */ /* ... same as before ... */ try { let targetUrl = url.split('?')[0]; const vid = new URLSearchParams(url.split('?')[1]).get('modal_id'); if (vid) targetUrl = `https://www.douyin.com/video/${vid}`; const timestamp = Date.now(); const hash = btoa(targetUrl + timestamp) + (targetUrl.length + 1000) + btoa('aio-dl'); const formData = `url=${encodeURIComponent(targetUrl)}&hash=${hash}&t=${timestamp}`; const response = await fetch('https://snapdouyin.app/wp-json/mx-downloader/video-data/', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: formData }); const data = await response.json(); if (data.medias && data.medias.length > 0) { data.medias.sort((a, b) => (b.size || 0) - (a.size || 0)); const best = data.medias[0]; return { success: true, downloadUrl: best.url, quality: best.formattedSize || 'HD', size: best.size || 0, type: 'douyin' }; } else { throw new Error("Mídia não encontrada pela API."); } } catch (e) { let msg = e.message; if(e.message.includes("Failed to fetch")) msg = "Falha ao conectar na API do Douyin. Pode estar offline."; return { success: false, error: msg }; } }
|
| 235 |
|
| 236 |
-
function parseLinks() {
|
| 237 |
-
|
| 238 |
function clearLinks() { linksInput.value = ''; urlChips.innerHTML = ''; downloadsList.innerHTML = ''; progressSection.style.display = 'none'; processedCount = 0; successCount = 0; errorCount = 0; processingCount = 0; updateStats(); }
|
| 239 |
-
function updateStats() { document.getElementById('successCount').innerText = successCount; document.getElementById('processingCount').innerText = processingCount; document.getElementById('errorCount').innerText = errorCount; const
|
| 240 |
-
function createItem(url, index) {
|
| 241 |
function downloadFile(url, filename) { const a = document.createElement('a'); a.href = url; a.download = filename; a.target = '_blank'; document.body.appendChild(a); a.click(); document.body.removeChild(a); }
|
| 242 |
|
| 243 |
async function processQueue() {
|
| 244 |
const urls = parseLinks(); if (urls.length === 0) { showNotification('Cole pelo menos um link!', 'error'); return; }
|
| 245 |
downloadQueue = urls; processedCount = 0; successCount = 0; errorCount = 0; processingCount = 0;
|
| 246 |
downloadsList.innerHTML = ''; progressSection.style.display = 'block'; startBtn.disabled = true;
|
| 247 |
-
urls.forEach((url, i) =>
|
| 248 |
for (let i = 0; i < urls.length; i++) {
|
| 249 |
const url = urls[i]; const type = getLinkType(url); processingCount++; updateStats();
|
| 250 |
-
try {
|
| 251 |
-
|
| 252 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
processingCount--; processedCount++; updateStats();
|
| 254 |
}
|
| 255 |
startBtn.disabled = false; showNotification('Processamento concluído!', 'success');
|
| 256 |
}
|
| 257 |
-
|
| 258 |
startBtn.addEventListener('click', processQueue);
|
| 259 |
clearBtn.addEventListener('click', clearLinks);
|
| 260 |
linksInput.addEventListener('input', parseLinks);
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Universal Downloader</title>
|
| 7 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 8 |
<style>
|
| 9 |
:root { --primary: #fe2c55; --secondary: #25f4ee; --bg-dark: #161823; --bg-card: #1f1f2e; --bg-hover: #2a2a3e; --text-primary: #ffffff; --text-secondary: #a0a0b0; --border: #2a2a3e; --success: #00ff88; --warning: #ffaa00; --error: #ff3366; --tube5s-accent: #bd93f9; }
|
| 10 |
* { margin: 0; padding: 0; box-sizing: border-box; }
|
| 11 |
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: linear-gradient(135deg, #0f0f1e 0%, #1a1a2e 100%); color: var(--text-primary); min-height: 100vh; }
|
| 12 |
+
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }
|
| 13 |
header { background: rgba(31, 31, 46, 0.8); backdrop-filter: blur(10px); border-bottom: 1px solid var(--border); padding: 20px 0; position: sticky; top: 0; z-index: 100; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); }
|
| 14 |
+
.header-content { display: flex; justify-content: space-between; align-items: center; max-width: 1200px; margin: 0 auto; padding: 0 20px; }
|
| 15 |
.logo { display: flex; align-items: center; gap: 15px; }
|
| 16 |
.logo-icon { width: 50px; height: 50px; background: linear-gradient(135deg, #fe2c55 0%, #25f4ee 100%); border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 24px; animation: pulse 2s infinite; }
|
| 17 |
h1 { font-size: 1.8rem; font-weight: 700; background: linear-gradient(135deg, #fe2c55 0%, #25f4ee 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
| 18 |
@keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } }
|
| 19 |
+
.main-content { display: grid; grid-template-columns: 1fr; gap: 30px; margin-top: 30px; }
|
|
|
|
| 20 |
.card { background: rgba(31, 31, 46, 0.6); backdrop-filter: blur(10px); border: 1px solid var(--border); border-radius: 20px; padding: 30px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); }
|
| 21 |
.card-header { display: flex; align-items: center; gap: 15px; margin-bottom: 25px; }
|
| 22 |
.card-icon { width: 45px; height: 45px; background: linear-gradient(135deg, #fe2c55 0%, #25f4ee 100%); border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 20px; }
|
| 23 |
.card-title { font-size: 1.3rem; font-weight: 600; }
|
|
|
|
| 24 |
.input-section { margin-bottom: 25px; }
|
| 25 |
.input-label { display: block; margin-bottom: 10px; color: var(--text-secondary); font-weight: 500; }
|
| 26 |
.links-textarea { width: 100%; min-height: 200px; padding: 15px; background: var(--bg-dark); border: 2px solid var(--border); border-radius: 12px; color: var(--text-primary); font-size: 14px; resize: vertical; transition: border-color 0.3s; }
|
| 27 |
.links-textarea:focus { outline: none; border-color: var(--primary); }
|
|
|
|
| 28 |
.url-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
|
| 29 |
.url-chip { background: var(--bg-hover); padding: 6px 12px; border-radius: 20px; font-size: 12px; color: var(--text-secondary); display: flex; align-items: center; gap: 8px; animation: fadeIn 0.3s; border: 1px solid transparent; }
|
| 30 |
.url-chip.type-tube5s { border-color: var(--tube5s-accent); color: var(--tube5s-accent); background: rgba(189, 147, 249, 0.1); }
|
| 31 |
@keyframes fadeIn { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } }
|
| 32 |
.url-chip .remove { cursor: pointer; color: var(--error); transition: transform 0.2s; }
|
| 33 |
.url-chip .remove:hover { transform: scale(1.2); }
|
|
|
|
| 34 |
.action-buttons { display: flex; gap: 15px; margin-top: 20px; }
|
| 35 |
.btn { flex: 1; padding: 14px 24px; border: none; border-radius: 10px; font-size: 1rem; font-weight: 600; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; justify-content: center; gap: 10px; }
|
| 36 |
.btn-primary { background: linear-gradient(135deg, #fe2c55 0%, #25f4ee 100%); color: white; }
|
|
|
|
| 38 |
.btn-secondary { background: var(--bg-hover); color: var(--text-primary); }
|
| 39 |
.btn-secondary:hover:not(:disabled) { background: #3a3a4e; }
|
| 40 |
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
|
|
| 41 |
.progress-section { margin-top: 30px; display: none; }
|
| 42 |
.progress-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
|
| 43 |
.progress-bar { width: 100%; height: 8px; background: var(--bg-dark); border-radius: 10px; overflow: hidden; margin-bottom: 20px; }
|
| 44 |
.progress-fill { height: 100%; background: linear-gradient(135deg, #fe2c55 0%, #25f4ee 100%); border-radius: 10px; transition: width 0.3s ease; width: 0%; }
|
|
|
|
| 45 |
.downloads-list { max-height: 400px; overflow-y: auto; margin-top: 20px; }
|
| 46 |
.download-item { background: var(--bg-dark); border: 1px solid var(--border); border-radius: 12px; padding: 15px; margin-bottom: 15px; display: flex; align-items: center; gap: 15px; transition: all 0.3s; }
|
| 47 |
.download-item:hover { border-color: var(--primary); transform: translateX(5px); }
|
| 48 |
.download-status { width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
| 49 |
.status-pending { background: rgba(255, 170, 0, 0.2); color: var(--warning); }
|
| 50 |
.status-resolving { background: rgba(189, 147, 249, 0.2); color: var(--tube5s-accent); animation: spin 1s linear infinite; }
|
| 51 |
+
.status-downloading { background: rgba(37, 244, 238, 0.2); color: var(--secondary); }
|
| 52 |
.status-success { background: rgba(0, 255, 136, 0.2); color: var(--success); }
|
| 53 |
.status-error { background: rgba(255, 51, 102, 0.2); color: var(--error); }
|
| 54 |
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
| 55 |
.download-info { flex: 1; }
|
| 56 |
.download-url { font-size: 0.9rem; color: var(--text-secondary); margin-bottom: 5px; word-break: break-all; }
|
| 57 |
.file-name { color: var(--primary); font-weight: 600; margin-top: 5px; }
|
| 58 |
+
.important-notice { color: var(--error); border: 1px solid var(--error); background: rgba(255, 51, 102, 0.1); padding: 20px; border-radius: 12px; margin-top: 20px; }
|
| 59 |
+
.important-notice strong { font-weight: bold; color: var(--warning); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
.notification { position: fixed; bottom: 20px; right: 20px; background: var(--bg-card); border: 1px solid var(--border); border-radius: 12px; padding: 15px 20px; display: flex; align-items: center; gap: 15px; box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3); transform: translateX(400px); transition: transform 0.3s; z-index: 1000; }
|
| 61 |
.notification.show { transform: translateX(0); }
|
|
|
|
|
|
|
| 62 |
</style>
|
| 63 |
</head>
|
| 64 |
<body>
|
|
|
|
| 72 |
</header>
|
| 73 |
|
| 74 |
<div class="container">
|
| 75 |
+
<div class="card">
|
| 76 |
+
<div class="card-header">
|
| 77 |
+
<div class="card-icon"><i class="fas fa-link"></i></div>
|
| 78 |
+
<div class="card-title">Adicionar Links</div>
|
| 79 |
+
</div>
|
|
|
|
| 80 |
|
| 81 |
+
<div class="input-section">
|
| 82 |
+
<label class="input-label">Cole os links (Tube5s ou Douyin):</label>
|
| 83 |
+
<textarea id="linksInput" class="links-textarea" placeholder="Cole aqui Links https://https//svX.tube5s.com... OU Links do Douyin"></textarea>
|
| 84 |
+
<div id="urlChips" class="url-chips"></div>
|
| 85 |
+
</div>
|
| 86 |
|
| 87 |
+
<div class="action-buttons">
|
| 88 |
+
<button class="btn btn-primary" id="startBtn"><i class="fas fa-play"></i> Processar Links</button>
|
| 89 |
+
<button class="btn btn-secondary" id="clearBtn"><i class="fas fa-trash"></i> Limpar</button>
|
| 90 |
+
</div>
|
| 91 |
|
| 92 |
+
<div class="progress-section" id="progressSection">
|
| 93 |
+
<div class="progress-header">
|
| 94 |
+
<div class="progress-title">Progresso</div>
|
| 95 |
+
<div style="display: flex; gap: 15px; font-size: 0.9rem;">
|
| 96 |
+
<span style="color: var(--success)">✅ <span id="successCount">0</span></span>
|
| 97 |
+
<span style="color: var(--secondary)">⏳ <span id="processingCount">0</span></span>
|
| 98 |
+
<span style="color: var(--error)">❌ <span id="errorCount">0</span></span>
|
|
|
|
| 99 |
</div>
|
|
|
|
|
|
|
| 100 |
</div>
|
| 101 |
+
<div class="progress-bar"><div class="progress-fill" id="progressFill"></div></div>
|
| 102 |
+
<div class="downloads-list" id="downloadsList"></div>
|
| 103 |
</div>
|
| 104 |
|
| 105 |
+
<div class="important-notice">
|
| 106 |
+
<h3><i class="fas fa-exclamation-triangle"></i> ATENÇÃO: Para Funcionar</h3>
|
| 107 |
+
<p><strong>NÃO clique duas vezes no arquivo para abrir.</strong> O navegador vai bloquear o download. Você precisa de um "servidor local". Use um dos métodos abaixo para abrir a página corretamente.</p>
|
| 108 |
+
<ol>
|
| 109 |
+
<li><strong>Método Python (Recomendado):</strong> Abra o terminal/prompt na pasta do arquivo e digite: <code>python -m http.server</code>. Depois acesse <a href="http://localhost:8000" target="_blank" style="color: var(--primary);">http://localhost:8000</a>.</li>
|
| 110 |
+
<li><strong>Método VS Code (Mais fácil visualmente):</strong> Abra a pasta no VS Code, instale a extensão "Live Server", clique com o direito no arquivo e escolha "Open with Live Server".</li>
|
| 111 |
+
</ol>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
</div>
|
| 113 |
</div>
|
| 114 |
</div>
|
| 115 |
|
| 116 |
+
<div class="notification" id="notification"><div id="notificationText" style="font-weight: 600;"></div></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
<script>
|
| 119 |
document.addEventListener('DOMContentLoaded', () => {
|
| 120 |
+
let downloadQueue = []; let processedCount = 0; let successCount = 0; let processingCount = 0; let errorCount = 0;
|
| 121 |
+
const linksInput = document.getElementById('linksInput'); const urlChips = document.getElementById('urlChips');
|
|
|
|
|
|
|
| 122 |
const startBtn = document.getElementById('startBtn'); const clearBtn = document.getElementById('clearBtn');
|
| 123 |
const progressSection = document.getElementById('progressSection'); const downloadsList = document.getElementById('downloadsList');
|
| 124 |
+
const progressFill = document.getElementById('progressFill');
|
| 125 |
+
const notification = document.getElementById('notification'); const notificationText = document.getElementById('notificationText');
|
| 126 |
+
|
| 127 |
+
function getLinkType(url) { if (url.includes('tube5s.com') || url.includes('sv5.') || url.includes('v3.')) return 'tube5s'; if (url.includes('douyin.com')) return 'douyin'; return 'unknown'; }
|
| 128 |
+
function showNotification(msg, type) { notificationText.textContent = msg; notification.style.border = `1px solid var(--${type})`; notification.style.background = `rgba(${type === 'error' ? '255, 51, 102' : '0, 255, 136'}, 0.1)`; notification.style.color = `var(--${type})`; notification.classList.add('show'); setTimeout(() => notification.classList.remove('show'), 4000); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
function setStatus(index, status, text) {
|
| 130 |
const el = document.getElementById(`status-${index}`); const filename = document.getElementById(`filename-${index}`);
|
| 131 |
+
if (!el) return; if (filename) filename.textContent = text;
|
| 132 |
el.classList.remove('status-pending', 'status-resolving', 'status-downloading', 'status-success', 'status-error');
|
| 133 |
+
el.innerHTML = status === 'resolving' ? '<i class="fas fa-bolt"></i>' : status === 'downloading' ? '<i class="fas fa-download"></i>' : status === 'success' ? '<i class="fas fa-check"></i>' : '<i class="fas fa-times"></i>';
|
| 134 |
+
el.className = `download-status status-${status}`;
|
| 135 |
}
|
|
|
|
| 136 |
async function resolveTube5S(originalUrl) {
|
| 137 |
try {
|
| 138 |
let cleanUrl = originalUrl.replace('https://https//', 'https://').replace('http://https//', 'https://');
|
|
|
|
|
|
|
|
|
|
| 139 |
const userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36";
|
| 140 |
+
const response = await fetch(cleanUrl, { method: 'GET', redirect: 'follow', headers: { 'User-Agent': userAgent } });
|
| 141 |
+
if (response.type === 'opaque') throw new Error("ERRO DE CORS/SERVIDOR: Abra a página via 'python -m http.server' ou 'Live Server' no VS Code, não clicando duas vezes no arquivo.");
|
| 142 |
+
if (!response.ok) throw new Error(`Erro do servidor: ${response.status}`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
const finalUrl = response.url;
|
| 144 |
+
if (finalUrl === cleanUrl) throw new Error('Redirecionamento falhou. Link inválido?');
|
|
|
|
| 145 |
return { success: true, downloadUrl: finalUrl, quality: 'Native (V3)', size: 0, type: 'tube5s' };
|
| 146 |
+
} catch (error) { console.error(error); return { success: false, error: error.message }; }
|
|
|
|
|
|
|
|
|
|
| 147 |
}
|
| 148 |
+
async function resolveDouyin(url) { /* Simplificada */ try { const response = await fetch('https://snapdouyin.app/wp-json/mx-downloader/video-data/', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: `url=${encodeURIComponent(url)}&hash=...&t=${Date.now()}` }); const data = await response.json(); if(data.medias && data.medias.length > 0) { const best = data.medias.sort((a,b)=> (b.size||0)-(a.size||0))[0]; return { success: true, downloadUrl: best.url, quality: best.formattedSize || 'HD', type: 'douyin' }; } throw new Error("API do Douyin não encontrou vídeo."); } catch (e) { return { success: false, error: 'Falha na API do Douyin. ' + e.message }; } }
|
|
|
|
| 149 |
|
| 150 |
+
function parseLinks() { const urls = linksInput.value.split('\n').map(l => l.trim()).filter(l => l); urlChips.innerHTML = ''; urls.forEach((url, index) => { const type = getLinkType(url); const chip = document.createElement('div'); chip.className = `url-chip ${type === 'tube5s' ? 'type-tube5s' : ''}`; chip.innerHTML = `${type === 'tube5s' ? '<i class="fas fa-bolt"></i>' : '<i class="fab fa-tiktok"></i>'}<span>${url.substring(0, 30)}...</span><i class="fas fa-times remove" onclick="removeLine(${index})"></i>`; urlChips.appendChild(chip); }); return urls; }
|
| 151 |
+
window.removeLine = function(index) { const lines = linksInput.value.split('\n'); lines.splice(index,1); linksInput.value = lines.join('\n'); parseLinks(); };
|
| 152 |
function clearLinks() { linksInput.value = ''; urlChips.innerHTML = ''; downloadsList.innerHTML = ''; progressSection.style.display = 'none'; processedCount = 0; successCount = 0; errorCount = 0; processingCount = 0; updateStats(); }
|
| 153 |
+
function updateStats() { document.getElementById('successCount').innerText = successCount; document.getElementById('processingCount').innerText = processingCount; document.getElementById('errorCount').innerText = errorCount; const progress = downloadQueue.length === 0 ? 0 : (processedCount / downloadQueue.length) * 100; progressFill.style.width = `${progress}%`; }
|
| 154 |
+
function createItem(url, index) { const div = document.createElement('div'); div.className = 'download-item'; div.innerHTML = `<div class="download-status status-pending" id="status-${index}"><i class="fas fa-clock"></i></div><div class="download-info"><div class="download-url">${url}</div><div class="file-name" id="filename-${index}">Aguardando...</div><div>Detectado: ${getLinkType(url).toUpperCase()}</div></div>`; return div; }
|
| 155 |
function downloadFile(url, filename) { const a = document.createElement('a'); a.href = url; a.download = filename; a.target = '_blank'; document.body.appendChild(a); a.click(); document.body.removeChild(a); }
|
| 156 |
|
| 157 |
async function processQueue() {
|
| 158 |
const urls = parseLinks(); if (urls.length === 0) { showNotification('Cole pelo menos um link!', 'error'); return; }
|
| 159 |
downloadQueue = urls; processedCount = 0; successCount = 0; errorCount = 0; processingCount = 0;
|
| 160 |
downloadsList.innerHTML = ''; progressSection.style.display = 'block'; startBtn.disabled = true;
|
| 161 |
+
urls.forEach((url, i) => downloadsList.appendChild(createItem(url, i)));
|
| 162 |
for (let i = 0; i < urls.length; i++) {
|
| 163 |
const url = urls[i]; const type = getLinkType(url); processingCount++; updateStats();
|
| 164 |
+
try {
|
| 165 |
+
let result;
|
| 166 |
+
if (type === 'tube5s') { setStatus(i, 'resolving', 'Resolvendo redirect...'); result = await resolveTube5S(url); }
|
| 167 |
+
else { setStatus(i, 'resolving', 'Processando API Douyin...'); result = await resolveDouyin(url); }
|
| 168 |
+
if (result.success) {
|
| 169 |
+
setStatus(i, 'downloading', 'Baixando...');
|
| 170 |
+
downloadFile(result.downloadUrl, `${type}_${i}.mp4`);
|
| 171 |
+
setStatus(i, 'success', `Concluído!`);
|
| 172 |
+
successCount++;
|
| 173 |
+
} else { throw new Error(result.error); }
|
| 174 |
+
} catch (err) { setStatus(i, 'error', `Falha: ${err.message}`); showNotification(`Falha em ${type}: ${err.message}`, 'error'); errorCount++; }
|
| 175 |
processingCount--; processedCount++; updateStats();
|
| 176 |
}
|
| 177 |
startBtn.disabled = false; showNotification('Processamento concluído!', 'success');
|
| 178 |
}
|
|
|
|
| 179 |
startBtn.addEventListener('click', processQueue);
|
| 180 |
clearBtn.addEventListener('click', clearLinks);
|
| 181 |
linksInput.addEventListener('input', parseLinks);
|