anycoder-a9a8d08f / index.html
eubottura's picture
Upload folder using huggingface_hub
0f557da verified
<!DOCTYPE html>
<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)&#10;Exemplo: https://www.douyin.com/video/7123456789012345678&#10;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?