anycoder-feb440bb / index.html
eubottura's picture
Upload folder using huggingface_hub
8f66d34 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 - 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...&#10;Exemplo:&#10;https://www.douyin.com/video/123456789&#10;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