Spaces:
Running
Running
| <html lang="pt-BR"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Douyin V3 Link Extractor - CDN Direta</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'); | |
| * { | |
| font-family: 'Inter', sans-serif; | |
| } | |
| .animated-bg { | |
| background: linear-gradient(-45deg, #667eea, #764ba2, #f093fb, #f5576c); | |
| background-size: 400% 400%; | |
| animation: gradient 15s ease infinite; | |
| } | |
| @keyframes gradient { | |
| 0% { | |
| background-position: 0% 50%; | |
| } | |
| 50% { | |
| background-position: 100% 50%; | |
| } | |
| 100% { | |
| background-position: 0% 50%; | |
| } | |
| } | |
| .glass-card { | |
| background: rgba(255, 255, 255, 0.95); | |
| backdrop-filter: blur(20px); | |
| border: 1px solid rgba(255, 255, 255, 0.3); | |
| box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15); | |
| } | |
| .url-line { | |
| animation: slideIn 0.3s ease-out; | |
| } | |
| @keyframes slideIn { | |
| from { | |
| opacity: 0; | |
| transform: translateX(-20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateX(0); | |
| } | |
| } | |
| .process-animation { | |
| animation: processPulse 1.5s ease-in-out infinite; | |
| } | |
| @keyframes processPulse { | |
| 0%, | |
| 100% { | |
| opacity: 1; | |
| transform: scale(1); | |
| } | |
| 50% { | |
| opacity: 0.6; | |
| transform: scale(1.05); | |
| } | |
| } | |
| .result-item { | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| .result-item:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 15px 35px -5px rgba(102, 126, 234, 0.3); | |
| } | |
| .btn-primary { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| transition: all 0.3s ease; | |
| } | |
| .btn-primary:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4); | |
| } | |
| .success-badge { | |
| background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); | |
| color: white; | |
| padding: 4px 12px; | |
| border-radius: 20px; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| } | |
| .error-badge { | |
| background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%); | |
| color: white; | |
| padding: 4px 12px; | |
| border-radius: 20px; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar { | |
| width: 10px; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-track { | |
| background: #f1f1f1; | |
| border-radius: 10px; | |
| } | |
| .custom-scrollbar::-webkit-scrollbar-thumb { | |
| background: linear-gradient(135deg, #667eea, #764ba2); | |
| border-radius: 10px; | |
| } | |
| .floating { | |
| animation: float 3s ease-in-out infinite; | |
| } | |
| @keyframes float { | |
| 0%, | |
| 100% { | |
| transform: translateY(0px); | |
| } | |
| 50% { | |
| transform: translateY(-10px); | |
| } | |
| } | |
| .script-view { | |
| background: #1e1e1e; | |
| color: #d4d4d4; | |
| font-family: 'Courier New', monospace; | |
| font-size: 0.85rem; | |
| line-height: 1.5; | |
| } | |
| .keyword { | |
| color: #569cd6; | |
| } | |
| .string { | |
| color: #ce9178; | |
| } | |
| .function { | |
| color: #dcdcaa; | |
| } | |
| .comment { | |
| color: #6a9955; | |
| } | |
| .number { | |
| color: #b5cea8; | |
| } | |
| .v3-badge { | |
| background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); | |
| color: white; | |
| padding: 2px 8px; | |
| border-radius: 12px; | |
| font-size: 0.7rem; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| .direct-link { | |
| background: linear-gradient(135deg, #667eea15 0%, #764ba215 100%); | |
| border: 2px solid transparent; | |
| background-clip: padding-box; | |
| position: relative; | |
| } | |
| .direct-link::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| z-index: -1; | |
| margin: -2px; | |
| border-radius: inherit; | |
| background: linear-gradient(135deg, #667eea, #764ba2); | |
| } | |
| .result-number { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| width: 32px; | |
| height: 32px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| border-radius: 50%; | |
| font-weight: bold; | |
| font-size: 14px; | |
| flex-shrink: 0; | |
| } | |
| .console-log { | |
| font-family: 'Courier New', monospace; | |
| background: #2d2d2d; | |
| color: #00ff00; | |
| padding: 8px 12px; | |
| border-radius: 6px; | |
| font-size: 0.85rem; | |
| margin: 4px 0; | |
| border-left: 3px solid #00ff00; | |
| } | |
| .console-error { | |
| color: #ff6b6b; | |
| border-left-color: #ff6b6b; | |
| } | |
| .console-success { | |
| color: #00ff00; | |
| border-left-color: #00ff00; | |
| } | |
| .console-info { | |
| color: #17a2b8; | |
| border-left-color: #17a2b8; | |
| } | |
| .download-btn { | |
| background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); | |
| transition: all 0.3s ease; | |
| } | |
| .download-btn:hover { | |
| transform: scale(1.05); | |
| box-shadow: 0 5px 15px rgba(17, 153, 142, 0.4); | |
| } | |
| .video-info { | |
| background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); | |
| border-radius: 12px; | |
| padding: 16px; | |
| } | |
| .v3-url { | |
| background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%); | |
| color: #00ff00; | |
| border-left: 4px solid #f5576c; | |
| padding: 12px; | |
| border-radius: 8px; | |
| font-family: 'Courier New', monospace; | |
| font-size: 0.85rem; | |
| word-break: break-all; | |
| max-height: 100px; | |
| overflow-y: auto; | |
| } | |
| .cdn-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| padding: 4px 10px; | |
| background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); | |
| color: white; | |
| border-radius: 20px; | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| } | |
| .copy-btn { | |
| transition: all 0.3s ease; | |
| } | |
| .copy-btn:hover { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| } | |
| .copy-btn.copied { | |
| background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); | |
| color: white; | |
| } | |
| .v3-header { | |
| background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); | |
| color: white; | |
| padding: 4px 12px; | |
| border-radius: 8px; | |
| font-size: 0.75rem; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| display: inline-block; | |
| margin-bottom: 8px; | |
| } | |
| .processing-step { | |
| background: rgba(102, 126, 234, 0.1); | |
| border-left: 3px solid #667eea; | |
| padding: 8px 12px; | |
| margin: 4px 0; | |
| border-radius: 4px; | |
| font-size: 0.85rem; | |
| } | |
| .step-success { | |
| background: rgba(17, 153, 142, 0.1); | |
| border-left-color: #11998e; | |
| } | |
| .step-error { | |
| background: rgba(235, 51, 73, 0.1); | |
| border-left-color: #eb3349; | |
| } | |
| .redirect-animation { | |
| animation: redirectPulse 1s ease-in-out infinite; | |
| } | |
| @keyframes redirectPulse { | |
| 0%, | |
| 100% { | |
| opacity: 1; | |
| } | |
| 50% { | |
| opacity: 0.5; | |
| } | |
| } | |
| .bookmarklet-btn { | |
| background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%); | |
| transition: all 0.3s ease; | |
| } | |
| .bookmarklet-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 20px rgba(255, 107, 107, 0.4); | |
| } | |
| .notification { | |
| position: fixed; | |
| top: 20px; | |
| right: 20px; | |
| padding: 16px 24px; | |
| border-radius: 12px; | |
| color: white; | |
| font-weight: 600; | |
| z-index: 1000; | |
| animation: slideInNotification 0.3s ease-out; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); | |
| } | |
| .notification.success { | |
| background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); | |
| } | |
| .notification.error { | |
| background: linear-gradient(135deg, #eb3349 0%, #f45c43 100%); | |
| } | |
| .notification.info { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| } | |
| @keyframes slideInNotification { | |
| from { | |
| transform: translateX(400px); | |
| opacity: 0; | |
| } | |
| to { | |
| transform: translateX(0); | |
| opacity: 1; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="animated-bg min-h-screen"> | |
| <div class="container mx-auto px-4 py-8 max-w-6xl"> | |
| <!-- Header --> | |
| <header class="text-center mb-10"> | |
| <div class="inline-flex items-center justify-center mb-4 floating"> | |
| <i class="fas fa-bolt text-white text-5xl mr-4"></i> | |
| <h1 class="text-5xl font-bold text-white">Douyin CDN Extractor</h1> | |
| <span class="v3-badge ml-3">V3</span> | |
| </div> | |
| <p class="text-white/90 text-xl mb-2">Extração Direta da CDN - Link V3 Final</p> | |
| <p class="text-white/70 text-sm">Redirecionamento automático para a URL original da CDN</p> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" | |
| class="inline-block mt-4 text-white/80 hover:text-white transition-all text-sm"> | |
| Built with anycoder <i class="fas fa-external-link-alt ml-1"></i> | |
| </a> | |
| </header> | |
| <!-- Main Card --> | |
| <main class="glass-card rounded-3xl p-8"> | |
| <!-- URL Input Section --> | |
| <section class="mb-8"> | |
| <div class="flex items-center mb-4"> | |
| <div | |
| class="w-10 h-10 bg-gradient-to-r from-purple-500 to-pink-500 rounded-lg flex items-center justify-center mr-3"> | |
| <i class="fas fa-link text-white"></i> | |
| </div> | |
| <h2 class="text-2xl font-bold text-gray-800">Insira as URLs do Douyin</h2> | |
| </div> | |
| <div class="space-y-4"> | |
| <div class="relative"> | |
| <textarea id="urlInput" | |
| placeholder="Digite as URLs do Douyin (uma por linha) Exemplo: https://www.douyin.com/video/7123456789012345678 Exemplo: https://v.douyin.com/ieABCDEF/" | |
| class="w-full p-5 border-2 border-gray-200 rounded-xl focus:border-purple-500 focus:outline-none resize-none custom-scrollbar transition-all duration-300 text-gray-700" | |
| rows="6"></textarea> | |
| <div class="absolute top-3 right-3"> | |
| <span id="urlCount" class="text-sm text-gray-500 bg-gray-100 px-3 py-1 rounded-full font-medium">0 URLs</span> | |
| </div> | |
| </div> | |
| <div class="flex gap-3 flex-wrap"> | |
| <button onclick="executeExtraction()" | |
| class="btn-primary text-white px-8 py-3 rounded-xl font-semibold flex items-center justify-center flex-1 min-w-[200px]"> | |
| <i class="fas fa-bolt mr-2"></i> | |
| Extrair CDN V3 | |
| </button> | |
| <button onclick="clearAll()" | |
| class="px-6 py-3 bg-gray-200 text-gray-700 rounded-xl font-semibold hover:bg-gray-300 transition-all duration-300 flex items-center"> | |
| <i class="fas fa-trash mr-2"></i> | |
| Limpar | |
| </button> | |
| <button onclick="loadSampleUrls()" | |
| class="px-6 py-3 bg-gradient-to-r from-green-400 to-blue-500 text-white rounded-xl font-semibold hover:from-green-5 hover:to-blue-600 transition-all duration-300 flex items-center"> | |
| <i class="fas fa-magic mr-2"></i> | |
| Exemplo | |
| </button> | |
| <button onclick="createBookmarklet()" | |
| class="bookmarklet-btn text-white px-6 py-3 rounded-xl font-semibold flex items-center"> | |
| <i class="fas fa-bookmark mr-2"></i> | |
| Bookmarklet | |
| </button> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Processing Status --> | |
| <section id="processingSection" class="hidden mb-8"> | |
| <div class="flex items-center mb-4"> | |
| <div | |
| class="w-10 h-10 bg-gradient-to-r from-blue-500 to-cyan-500 rounded-lg flex items-center justify-center mr-3"> | |
| <i class="fas fa-cogs text-white process-animation"></i> | |
| </div> | |
| <h2 class="text-2xl font-bold text-gray-800">Processando Links V3</h2> | |
| </div> | |
| <div id="processingList" class="space-y-3 max-h-96 overflow-y-auto custom-scrollbar"></div> | |
| </section> | |
| <!-- Results Section --> | |
| <section id="resultsSection" class="hidden"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <div class="flex items-center"> | |
| <div | |
| class="w-10 h-10 bg-gradient-to-r from-green-5 to-emerald-500 rounded-lg flex items-center justify-center mr-3"> | |
| <i class="fas fa-check-double text-white"></i> | |
| </div> | |
| <h2 class="text-2xl font-bold text-gray-800">Links CDN V3 Extraídos</h2> | |
| </div> | |
| <div class="flex gap-2"> | |
| <button onclick="downloadAllAsText()" | |
| class="px-4 py-2 bg-blue-100 text-blue-700 rounded-lg font-semibold hover:bg-blue-200 transition-all duration-300 flex items-center"> | |
| <i class="fas fa-file-download mr-2"></i> | |
| Salvar Links | |
| </button> | |
| <button onclick="copyAllResults()" | |
| class="px-4 py-2 bg-green-100 text-green-700 rounded-lg font-semibold hover:bg-green-200 transition-all duration-300 flex items-center"> | |
| <i class="fas fa-copy mr-2"></i> | |
| Copiar Tudo | |
| </button> | |
| </div> | |
| </div> | |
| <div class="bg-gradient-to-br from-gray-50 to-gray-100 rounded-xl p-6"> | |
| <ol id="resultsList" class="space-y-4 custom-scrollbar max-h-[600px] overflow-y-auto"></ol> | |
| </div> | |
| </section> | |
| <!-- JavaScript Function Display --> | |
| <section class="mt-8"> | |
| <div class="direct-link rounded-xl p-6"> | |
| <div class="flex items-center mb-3"> | |
| <i class="fas fa-code text-purple-600 text-xl mr-3"></i> | |
| <h3 class="text-lg font-bold text-gray-800">Script de Extração V3 - CDN Direta</h3> | |
| </div> | |
| <div class="script-view p-4 rounded-lg overflow-x-auto text-xs"> | |
| <pre><span class="comment">// Script exato para extração V3 com redirecionamento</span> | |
| <span class="keyword">javascript:</span>(<span class="keyword">function</span>(){ | |
| <span class="keyword">const</span> u=window.location.href.<span class="function">split</span>(<span class="string">'?'</span>)[<span class="number">0</span>]; | |
| <span class="keyword">const</span> vid=<span class="keyword">new</span> <span class="function">URLSearchParams</span>(window.location.search).<span class="function">get</span>(<span class="string">'modal_id'</span>); | |
| <span class="keyword">const</span> targetUrl=vid?<span class="string">`https://www.douyin.com/video/</span><span class="function">${vid}</span><span class="string">`</span>:u; | |
| <span class="keyword">const</span> hash=<span class="function">btoa</span>(targetUrl)+(targetUrl.length+<span class="number">1000</span>)+<span class="function">btoa</span>(<span class="string">'aio-dl'</span>); | |
| <span class="function">console</span>.<span class="function">log</span>(<span class="string">"🚀 Extraindo link real da CDN..."</span>); | |
| <span class="function">fetch</span>(<span class="string">'https://snapdouyin.app/wp-json/mx-downloader/video-data/'</span>, { | |
| method: <span class="string">'POST'</span>, | |
| headers: {<span class="string">'Content-Type'</span>:<span class="string">'application/x-www-form-urlencoded'</span>}, | |
| body: <span class="string">`url=</span><span class="function">${encodeURIComponent(targetUrl)}</span><span class="string">&hash=</span><span class="function">${hash}</span><span class="string">`</span> | |
| }) | |
| .<span class="function">then</span>(r => r.<span class="function">json</span>()) | |
| .<span class="function">then</span>(data => { | |
| <span class="keyword">if</span>(data.medias && data.medias.length > <span class="number">0</span>) { | |
| <span class="comment">/* Pega a melhor qualidade (Original) */</span> | |
| <span class="keyword">let</span> best = data.medias.<span class="function">find</span>(m => m.quality === <span class="string">'original'</span>) || data.medias[<span class="number">0</span>]; | |
| <span class="keyword">let</span> phpLink = best.url; | |
| <span class="comment">/* Segue o rastro do link V3 */</span> | |
| <span class="function">console</span>.<span class="function">log</span>(<span class="string">"🔗 Seguindo rastro do link V3..."</span>); | |
| window.location.href = phpLink; | |
| } | |
| }) | |
| .<span class="function">catch</span>(e => { | |
| <span class="comment">/* Fallback caso o fetch seja bloqueado */</span> | |
| <span class="function">alert</span>(<span class="string">"Clique OK para gerar o link final na página do Snap."</span>); | |
| window.<span class="function">open</span>(<span class="string">`https://snapdouyin.app/#url=</span><span class="function">${encodeURIComponent(targetUrl)}</span><span class="string">`</span>,<span class="string">'_blank'</span>); | |
| }); | |
| })();</pre> | |
| </div> | |
| <div class="mt-3 p-3 bg-pink-50 rounded-lg"> | |
| <p class="text-sm text-pink-700"> | |
| <i class="fas fa-bolt mr-2"></i> | |
| Redirecionamento automático para URL final da CDN V3 | |
| </p> | |
| </div> | |
| </div> | |
| </section> | |
| </main> | |
| <!-- Footer --> | |
| <footer class="text-center mt-10 text-white/80"> | |
| <p class="text-sm"> | |
| <i class="fas fa-server mr-2"></i> | |
| Links V3 Diretos da CDN | Redirecionamento Automático | Qualidade Original | |
| </p> | |
| </footer> | |
| </div> | |
| <script> | |
| let extractedLinks = []; | |
| // Update URL count | |
| document.getElementById('urlInput').addEventListener('input', function() { | |
| const urls = this.value.split('\n').filter(url => url.trim()); | |
| document.getElementById('urlCount').textContent = `${urls.length} URLs`; | |
| }); | |
| // Show notification | |
| function showNotification(message, type = 'info') { | |
| const notification = document.createElement('div'); | |
| notification.className = `notification ${type}`; | |
| notification.textContent = message; | |
| document.body.appendChild(notification); | |
| setTimeout(() => { | |
| notification.remove(); | |
| }, 3000); | |
| } | |
| // Load sample URLs | |
| function loadSampleUrls() { | |
| const sampleUrls = [ | |
| 'https://v.douyin.com/ie2dGKkT/', | |
| 'https://www.douyin.com/video/7300000000000000000', | |
| 'https://v.douyin.com/ie1ABCDEF/' | |
| ]; | |
| document.getElementById('urlInput').value = sampleUrls.join('\n'); | |
| document.getElementById('urlCount').textContent = `${sampleUrls.length} URLs`; | |
| showNotification('URLs de exemplo carregadas', 'success'); | |
| } | |
| // Clear all | |
| function clearAll() { | |
| document.getElementById('urlInput').value = ''; | |
| document.getElementById('urlCount').textContent = '0 URLs'; | |
| document.getElementById('processingSection').classList.add('hidden'); | |
| document.getElementById('resultsSection').classList.add('hidden'); | |
| extractedLinks = []; | |
| showNotification('Todos os campos foram limpos', 'success'); | |
| } | |
| // Create bookmarklet | |
| function createBookmarklet() { | |
| const bookmarkletCode = `javascript:(function(){ | |
| const u=window.location.href.split('?')[0]; | |
| const vid=new URLSearchParams(window.location.search).get('modal_id'); | |
| const targetUrl=vid ? \`https://www.douyin.com/video/\${vid}\` : u; | |
| const hash=btoa(targetUrl)+(targetUrl.length+1000)+btoa('aio-dl'); | |
| console.log("🚀 Extraindo link real da CDN..."); | |
| fetch('https://snapdouyin.app/wp-json/mx-downloader/video-data/', { | |
| method: 'POST', | |
| headers: {'Content-Type':'application/x-www-form-urlencoded'}, | |
| body: \`url=\${encodeURIComponent(targetUrl)}&hash=\${hash}\` | |
| }) | |
| .then(r => r.json()) | |
| .then(data => { | |
| if(data.medias && data.medias.length > 0) { | |
| let best = data.medias.find(m => m.quality === 'original') || data.medias[0]; | |
| let phpLink = best.url; | |
| console.log("🔗 Seguindo rastro do link V3..."); | |
| window.location.href = phpLink; | |
| } | |
| }) | |
| .catch(e => { | |
| alert("Clique OK para gerar o link final na página do Snap."); | |
| window.open(\`https://snapdouyin.app/#url=\${encodeURIComponent(targetUrl)}\`, '_blank'); | |
| }); | |
| })();`; | |
| navigator.clipboard.writeText(bookmarkletCode).then(() => { | |
| showNotification('Bookmarklet copiado! Arraste para sua barra de favoritos', 'success'); | |
| }).catch(() => { | |
| showNotification('Não foi possível copiar o bookmarklet', 'error'); | |
| }); | |
| } | |
| // Execute extraction | |
| async function executeExtraction() { | |
| const input = document.getElementById('urlInput').value; | |
| const urls = input.split('\n').filter(url => url.trim()); | |
| if (urls.length === 0) { | |
| showNotification('Por favor, insira pelo menos uma URL do Douyin', 'error'); | |
| return; | |
| } | |
| // Show processing section | |
| document.getElementById('processingSection').classList.remove('hidden'); | |
| document.getElementById('processingList').innerHTML = ''; | |
| document.getElementById('resultsSection').classList.add('hidden'); | |
| extractedLinks = []; | |
| // Process each URL | |
| for (let i = 0; i < urls.length; i++) { | |
| const url = urls[i].trim(); | |
| await extractV3Link(url, i + 1); | |
| } | |
| // Show results | |
| showResults(); | |
| } | |
| // Extract V3 link following the exact pattern | |
| async function extractV3Link(url, index) { | |
| // Create processing container | |
| const processingContainer = document.createElement('div'); | |
| processingContainer.className = 'url-line bg-white p-4 rounded-lg border border-gray-200 shadow-sm mb-3'; | |
| document.getElementById('processingList').appendChild(processingContainer); | |
| try { | |
| // Step 1: Preparação | |
| addProcessingStep(processingContainer, '🚀 Extraindo link real da CDN...', 'info'); | |
| await sleep(500); | |
| // Step 2: Process URL (exactly like the script) | |
| addProcessingStep(processingContainer, '📝 Processando URL e gerando hash...', 'info'); | |
| const u = url.split('?')[0]; | |
| const urlParams = new URLSearchParams(url.split('?')[1] || ''); | |
| const vid = urlParams.get('modal_id'); | |
| const targetUrl = vid ? `https://www.douyin.com/video/${vid}` : u; | |
| const hash = btoa(targetUrl) + (targetUrl.length + 1000) + btoa('aio-dl'); | |
| await sleep(500); | |
| addProcessingStep(processingContainer, '🔗 Conectando ao servidor...', 'info'); | |
| // Step 3: Fetch video data | |
| 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(targetUrl)}&hash=${hash}` | |
| }); | |
| const data = await response.json(); | |
| if (!data.medias || data.medias.length === 0) { | |
| throw new Error('Nenhuma mídia encontrada'); | |
| } | |
| await sleep(500); | |
| addProcessingStep(processingContainer, '🎯 Buscando melhor qualidade (Original)...', 'info'); | |
| // Step 4: Find best quality (exactly like the script) | |
| let best = data.medias.find(m => m.quality === 'original') || data.medias[0]; | |
| let phpLink = best.url; | |
| await sleep(500); | |
| addProcessingStep(processingContainer, '🔍 Seguindo rastro do link V3...', 'info'); | |
| // Step 5: Get final V3 URL (follow redirect) | |
| const headResponse = await fetch(phpLink, { | |
| method: 'HEAD', | |
| redirect: 'follow' | |
| }); | |
| let finalV3 = headResponse.url; | |
| await sleep(500); | |
| // Success! | |
| addProcessingStep(processingContainer, '✅ Link V3 final obtido!', 'success'); | |
| extractedLinks.push({ | |
| originalUrl: url, | |
| v3Url: finalV3, | |
| index: index, | |
| title: data.title || 'Vídeo Douyin', | |
| quality: best.quality || 'original' | |
| }); | |
| // Update container to success state | |
| setTimeout(() => { | |
| processingContainer.className = 'url-line bg-gradient-to-r from-green-50 to-emerald-50 p-4 rounded-lg border-2 border-green-300 shadow-md mb-3'; | |
| processingContainer.innerHTML = ` | |
| <div class="flex items-center justify-between mb-2"> | |
| <div class="flex items-center"> | |
| <div class="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center mr-3"> | |
| <i class="fas fa-check text-white text-sm"></i> | |
| </div> | |
| <div> | |
| <span class="text-sm font-bold text-gray-800">Link V3 ${index} - Extraído!</span> | |
| <div class="text-xs text-gray-600">Redirecionamento CDN | Qualidade: ${extractedLinks[index-1].quality}</div> | |
| </div> | |
| </div> | |
| <div class="cdn-badge"> | |
| <i class="fas fa-server mr-1"></i>CDN V3 | |
| </div> | |
| </div> | |
| <div class="space-y-1"> | |
| <div class="processing-step step-success">🚀 Link real da CDN extraído</div> | |
| <div class="processing-step step-success">📝 URL processada e hash gerado</div> | |
| <div class="processing-step step-success">🔗 Conectado ao servidor</div> | |
| <div class="processing-step step-success">🎯 Qualidade original encontrada</div> | |
| <div class="processing-step step-success">🔍 Rastro do link V3 seguido</div> | |
| <div class="console-log console-success">✅ Link V3 final obtido!</div> | |
| </div> | |
| `; | |
| }, 500); | |
| } catch (error) { | |
| addProcessingStep(processingContainer, `❌ Erro: ${error.message}`, 'error'); | |
| // Update container to error state | |
| processingContainer.className = 'url-line bg-gradient-to-r from-red-50 to-pink-50 p-4 rounded-lg border-2 border-red-300 shadow-md mb-3'; | |
| processingContainer.innerHTML = ` | |
| <div class="flex items-center justify-between mb-2"> | |
| <div class="flex items-center"> | |
| <div class="w-8 h-8 bg-red-500 rounded-full flex items-center justify-center mr-3"> | |
| <i class="fas fa-times text-white text-sm"></i> | |
| </div> | |
| <div> | |
| <span class="text-sm font-bold text-gray-800">URL ${index} - Falha</span> | |
| <div class="text-xs text-gray-600">Não foi possível extrair o link V3</div> | |
| </div> | |
| </div> | |
| <div class="error-badge text-xs">Erro</div> | |
| </div> | |
| <div class="console-log console-error">❌ Erro: ${error.message}</div> | |
| `; | |
| } | |
| } | |
| // Add processing step | |
| function addProcessingStep(container, message, type) { | |
| const step = document.createElement('div'); | |
| const stepClass = type === 'success' ? 'step-success' : | |
| type === 'error' ? 'step-error' : ''; | |
| step.className = `processing-step ${stepClass}`; | |
| step.textContent = message; | |
| container.appendChild(step); | |
| container.scrollTop = container.scrollHeight; | |
| } | |
| // Sleep helper | |
| function sleep(ms) { | |
| return new Promise(resolve => setTimeout(resolve, ms)); | |
| } | |
| // Show results | |
| function showResults() { | |
| if (extractedLinks.length === 0) { | |
| showNotification('Nenhum link V3 foi extraído', 'error'); | |
| return; | |
| } | |
| document.getElementById('resultsSection').classList.remove('hidden'); | |
| const resultsList = document.getElementById('resultsList'); | |
| resultsList.innerHTML = ''; | |
| extractedLinks.forEach((item, index) => { | |
| const li = document.createElement('li'); | |
| li.className = 'result-item bg-white p-5 rounded-xl border-2 border-gray-200 hover:border-purple-300'; | |
| li.innerHTML = ` | |
| <div class="flex items-start"> | |
| <span class="result-number mr-4">${item.index}</span> | |
| <div class="flex-1"> | |
| <div class="video-info mb-3"> | |
| <div class="flex items-center justify-between mb-2"> | |
| <h4 class="font-semibold text-gray-800">${item.title}</h4> | |
| <div class="flex gap-2"> | |
| <span class="cdn-badge"> | |
| <i class="fas fa-server mr-1"></i>CDN V3 | |
| </span> | |
| </div> | |
| </div> | |
| <div class="text-xs text-gray-500"> | |
| <i class="fas fa-link mr-1"></i> | |
| Fonte: ${item.originalUrl} | |
| <span class="ml-3"> | |
| <i class="fas fa-video mr-1"></i> | |
| Qualidade: ${item.quality} | |
| </span> | |
| </div> | |
| </div> | |
| <div class="space-y-3"> | |
| <div> | |
| <span class="v3-header"> | |
| <i class="fas fa-bolt mr-1"></i>LINK CDN V3 DIRETO | |
| </span | |
| <div class="v3-url"> | |
| ${item.v3Url} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex flex-col gap-2 ml-4"> | |
| <button onclick="downloadVideo('${item.v3Url}', '${item.title}')" | |
| class="download-btn px-3 py-2 text-white rounded-lg text-xs font-semibold flex items-center" | |
| title="Baixar vídeo"> | |
| <i class="fas fa-download mr-1"></i> | |
| Download | |
| </button> | |
| <button onclick="copySingleUrl('${item.v3Url}', this)" | |
| class="copy-btn px-3 py-2 bg-gray-100 text-gray-700 rounded-lg text-xs font-semibold flex items-center" | |
| title="Copiar URL"> | |
| <i class="fas fa-copy mr-1"></i> | |
| Copiar | |
| </button> | |
| <button onclick="openInNewTab('${item.v3Url}')" | |
| class="px-3 py-2 bg-purple-100 text-purple-700 rounded-lg text-xs font-semibold flex items-center hover:bg-purple-200 transition-colors" | |
| title="Abrir em nova aba"> | |
| <i class="fas fa-external-link-alt mr-1"></i> | |
| Abrir | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| resultsList.appendChild(li); | |
| }); | |
| showNotification(`${extractedLinks.length} links V3 extraídos com sucesso!`, 'success'); | |
| } | |
| // Download video | |
| function downloadVideo(url, title) { | |
| // Create a temporary anchor element | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| // Extract filename or use title | |
| const filename = title.replace(/[^a-z0-9]/gi, '_').toLowerCase() + '.mp4'; | |
| a.download = filename || 'douyin_video.mp4'; | |
| // Trigger download | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| showNotification(`Baixando: ${title}`, 'info'); | |
| } | |
| // Copy single URL | |
| function copySingleUrl(url, buttonElement) { | |
| navigator.clipboard.writeText(url).then(() => { | |
| // Change button appearance temporarily | |
| const originalHTML = buttonElement.innerHTML; | |
| buttonElement.classList.add('copied'); | |
| buttonElement.innerHTML = '<i class="fas fa-check mr-1"></i>Copiado!'; | |
| // Reset after 2 seconds | |
| setTimeout(() => { | |
| buttonElement.classList.remove('copied'); | |
| buttonElement.innerHTML = originalHTML; | |
| }, 2000); | |
| showNotification('Link copiado para a área de transferência', 'success'); | |
| }).catch(() => { | |
| showNotification('Não foi possível copiar o link', 'error'); | |
| }); | |
| } | |
| // Open in new tab | |
| function openInNewTab(url) { | |
| window.open(url, '_blank'); | |
| showNotification('Abrindo link em nova aba...', 'info'); | |
| } | |
| // Download all as text | |
| function downloadAllAsText() { | |
| if (extractedLinks.length === 0) { | |
| showNotification('Nenhum link para baixar', 'error'); | |
| return; | |
| } | |
| let textContent = 'DOUYIN CDN V3 LINKS EXTRAÍDOS\n'; | |
| textContent += '=' .repeat(50) + '\n\n'; | |
| extractedLinks.forEach((item, index) => { | |
| textContent += `LINK ${index + 1}\n`; | |
| textContent += `Título: ${item.title}\n`; | |
| textContent += `Qualidade: ${item.quality}\n`; | |
| textContent += `URL Original: ${item.originalUrl}\n`; | |
| textContent += `Link CDN V3: ${item.v3Url}\n`; | |
| textContent += '-'.repeat(30) + '\n\n'; | |
| }); | |
| // Create blob and download | |
| const blob = new Blob([textContent], { type: 'text/plain' }); | |
| const url = window.URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `douyin_links_${new Date().toISOString().slice(0,10)}.txt`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| window.URL.revokeObjectURL(url); | |
| showNotification('Arquivo de links baixado com sucesso!', 'success'); | |
| } | |
| // Copy all results | |
| function copyAllResults() { | |
| if (extractedLinks.length === 0) { | |
| showNotification('Nenhum link para copiar', 'error'); | |
| return; | |
| } | |
| let allUrls = extractedLinks.map(item => | |
| `${item.title}\n${item.v3Url}\n(${item.originalUrl})` | |
| ).join('\n\n'); | |
| navigator.clipboard.writeText(allUrls).then(() => { | |
| showNotification(`Todos os ${extractedLinks.length} links foram copiados!`, 'success'); | |
| }).catch(() => { | |
| showNotification('Não foi possível copiar os links', 'error'); | |
| }); | |
| } | |
| // Initialize | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Add some animation to the header | |
| const header = document.querySelector('header'); | |
| header.style.opacity = '0'; | |
| header.style.transform = 'translateY(-20px)'; | |
| setTimeout(() => { | |
| header.style.transition = 'all 0.6s ease-out'; | |
| header.style.opacity = '1'; | |
| header.style.transform = 'translateY(0)'; | |
| }, 100); | |
| // Focus on URL input | |
| const urlInput = document.getElementById('urlInput'); | |
| urlInput.focus(); | |
| // Add keyboard shortcut | |
| document.addEventListener('keydown', function(e) { | |
| // Ctrl/Cmd + Enter to extract | |
| if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { | |
| executeExtraction(); | |
| } | |
| // Ctrl/Cmd + L to clear | |
| if ((e.ctrlKey || e.metaKey) && e.key === 'l') { | |
| e.preventDefault(); | |
| clearAll(); | |
| } | |
| // Ctrl/Cmd + D to download all | |
| if ((e.ctrlKey || e.metaKey) && e.key === 'd') { | |
| e.preventDefault(); | |
| downloadAllAsText(); | |
| } | |
| // Ctrl/Cmd + C to copy all (if results are shown) | |
| if ((e.ctrlKey || e.metaKey) && e.key === 'c') { | |
| if (!document.getElementById('resultsSection').classList.contains('hidden')) { | |
| e.preventDefault(); | |
| copyAllResults(); | |
| } | |
| } | |
| }); | |
| // Add some visual feedback | |
| const buttons = document.querySelectorAll('button'); | |
| buttons.forEach(button => { | |
| button.addEventListener('mouseenter', function() { | |
| this.style.transform = 'scale(1.05)'; | |
| }); | |
| button.addEventListener('mouseleave', function() { | |
| this.style.transform = 'scale(1)'; | |
| }); | |
| }); | |
| // Auto-hide notifications on mobile after tap | |
| if ('ontouchstart' in window) { | |
| document.addEventListener('touchstart', function(e) { | |
| if (e.target.classList.contains('notification')) { | |
| e.target.remove(); | |
| } | |
| }); | |
| } | |
| }); | |
| // Handle paste event to auto-detect URLs | |
| document.getElementById('urlInput').addEventListener('paste', function(e) { | |
| setTimeout(() => { | |
| const pastedText = this.value; | |
| // Check if pasted content contains URLs | |
| const urlPattern = /https?:\/\/(www\.)?(douyin\.com|v\.douyin\.com)[^\s]+/gi; | |
| const urls = pastedText.match(urlPattern); | |
| if (urls && urls.length > 0) { | |
| // Update URL count | |
| document.getElementById('urlCount').textContent = `${urls.length} URLs`; | |
| showNotification(`Detectado(s) ${urls.length} URL(s) do Douyin`, 'info'); | |
| } | |
| }, 100); | |
| }); | |
| // Add drag and drop functionality | |
| const urlInput = document.getElementById('urlInput'); | |
| urlInput.addEventListener('dragoover', function(e) { | |
| e.preventDefault(); | |
| this.style.borderColor = '#667eea'; | |
| this.style.borderWidth = '3px'; | |
| }); | |
| urlInput.addEventListener('dragleave', function(e) { | |
| e.preventDefault(); | |
| this.style.borderColor = ''; | |
| this.style.borderWidth = ''; | |
| }); | |
| urlInput.addEventListener('drop', function(e) { | |
| e.preventDefault(); | |
| this.style.borderColor = ''; | |
| this.style.borderWidth = ''; | |
| const droppedText = e.dataTransfer.getData('text'); | |
| const urlPattern = /https? |