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 - Multi URL Processor</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> | |
| :root { | |
| --primary: #6366f1; | |
| --primary-dark: #4f46e5; | |
| --secondary: #8b5cf6; | |
| --success: #10b981; | |
| --danger: #ef4444; | |
| --warning: #f59e0b; | |
| --dark: #1f2937; | |
| --gray: #6b7280; | |
| --light: #f3f4f6; | |
| --white: #ffffff; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; | |
| position: relative; | |
| overflow-x: hidden; | |
| } | |
| body::before { | |
| content: ''; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320"><path fill="%23ffffff" fill-opacity="0.05" d="M0,96L48,112C96,128,192,160,288,165.3C384,171,480,149,576,154.7C672,160,768,192,864,197.3C960,203,1056,181,1152,165.3C1248,149,1344,139,1392,133.3L1440,128L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"></path></svg>') no-repeat bottom; | |
| background-size: cover; | |
| pointer-events: none; | |
| opacity: 0.3; | |
| } | |
| .glass-morphism { | |
| background: rgba(255, 255, 255, 0.95); | |
| backdrop-filter: blur(20px); | |
| -webkit-backdrop-filter: blur(20px); | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); | |
| } | |
| .gradient-text { | |
| background: linear-gradient(135deg, var(--primary), var(--secondary)); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| } | |
| .code-block { | |
| background: linear-gradient(135deg, #1e293b, #334155); | |
| color: #e2e8f0; | |
| font-family: 'Fira Code', 'Courier New', monospace; | |
| padding: 1.5rem; | |
| border-radius: 12px; | |
| overflow-x: auto; | |
| position: relative; | |
| border: 1px solid rgba(99, 102, 241, 0.2); | |
| } | |
| .extracted-link { | |
| background: linear-gradient(135deg, #0f172a, #1e293b); | |
| color: #10b981; | |
| font-family: 'Fira Code', 'Courier New', monospace; | |
| word-break: break-all; | |
| padding: 1rem; | |
| border-radius: 8px; | |
| border: 1px solid rgba(16, 185, 129, 0.2); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .extracted-link::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: -100%; | |
| width: 100%; | |
| height: 100%; | |
| background: linear-gradient(90deg, transparent, rgba(16, 185, 129, 0.1), transparent); | |
| animation: shimmer 3s infinite; | |
| } | |
| @keyframes shimmer { | |
| to { | |
| left: 100%; | |
| } | |
| } | |
| @keyframes fadeIn { | |
| from { | |
| opacity: 0; | |
| transform: translateY(20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| @keyframes slideIn { | |
| from { | |
| opacity: 0; | |
| transform: translateX(-20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateX(0); | |
| } | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.5s ease-out; | |
| } | |
| .slide-in { | |
| animation: slideIn 0.5s ease-out; | |
| } | |
| .url-item { | |
| transition: all 0.3s ease; | |
| border-left: 4px solid transparent; | |
| } | |
| .url-item:hover { | |
| transform: translateX(5px); | |
| border-left-color: var(--primary); | |
| background: linear-gradient(to right, rgba(99, 102, 241, 0.05), transparent); | |
| } | |
| .url-item.processing { | |
| border-left-color: var(--warning); | |
| background: linear-gradient(to right, rgba(245, 158, 11, 0.05), transparent); | |
| } | |
| .url-item.success { | |
| border-left-color: var(--success); | |
| background: linear-gradient(to right, rgba(16, 185, 129, 0.05), transparent); | |
| } | |
| .url-item.error { | |
| border-left-color: var(--danger); | |
| background: linear-gradient(to right, rgba(239, 68, 68, 0.05), transparent); | |
| } | |
| .btn-primary { | |
| background: linear-gradient(135deg, var(--primary), var(--secondary)); | |
| transition: all 0.3s ease; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .btn-primary::before { | |
| content: ''; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| width: 0; | |
| height: 0; | |
| background: rgba(255, 255, 255, 0.3); | |
| border-radius: 50%; | |
| transform: translate(-50%, -50%); | |
| transition: width 0.6s, height 0.6s; | |
| } | |
| .btn-primary:hover::before { | |
| width: 300px; | |
| height: 300px; | |
| } | |
| .btn-primary:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 30px rgba(99, 102, 241, 0.3); | |
| } | |
| .toast { | |
| position: fixed; | |
| bottom: 2rem; | |
| right: 2rem; | |
| transform: translateX(400px); | |
| transition: transform 0.3s ease; | |
| z-index: 1000; | |
| } | |
| .toast.show { | |
| transform: translateX(0); | |
| } | |
| .instruction-step { | |
| transition: all 0.3s ease; | |
| cursor: pointer; | |
| } | |
| .instruction-step:hover { | |
| transform: scale(1.02); | |
| background: linear-gradient(135deg, rgba(99, 102, 241, 0.05), rgba(139, 92, 246, 0.05)); | |
| } | |
| .tab-active { | |
| background: linear-gradient(135deg, var(--primary), var(--secondary)); | |
| color: white; | |
| } | |
| .scroll-smooth { | |
| scroll-behavior: smooth; | |
| } | |
| /* Custom Scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: rgba(0, 0, 0, 0.1); | |
| border-radius: 10px; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: linear-gradient(135deg, var(--primary), var(--secondary)); | |
| border-radius: 10px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: var(--primary-dark); | |
| } | |
| /* Loading Animation */ | |
| .loading-dots span { | |
| animation: blink 1.4s infinite both; | |
| } | |
| .loading-dots span:nth-child(2) { | |
| animation-delay: 0.2s; | |
| } | |
| .loading-dots span:nth-child(3) { | |
| animation-delay: 0.4s; | |
| } | |
| @keyframes blink { | |
| 0%, | |
| 80%, | |
| 100% { | |
| opacity: 0.3; | |
| } | |
| 40% { | |
| opacity: 1; | |
| } | |
| } | |
| /* Dark theme */ | |
| body.dark-theme { | |
| background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); | |
| } | |
| body.dark-theme .glass-morphism { | |
| background: rgba(30, 30, 30, 0.95); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| body.dark-theme .text-gray-600 { | |
| color: #a0a0a0; | |
| } | |
| body.dark-theme .bg-gray-50 { | |
| background: rgba(255, 255, 255, 0.05); | |
| } | |
| body.dark-theme .text-gray-700 { | |
| color: #e0e0e0; | |
| } | |
| body.dark-theme .border-gray-200 { | |
| border-color: rgba(255, 255, 255, 0.1); | |
| } | |
| </style> | |
| </head> | |
| <body class="scroll-smooth"> | |
| <!-- Header --> | |
| <header class="glass-morphism rounded-2xl p-6 md:p-8 m-4 md:m-8 fade-in"> | |
| <div class="flex items-center justify-between flex-wrap gap-4"> | |
| <div class="flex items-center gap-4"> | |
| <div | |
| class="w-16 h-16 bg-gradient-to-br from-purple-500 to-pink-500 rounded-xl flex items-center justify-center shadow-lg transform hover:rotate-12 transition-transform duration-300"> | |
| <i class="fas fa-video text-white text-2xl"></i> | |
| </div> | |
| <div> | |
| <h1 class="text-3xl font-bold gradient-text">Douyin v3 Link Extractor</h1> | |
| <p class="text-gray-600 mt-1">Extraia links v3 de múltiplos vídeos Douyin</p> | |
| </div> | |
| </div> | |
| <div class="flex gap-3"> | |
| <button onclick="toggleTheme()" class="px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg transition"> | |
| <i class="fas fa-moon" id="themeIcon"></i> | |
| </button> | |
| <button onclick="showHelp()" class="px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg transition"> | |
| <i class="fas fa-question-circle mr-2"></i>Ajuda | |
| </button> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Container --> | |
| <main class="max-w-7xl mx-auto p-4 md:p-8 grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
| <!-- Left Panel - Instructions & Code --> | |
| <section class="lg:col-span-1 space-y-6"> | |
| <!-- Instructions Card --> | |
| <div class="glass-morphism rounded-2xl p-6 shadow-xl fade-in"> | |
| <h2 class="text-xl font-semibold mb-4 flex items-center"> | |
| <i class="fas fa-list-ol text-purple-500 mr-3"></i> | |
| Instruções de Uso | |
| </h2> | |
| <div class="space-y-3"> | |
| <div class="instruction-step bg-gray-50 p-4 rounded-lg"> | |
| <div class="flex items-start gap-3"> | |
| <span class="flex-shrink-0 w-8 h-8 bg-purple-500 text-white rounded-full flex items-center justify-center font-bold text-sm">1</span> | |
| <div> | |
| <p class="font-medium">Adicione URLs</p> | |
| <p class="text-sm text-gray-600 mt-1">Insira múltiplas URLs Douyin, uma por linha</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="instruction-step bg-gray-50 p-4 rounded-lg"> | |
| <div class="flex items-start gap-3"> | |
| <span class="flex-shrink-0 w-8 h-8 bg-purple-500 text-white rounded-full flex items-center justify-center font-bold text-sm">2</span> | |
| <div> | |
| <p class="font-medium">Copie o Código</p> | |
| <p class="text-sm text-gray-600 mt-1">Copie o script de extração abaixo</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="instruction-step bg-gray-50 p-4 rounded-lg"> | |
| <div class="flex items-start gap-3"> | |
| <span class="flex-shrink-0 w-8 h-8 bg-purple-500 text-white rounded-full flex items-center justify-center font-bold text-sm">3</span> | |
| <div> | |
| <p class="font-medium">Execute no Console</p> | |
| <p class="text-sm text-gray-600 mt-1">Abra F12 → Console → Cole e pressione Enter</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="instruction-step bg-gray-50 p-4 rounded-lg"> | |
| <div class="flex items-start gap-3"> | |
| <span class="flex-shrink-0 w-8 h-8 bg-purple-500 text-white rounded-full flex items-center justify-center font-bold text-sm">4</span> | |
| <div> | |
| <p class="font-medium">Cole o Resultado</p> | |
| <p class="text-sm text-gray-600 mt-1">Cole o link v3 extraído no campo correspondente</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Extraction Code --> | |
| <div class="glass-morphism rounded-2xl p-6 shadow-xl fade-in"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <h3 class="text-lg font-semibold flex items-center"> | |
| <i class="fas fa-code text-green-500 mr-2"></i> | |
| Código de Extração | |
| </h3> | |
| <button onclick="copyExtractionCode()" class="text-sm px-3 py-1 bg-green-100 hover:bg-green-200 text-green-700 rounded-lg transition"> | |
| <i class="fas fa-copy mr-1"></i>Copiar | |
| </button> | |
| </div> | |
| <div class="code-block text-xs"> | |
| <pre id="extractionCode">try { | |
| const state = window.__INITIAL_STATE__; | |
| const videoData = state?.videoDetail?.awemeDetail; | |
| const playAddr = videoData?.video?.playAddr; | |
| const urlList = playAddr?.urlList || []; | |
| // Procura primeiro por URL com 'v3' | |
| let v3Url = urlList.find(url => url.includes('v3')); | |
| // Se não encontrar v3, pega a primeira URL | |
| if (!v3Url && urlList.length > 0) { | |
| v3Url = urlList[0]; | |
| } | |
| if (v3Url) { | |
| console.log('🔗 Link v3 encontrado:', v3Url); | |
| // Copia automaticamente para área de transferência | |
| navigator.clipboard.writeText(v3Url); | |
| console.log('✅ Link copiado para área de transferência!'); | |
| } else { | |
| console.log('❌ Nenhum link v3 encontrado'); | |
| } | |
| } catch(e) { | |
| console.error('❌ Erro ao extrair:', e.message); | |
| }</pre> | |
| </div> | |
| </div> | |
| <!-- Stats --> | |
| <div class="glass-morphism rounded-2xl p-6 shadow-xl fade-in"> | |
| <h3 class="text-lg font-semibold mb-4 flex items-center"> | |
| <i class="fas fa-chart-bar text-blue-500 mr-2"></i> | |
| Estatísticas | |
| </h3> | |
| <div class="grid grid-cols-2 gap-4"> | |
| <div class="text-center p-3 bg-blue-50 rounded-lg"> | |
| <p class="text-2xl font-bold text-blue-600" id="totalUrls">0</p> | |
| <p class="text-sm text-gray-600">Total URLs</p> | |
| </div> | |
| <div class="text-center p-3 bg-green-50 rounded-lg"> | |
| <p class="text-2xl font-bold text-green-600" id="extractedCount">0</p> | |
| <p class="text-sm text-gray-600">Extraídos</p> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Right Panel - URL Input & Results --> | |
| <section class="lg:col-span-2 space-y-6"> | |
| <!-- URL Input Section --> | |
| <div class="glass-morphism rounded-2xl p-6 shadow-xl fade-in"> | |
| <h2 class="text-xl font-semibold mb-4 flex items-center justify-between"> | |
| <span> | |
| <i class="fas fa-link text-purple-500 mr-3"></i> | |
| URLs Douyin | |
| </span> | |
| <div class="flex gap-2"> | |
| <button onclick="addSampleUrls()" class="text-sm px-3 py-1 bg-purple-100 hover:bg-purple-200 text-purple-700 rounded-lg transition"> | |
| <i class="fas fa-magic mr-1"></i>Exemplo | |
| </button> | |
| <button onclick="clearUrls()" class="text-sm px-3 py-1 bg-red-100 hover:bg-red-200 text-red-700 rounded-lg transition"> | |
| <i class="fas fa-trash mr-1"></i>Limpar | |
| </button> | |
| </div> | |
| </h2> | |
| <div class="mb-4"> | |
| <textarea | |
| id="urlInput" | |
| rows="6" | |
| placeholder="Cole múltiplas URLs Douyin aqui, uma por linha... Exemplo: https://www.douyin.com/video/123456789 https://www.douyin.com/video/987654321" | |
| class="w-full px-4 py-3 border-2 border-gray-200 rounded-lg focus:border-purple-500 focus:outline-none transition resize-none font-mono text-sm" | |
| ></textarea> | |
| <div class="flex justify-between items-center mt-2"> | |
| <span class="text-sm text-gray-500"> | |
| <span id="urlCount">0</span> URLs detectadas | |
| </span> | |
| <button onclick="processUrls()" class="btn-primary px-6 py-2 text-white rounded-lg font-semibold"> | |
| <i class="fas fa-play mr-2"></i>Processar URLs | |
| </button> | |
| </div> | |
| </div> | |
| <!-- URL List --> | |
| <div id="urlList" class="space-y-2 max-h-64 overflow-y-auto"> | |
| <div class="text-center py-8 text-gray-400"> | |
| <i class="fas fa-inbox text-4xl mb-2"></i> | |
| <p class="text-sm">Nenhuma URL adicionada</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Results Section --> | |
| <div class="glass-morphism rounded-2xl p-6 shadow-xl fade-in"> | |
| <h2 class="text-xl font-semibold mb-4 flex items-center justify-between"> | |
| <span> | |
| <i class="fas fa-download text-green-500 mr-3"></i> | |
| Links v3 Extraídos | |
| </span> | |
| <button onclick="exportResults()" class="text-sm px-3 py-1 bg-blue-100 hover:bg-blue-200 text-blue-700 rounded-lg transition"> | |
| <i class="fas fa-file-export mr-1"></i>Exportar | |
| </button> | |
| </h2> | |
| <div id="resultsList" class="space-y-3 max-h-96 overflow-y-auto"> | |
| <div class="text-center py-8 text-gray-400"> | |
| <i class="fas fa-clipboard-list text-4xl mb-2"></i> | |
| <p class="text-sm">Nenhum resultado ainda</p> | |
| <p class="text-xs mt-1">Processando URLs e cole os links v3 aqui</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Quick Actions --> | |
| <div class="glass-morphism rounded-2xl p-4 shadow-xl fade-in"> | |
| <div class="flex flex-wrap gap-3 justify-center"> | |
| <button onclick="copyAllLinks()" class="px-4 py-2 bg-green-500 hover:bg-green-600 text-white rounded-lg transition flex items-center gap-2"> | |
| <i class="fas fa-copy"></i> | |
| Copiar Todos | |
| </button> | |
| <button onclick="downloadAll()" class="px-4 py-2 bg-purple-500 hover:bg-purple-600 text-white rounded-lg transition flex items-center gap-2"> | |
| <i class="fas fa-download"></i> | |
| Baixar Todos | |
| </button> | |
| <button onclick="clearResults()" class="px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-lg transition flex items-center gap-2"> | |
| <i class="fas fa-trash"></i> | |
| Limpar Resultados | |
| </button> | |
| </div> | |
| </div> | |
| </section> | |
| </main> | |
| <!-- Help Modal --> | |
| <div id="helpModal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50 flex items-center justify-center p-4"> | |
| <div class="glass-morphism rounded-2xl p-6 max-w-3xl w-full max-h-[80vh] overflow-y-auto fade-in"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-xl font-semibold">Guia Completo de Uso</h3> | |
| <button onclick="closeHelp()" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times text-xl"></i> | |
| </button> | |
| </div> | |
| <div class="space-y-4 text-sm"> | |
| <div class="bg-yellow-50 border-l-4 border-yellow-500 p-4 rounded"> | |
| <p class="font-semibold text-yellow-800">⚠️ Importante</p> | |
| <p class="text-yellow-700 mt-1"> | |
| Este processo requer que você execute o código diretamente na página do Douyin devido a restrições de | |
| segurança do navegador (CORS). | |
| </p> | |
| </div> | |
| <div> | |
| <h4 class="font-semibold mb-2 text-lg">📋 Passo a Passo Detalhado:</h4> | |
| <ol class="list-decimal list-inside space-y-3 text-gray-700"> | |
| <li class="p-3 bg-gray-50 rounded"> | |
| <strong>Adicione as URLs:</strong> Cole as URLs dos vídeos Douyin que deseja processar, uma por linha. | |
| </li> | |
| <li class="p-3 bg-gray-50 rounded"> | |
| <strong>Copie o código:</strong> Use o botão "Copiar" para copiar o script de extração. | |
| </li> | |
| <li class="p-3 bg-gray-50 rounded"> | |
| <strong>Abra a página Douyin:</strong> Navegue até a primeira URL em uma nova aba. | |
| </li> | |
| <li class="p-3 bg-gray-50 rounded"> | |
| <strong>Abra DevTools:</strong> Pressione F12 ou clique com o botão direito → Inspecionar. | |
| </li> | |
| <li class="p-3 bg-gray-50 rounded"> | |
| <strong>Vá para o Console:</strong> Clique na aba "Console". | |
| </li> | |
| <li class="p-3 bg-gray-50 rounded"> | |
| <strong>Cole e execute:</strong> Cole o código e pressione Enter. | |
| </li> | |
| <li class="p-3 bg-gray-50 rounded"> | |
| <strong>Copie o resultado:</strong> O link v3 será exibido e copiado automaticamente. | |
| </li> | |
| <li class="p-3 bg-gray-50 rounded"> | |
| <strong>Cole no resultado:</strong> Volte para esta ferramenta e cole o link v3 no campo correspondente. | |
| </li> | |
| </ol> | |
| </div> | |
| <div> | |
| <h4 class="font-semibold mb-2">🔍 O que o código faz:</h4> | |
| <ul class="list-disc list-inside space-y-2 text-gray-700"> | |
| <li>Acessa o objeto <code>window.__INITIAL_STATE__</code></li> | |
| <li>Navega até os dados do vídeo</li> | |
| <li>Localiza a lista de URLs de reprodução</li> | |
| <li>Filtra para encontrar o link com formato 'v3'</li> | |
| <li>Retorna e copia o link encontrado</li> | |
| </ul> | |
| </div> | |
| <div class="bg-blue-50 p-4 rounded"> | |
| <p class="font-semibold text-blue-800">💡 Dica Pro</p> | |
| <p class="text-blue-700 mt-1"> | |
| Você pode processar múltiplas URLs em sequência. Mantenha esta ferramenta aberta em uma aba e execute o | |
| código em cada página Douyin, colando os resultados à medida que os obtém. | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Toast Notification --> | |
| <div id="toast" class="toast glass-morphism px-6 py-4 rounded-lg shadow-xl"> | |
| <div class="flex items-center gap-3"> | |
| <i id="toastIcon" class="fas fa-check-circle text-2xl"></i> | |
| <div> | |
| <p id="toastMessage" class="font-semibold"></p> | |
| <p id="toastSubMessage" class="text-sm text-gray-600"></p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <footer class="text-center mt-12 pb-8 text-white text-sm"> | |
| <p>Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" | |
| class="underline hover:no-underline">anycoder</a></p> | |
| </footer> | |
| <script> | |
| // Global state | |
| let urls = []; | |
| let results = []; | |
| let extractedCount = 0; | |
| let isDarkTheme = false; | |
| // Initialize | |
| document.addEventListener('DOMContentLoaded', () => { | |
| updateStats(); | |
| document.getElementById('urlInput').addEventListener('input', handleUrlInput); | |
| }); | |
| // Toggle theme | |
| function toggleTheme() { | |
| isDarkTheme = !isDarkTheme; | |
| document.body.classList.toggle('dark-theme'); | |
| const icon = document.getElementById('themeIcon'); | |
| icon.className = isDarkTheme ? 'fas fa-sun' : 'fas fa-moon'; | |
| showToast('info', 'Tema Alterado', isDarkTheme ? 'Modo escuro ativado' : 'Modo claro ativado'); | |
| } | |
| // Show help modal | |
| function showHelp() { | |
| document.getElementById('helpModal').classList.remove('hidden'); | |
| } | |
| // Close help modal | |
| function closeHelp() { | |
| document.getElementById('helpModal').classList.add('hidden'); | |
| } | |
| // Show toast notification | |
| function showToast(type, message, subMessage = '') { | |
| const toast = document.getElementById('toast'); | |
| const icon = document.getElementById('toastIcon'); | |
| const msgEl = document.getElementById('toastMessage'); | |
| const subMsgEl = document.getElementById('toastSubMessage'); | |
| // Set icon and color based on type | |
| const types = { | |
| success: { icon: 'fas fa-check-circle', color: 'text-green-500' }, | |
| error: { icon: 'fas fa-times-circle', color: 'text-red-500' }, | |
| warning: { icon: 'fas fa-exclamation-triangle', color: 'text-yellow-500' }, | |
| info: { icon: 'fas fa-info-circle', color: 'text-blue-500' } | |
| }; | |
| const config = types[type] || types.info; | |
| icon.className = `${config.icon} text-2xl ${config.color}`; | |
| msgEl.textContent = message; | |
| subMsgEl.textContent = subMessage; | |
| toast.classList.add('show'); | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, 3000); | |
| } | |
| // Add sample URLs | |
| function addSampleUrls() { | |
| const sampleUrls = `https://www.douyin.com/video/7123456789012345678 | |
| https://www.douyin.com/video/7123456789012345679 | |
| https://www.douyin.com/video/7123456789012345680`; | |
| document.getElementById('urlInput').value = sampleUrls; | |
| handleUrlInput(); | |
| showToast('info', 'Exemplos Adicionados', '3 URLs de exemplo foram inseridas'); | |
| } | |
| // Clear all URLs | |
| function clearUrls() { | |
| urls = []; | |
| document.getElementById('urlInput').value = ''; | |
| handleUrlInput(); | |
| showToast('info', 'URLs Limpos', 'Todas as URLs foram removidas'); | |
| } | |
| // Update statistics | |
| function updateStats() { | |
| document.getElementById('totalUrls').textContent = urls.length; | |
| document.getElementById('extractedCount').textContent = extractedCount; | |
| } | |
| // Handle URL input | |
| function handleUrlInput() { | |
| const input = document.getElementById('urlInput').value; | |
| const urlList = input.split('\n').filter(url => url.trim()); | |
| urls = urlList; | |
| document.getElementById('urlCount').textContent = urlList.length; | |
| updateUrlList(); | |
| updateStats(); | |
| } | |
| // Update URL list display | |
| function updateUrlList() { | |
| const container = document.getElementById('urlList'); | |
| if (urls.length === 0) { | |
| container.innerHTML = ` | |
| <div class="text-center py-8 text-gray-400"> | |
| <i class="fas fa-inbox text-4xl mb-2"></i> | |
| <p class="text-sm">Nenhuma URL adicionada</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| container.innerHTML = urls.map((url, index) => ` | |
| <div class="url-item p-3 bg-gray-50 rounded-lg" data-index="${index}"> | |
| <div class="flex items-center justify-between"> | |
| <div class="flex items-center gap-3 flex-1 min-w-0"> | |
| <span class="flex-shrink-0 w-8 h-8 bg-purple-100 text-purple-600 rounded-full flex items-center justify-center font-bold text-sm">${index + 1}</span> | |
| <p class="text-sm font-mono truncate flex-1">${url}</p> | |
| </div> | |
| <div class="flex items-center gap-2"> | |
| <span class="status-badge text-xs px-2 py-1 rounded-full bg-gray-200 text-gray-600">Pendente</span> | |
| <button onclick="removeUrl(${index})" class="text-red-500 hover:text-red-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| // Remove URL | |
| function removeUrl(index) { | |
| urls.splice(index, 1); | |
| document.getElementById('urlInput').value = urls.join('\n'); | |
| handleUrlInput(); | |
| showToast('info', 'URL Removida', 'A URL foi removida da lista'); | |
| } | |
| // Process URLs - CORRECTED VERSION | |
| function processUrls() { | |
| if (urls.length === 0) { | |
| showToast('warning', 'Sem URLs', 'Adicione algumas URLs Douyin para processar'); | |
| return; | |
| } | |
| // Mark all as processing | |
| document.querySelectorAll('.url-item').forEach(item => { | |
| item.classList.add('processing'); | |
| const badge = item.querySelector('.status-badge'); | |
| if (badge) { | |
| badge.className = 'status-badge text-xs px-2 py-1 rounded-full bg-yellow-100 text-yellow-600'; | |
| badge.textContent = 'Processando'; | |
| } | |
| }); | |
| // Create result inputs with proper event binding | |
| const resultsList = document.getElementById('resultsList'); | |
| resultsList.innerHTML = ''; | |
| urls.forEach((url, index) => { | |
| const resultDiv = document.createElement('div'); | |
| resultDiv.className = 'bg-gray-50 p-4 rounded-lg slide-in'; | |
| resultDiv.style.animationDelay = `${index * 0.1}s`; | |
| resultDiv.innerHTML = ` | |
| <div class="flex items-center justify-between mb-2"> | |
| <h4 class="font-semibold text-sm">URL ${index + 1}</h4> | |
| <span class="text-xs text-gray-500">${url}</span> | |
| </div> | |
| <textarea | |
| id="result-${index}" | |
| rows="2" | |
| placeholder="Cole o link v3 extraído aqui..." | |
| class="w-full px-3 py-2 border border-gray-200 rounded focus:border-purple-500 focus:outline-none resize-none font-mono text-xs" | |
| ></textarea> | |
| <div class="flex gap-2 mt-2"> | |
| <button data-action="copy" data-index="${index}" class="flex-1 px-3 py-1 bg-green-100 hover:bg-green-200 text-green-700 rounded text-xs transition"> | |
| <i class="fas fa-copy mr-1"></i>Copiar | |
| </button> | |
| <button data-action="download" data-index="${index}" class="flex-1 px-3 py-1 bg-purple-100 hover:bg-purple-200 text-purple-700 rounded text-xs transition"> | |
| <i class="fas fa-download mr-1"></i>Baixar | |
| </button> | |
| </div> | |
| `; | |
| resultsList.appendChild(resultDiv); | |
| }); | |
| // Add event listeners for dynamically created buttons | |
| resultsList.addEventListener('click', handleResultButtonClick); | |
| showToast('info', 'URLs Processadas', 'Copie o código e execute nas páginas Douyin'); | |
| // Copy extraction code automatically with enhanced error handling | |
| copyExtractionCode(); | |
| } | |
| // Handle result button clicks - CORRECTED VERSION | |
| function handleResultButtonClick(event) { | |
| const button = event.target.closest('button[data-action]'); | |
| if (!button) return; | |
| const action = button.getAttribute('data-action'); | |
| const index = parseInt(button.getAttribute('data-index')); | |
| event.preventDefault(); | |
| event.stopPropagation(); | |
| switch (action) { | |
| case 'copy': | |
| copyResult(index); | |
| break; | |
| case 'download': | |
| downloadResult(index); | |
| break; | |
| } | |
| } | |
| // Enhanced copy extraction code - CORRECTED VERSION | |
| function copyExtractionCode() { | |
| const preElement = document.getElementById('extractionCode'); | |
| if (!preElement) { | |
| showToast('error', 'Erro', 'Elemento de código não encontrado'); | |
| return; | |
| } | |
| // Get the exact text content | |
| const codeText = preElement.textContent; | |
| // Enhanced clipboard API with fallback | |
| if (navigator.clipboard && window.isSecureContext) { | |
| navigator.clipboard.writeText(codeText) | |
| .then(() => { | |
| showToast('success', 'Código Copiado!', 'Cole no console da página Douyin'); | |
| }) | |
| .catch(err => { | |
| console.error('Clipboard API failed:', err); | |
| fallbackCopyText(codeText); | |
| }); | |
| } else { | |
| fallbackCopyText(codeText); | |
| } | |
| } | |
| // Fallback copy method | |
| function fallbackCopyText(text) { | |
| try { | |
| const textArea = document.createElement('textarea'); | |
| textArea.value = text; | |
| textArea.style.position = 'fixed'; | |
| textArea.style.left = '-999999px'; | |
| textArea.style.top = '-999999px'; | |
| document.body.appendChild(textArea); | |
| textArea.focus(); | |
| textArea.select(); | |
| const successful = document.execCommand('copy'); | |
| textArea.remove(); | |
| if (successful) { | |
| showToast('success', 'Código Copiado!', 'Método alternativo usado - cole no console da página Douyin'); | |
| } else { | |
| throw new Error('Fallback copy failed'); | |
| } | |
| } catch (err) { | |
| console.error('All copy methods failed:', err); | |
| showToast('error', 'Falha ao Copiar', 'Não foi possível copiar o código automaticamente. Copie manualmente.'); | |
| } | |
| } | |
| // Save result | |
| function saveResult(index, value) { | |
| if (!results[index]) { | |
| results[index] = {}; | |
| } | |
| results[index].v3Link = value.trim(); | |
| results[index].originalUrl = urls[index]; | |
| if (value.trim()) { | |
| // Update URL item status | |
| const urlItem = document.querySelector(`.url-item[data-index="${index}"]`); | |
| if (urlItem) { | |
| urlItem.classList.remove('processing'); | |
| urlItem.classList.add('success'); | |
| const badge = urlItem.querySelector('.status-badge'); | |
| if (badge) { | |
| badge.className = 'status-badge text-xs px-2 py-1 rounded-full bg-green-100 text-green-600'; | |
| badge.textContent = 'Extraído'; | |
| } | |
| } | |
| extractedCount++; | |
| updateStats(); | |
| showToast('success', 'Link Salvo', `Link v3 da URL ${index + 1} salvo com sucesso`); | |
| } | |
| } | |
| // Copy result | |
| function copyResult(index) { | |
| const textarea = document.getElementById(`result-${index}`); | |
| if (!textarea) { | |
| showToast('error', 'Erro', 'Campo de resultado não encontrado'); | |
| return; | |
| } | |
| const value = textarea.value.trim(); | |
| if (!value) { | |
| showToast('warning', 'Nenhum Link', 'Cole um link v3 antes de copiar'); | |
| return; | |
| } | |
| // Enhanced clipboard API with fallback | |
| if (navigator.clipboard && window.isSecureContext) { | |
| navigator.clipboard.writeText(value) | |
| .then(() => { | |
| showToast('success', 'Link Copiado!', `Link da URL ${index + 1} copiado para área de transferência`); | |
| }) | |
| .catch(err => { | |
| console.error('Clipboard failed:', err); | |
| fallbackCopyText(value); | |
| }); | |
| } else { | |
| fallbackCopyText(value); | |
| } | |
| } | |
| // Download result | |
| function downloadResult(index) { | |
| const textarea = document.getElementById(`result-${index}`); | |
| if (!textarea) { | |
| showToast('error', 'Erro', 'Campo de resultado não encontrado'); | |
| return; | |
| } | |
| const value = textarea.value.trim(); | |
| if (!value) { | |
| showToast('warning', 'Nenhum Link', 'Cole um link v3 antes de baixar'); | |
| return; | |
| } | |
| const blob = new Blob([value], { type: 'text/plain' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `douyin_link_${index + 1}.txt`; | |
| a.click(); | |
| URL.revokeObjectURL(url); | |
| showToast('success', 'Download Iniciado', `Download do link da URL ${index + 1} iniciado`); | |
| } | |
| // Export all results | |
| function exportResults() { | |
| const allResults = Array.from(document.querySelectorAll('[id^="result-"]')); | |
| if (allResults.length === 0) { | |
| showToast('warning', 'Sem Resultados', 'Nenhum link v3 foi extraído ainda'); | |
| return; | |
| } | |
| let exportData = ''; | |
| allResults.forEach((textarea, index) => { | |
| const value = textarea.value.trim(); | |
| if (value) { | |
| const url = urls[index] || 'Unknown URL'; | |
| exportData += `URL ${index + 1}: ${url}\nLink v3: ${value}\n\n`; | |
| } | |
| }); | |
| if (!exportData.trim()) { | |
| showToast('warning |