eubottura commited on
Commit
0adc9c4
·
verified ·
1 Parent(s): 9f2e600

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +208 -73
index.html CHANGED
@@ -1,10 +1,9 @@
1
  <!DOCTYPE html>
2
  <html lang="pt-BR">
3
-
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Douyin Video Downloader - Automático</title>
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
  <style>
10
  :root {
@@ -502,6 +501,30 @@
502
  color: var(--error);
503
  }
504
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  @media (max-width: 968px) {
506
  .main-content {
507
  grid-template-columns: 1fr;
@@ -519,9 +542,17 @@
519
  margin-bottom: 15px;
520
  opacity: 0.5;
521
  }
 
 
 
 
 
 
 
 
 
522
  </style>
523
  </head>
524
-
525
  <body>
526
  <header>
527
  <div class="header-content">
@@ -547,11 +578,21 @@
547
  <div class="card-title">Adicionar Links do Douyin</div>
548
  </div>
549
 
 
 
 
 
 
 
 
 
 
 
550
  <div class="input-section">
551
  <label class="input-label">Cole os links dos vídeos (um por linha):</label>
552
- <textarea
553
- id="linksInput"
554
- class="links-textarea"
555
  placeholder="https://www.douyin.com/video/7123456789012345678&#10;https://v.douyin.com/ABC123&#10;https://www.douyin.com/video/7123456789012345679"
556
  ></textarea>
557
  <div id="urlChips" class="url-chips"></div>
@@ -611,13 +652,17 @@
611
  <div class="toggle-switch active" onclick="toggleSetting(this, 'maxQuality')"></div>
612
  </div>
613
  <div class="setting-item">
614
- <span class="setting-label">Notificações de conclusão</span>
615
  <div class="toggle-switch active" onclick="toggleSetting(this, 'notifications')"></div>
616
  </div>
617
  <div class="setting-item">
618
  <span class="setting-label">Processamento simultâneo</span>
619
  <div class="toggle-switch" onclick="toggleSetting(this, 'parallel')"></div>
620
  </div>
 
 
 
 
621
  </div>
622
 
623
  <div style="margin-top: 30px; padding: 20px; background: rgba(254, 44, 85, 0.1); border-radius: 12px;">
@@ -625,11 +670,12 @@
625
  <i class="fas fa-info-circle"></i> Como Funciona
626
  </h3>
627
  <ol style="color: var(--text-secondary); margin-left: 20px; line-height: 1.8;">
 
628
  <li>Cole os links dos vídeos do Douyin na área de texto</li>
629
  <li>Clique em "Iniciar Downloads" para começar o processamento</li>
630
- <li>O sistema extrairá automaticamente os links de download na máxima qualidade</li>
631
- <li>Os vídeos serão baixados automaticamente para sua pasta de downloads</li>
632
- <li>Acompanhe o progresso em tempo real na lista abaixo</li>
633
  </ol>
634
  </div>
635
 
@@ -648,6 +694,14 @@
648
  </div>
649
  </div>
650
  </div>
 
 
 
 
 
 
 
 
651
  </div>
652
  </div>
653
  </div>
@@ -666,19 +720,26 @@
666
  autoDownload: true,
667
  maxQuality: true,
668
  notifications: true,
669
- parallel: false
 
670
  };
671
 
672
  let downloadQueue = [];
673
  let processedCount = 0;
674
  let successCount = 0;
675
  let errorCount = 0;
 
676
 
677
  function toggleSetting(element, setting) {
678
  element.classList.toggle('active');
679
  settings[setting] = element.classList.contains('active');
680
  }
681
 
 
 
 
 
 
682
  function parseLinks() {
683
  const input = document.getElementById('linksInput').value;
684
  const urls = input.split('\n')
@@ -729,63 +790,144 @@
729
  updateProgressDisplay();
730
  }
731
 
732
- async function extractVideoUrl(url) {
 
733
  try {
734
- // Extrair URL alvo
735
- const targetUrl = url.split('?')[0];
736
 
737
- // Identificar ID do vídeo
738
- const urlParams = new URLSearchParams(url.split('?')[1] || '');
739
- let videoId = urlParams.get('modal_id');
 
740
 
741
- if (!videoId) {
742
- const pathMatch = targetUrl.match(/\/video\/(\d+)/);
743
- if (pathMatch) {
744
- videoId = pathMatch[1];
745
- }
 
 
 
746
  }
 
 
 
 
 
 
 
 
 
 
747
 
748
- const finalUrl = videoId ? `https://www.douyin.com/video/${videoId}` : targetUrl;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
749
 
750
- // Gerar hash
751
- const hash = btoa(finalUrl) + (finalUrl.length + 1000) + btoa('aio-dl');
 
752
 
753
- // Fazer chamada API
754
- const response = await fetch('https://api.douyin.wtf/api', {
755
- method: 'POST',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
756
  headers: {
757
- 'Content-Type': 'application/json',
758
- },
759
- body: JSON.stringify({
760
- url: finalUrl,
761
- hash: hash
762
- })
763
  });
764
 
765
- const data = await response.json();
 
 
 
766
 
767
- if (data.success && data.data.medias && data.data.medias.length > 0) {
768
- // Ordenar e selecionar melhor mídia
769
- const sortedMedias = data.data.medias.sort((a, b) => (b.size || 0) - (a.size || 0));
770
- const bestMedia = sortedMedias[0];
771
-
772
  return {
773
  success: true,
774
- downloadUrl: bestMedia.url,
775
- quality: bestMedia.quality || 'HD',
776
- size: bestMedia.formattedSize || 'Unknown',
777
- title: data.data.title || 'Video'
778
  };
779
- } else {
780
- throw new Error('No media found');
781
  }
 
 
782
  } catch (error) {
783
- return {
784
- success: false,
785
- error: error.message,
786
- fallbackUrl: `https://snapdouyin.app/#url=${encodeURIComponent(url)}`
787
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
788
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
789
  }
790
 
791
  function createDownloadItem(url, index) {
@@ -802,6 +944,7 @@
802
  <span id="quality-${index}">Aguardando...</span>
803
  <span id="size-${index}">--</span>
804
  </div>
 
805
  </div>
806
  <div class="download-actions">
807
  <button class="action-btn" onclick="copyLink('${index}')" title="Copiar Link">
@@ -817,9 +960,12 @@
817
 
818
  function updateDownloadStatus(index, status, data = {}) {
819
  const item = document.getElementById(`download-${index}`);
 
 
820
  const statusDiv = item.querySelector('.download-status');
821
  const qualitySpan = document.getElementById(`quality-${index}`);
822
  const sizeSpan = document.getElementById(`size-${index}`);
 
823
 
824
  statusDiv.className = `download-status status-${status}`;
825
 
@@ -838,7 +984,11 @@
838
  case 'error':
839
  statusDiv.innerHTML = '<i class="fas fa-times"></i>';
840
  qualitySpan.textContent = 'Erro';
841
- sizeSpan.textContent = data.error || 'Falha';
 
 
 
 
842
  break;
843
  }
844
  }
@@ -861,7 +1011,7 @@
861
  errorCount++;
862
 
863
  if (settings.notifications) {
864
- showNotification('Falha ao extrair vídeo', 'error');
865
  }
866
  }
867
 
@@ -898,10 +1048,8 @@
898
 
899
  // Processar downloads
900
  if (settings.parallel) {
901
- // Processar todos simultaneamente
902
  await Promise.all(urls.map((url, index) => processDownload(url, index)));
903
  } else {
904
- // Processar sequencialmente
905
  for (let i = 0; i < urls.length; i++) {
906
  await processDownload(urls[i], i);
907
  }
@@ -912,7 +1060,8 @@
912
 
913
  // Notificação final
914
  if (settings.notifications) {
915
- showNotification(`Processamento concluído! ${successCount} de ${urls.length} vídeos extraídos.`, 'success');
 
916
  }
917
  }
918
 
@@ -934,7 +1083,7 @@
934
  const data = downloadQueue[index];
935
  if (data && data.downloadUrl) {
936
  navigator.clipboard.writeText(data.downloadUrl);
937
- showNotification('Link copiado para área de transferência!', 'success');
938
  }
939
  }
940
 
@@ -946,6 +1095,7 @@
946
  const a = document.createElement('a');
947
  a.href = url;
948
  a.download = `douyin_video_${index}.mp4`;
 
949
  document.body.appendChild(a);
950
  a.click();
951
  document.body.removeChild(a);
@@ -962,19 +1112,4 @@
962
  const text = document.getElementById('notificationText');
963
 
964
  notification.className = `notification ${type}`;
965
- icon.className = type === 'success' ? 'fas fa-check' : 'fas fa-exclamation';
966
- text.textContent = message;
967
-
968
- notification.classList.add('show');
969
-
970
- setTimeout(() => {
971
- notification.classList.remove('show');
972
- }, 3000);
973
- }
974
-
975
- // Auto-parse links on input
976
- document.getElementById('linksInput').addEventListener('input', parseLinks);
977
- </script>
978
- </body>
979
-
980
- </html>
 
1
  <!DOCTYPE html>
2
  <html lang="pt-BR">
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Douyin Video Downloader - Melhorado</title>
7
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
8
  <style>
9
  :root {
 
501
  color: var(--error);
502
  }
503
 
504
+ .api-selector {
505
+ margin-bottom: 20px;
506
+ padding: 15px;
507
+ background: var(--bg-dark);
508
+ border-radius: 10px;
509
+ }
510
+
511
+ .api-selector label {
512
+ display: block;
513
+ margin-bottom: 10px;
514
+ color: var(--text-secondary);
515
+ font-size: 0.9rem;
516
+ }
517
+
518
+ .api-selector select {
519
+ width: 100%;
520
+ padding: 10px;
521
+ background: var(--bg-hover);
522
+ border: 1px solid var(--border);
523
+ border-radius: 8px;
524
+ color: var(--text-primary);
525
+ cursor: pointer;
526
+ }
527
+
528
  @media (max-width: 968px) {
529
  .main-content {
530
  grid-template-columns: 1fr;
 
542
  margin-bottom: 15px;
543
  opacity: 0.5;
544
  }
545
+
546
+ .debug-info {
547
+ margin-top: 10px;
548
+ padding: 10px;
549
+ background: rgba(255, 0, 0, 0.1);
550
+ border-radius: 8px;
551
+ font-size: 0.8rem;
552
+ color: var(--error);
553
+ }
554
  </style>
555
  </head>
 
556
  <body>
557
  <header>
558
  <div class="header-content">
 
578
  <div class="card-title">Adicionar Links do Douyin</div>
579
  </div>
580
 
581
+ <div class="api-selector">
582
+ <label for="apiSelect">Selecionar API:</label>
583
+ <select id="apiSelect" onchange="changeApi()">
584
+ <option value="tikmate">TikMate Online</option>
585
+ <option value="snaptik">SnapTik</option>
586
+ <option value="musicallydown">MusicallyDown</option>
587
+ <option value="direct">Método Direto (Experimental)</option>
588
+ </select>
589
+ </div>
590
+
591
  <div class="input-section">
592
  <label class="input-label">Cole os links dos vídeos (um por linha):</label>
593
+ <textarea
594
+ id="linksInput"
595
+ class="links-textarea"
596
  placeholder="https://www.douyin.com/video/7123456789012345678&#10;https://v.douyin.com/ABC123&#10;https://www.douyin.com/video/7123456789012345679"
597
  ></textarea>
598
  <div id="urlChips" class="url-chips"></div>
 
652
  <div class="toggle-switch active" onclick="toggleSetting(this, 'maxQuality')"></div>
653
  </div>
654
  <div class="setting-item">
655
+ <span class="setting-label">Mostrar notificações</span>
656
  <div class="toggle-switch active" onclick="toggleSetting(this, 'notifications')"></div>
657
  </div>
658
  <div class="setting-item">
659
  <span class="setting-label">Processamento simultâneo</span>
660
  <div class="toggle-switch" onclick="toggleSetting(this, 'parallel')"></div>
661
  </div>
662
+ <div class="setting-item">
663
+ <span class="setting-label">Modo Debug (mostrar erros)</span>
664
+ <div class="toggle-switch" onclick="toggleSetting(this, 'debug')"></div>
665
+ </div>
666
  </div>
667
 
668
  <div style="margin-top: 30px; padding: 20px; background: rgba(254, 44, 85, 0.1); border-radius: 12px;">
 
670
  <i class="fas fa-info-circle"></i> Como Funciona
671
  </h3>
672
  <ol style="color: var(--text-secondary); margin-left: 20px; line-height: 1.8;">
673
+ <li>Selecione uma API funcional no menu suspenso</li>
674
  <li>Cole os links dos vídeos do Douyin na área de texto</li>
675
  <li>Clique em "Iniciar Downloads" para começar o processamento</li>
676
+ <li>O sistema tentará extrair os links usando múltiplos métodos</li>
677
+ <li>Se falhar, tente outra API da lista</li>
678
+ <li>Use o modo Debug para ver detalhes dos erros</li>
679
  </ol>
680
  </div>
681
 
 
694
  </div>
695
  </div>
696
  </div>
697
+
698
+ <div style="margin-top: 20px; padding: 15px; background: rgba(255, 170, 0, 0.1); border-radius: 12px;">
699
+ <p style="color: var(--warning); font-size: 0.9rem;">
700
+ <i class="fas fa-exclamation-triangle"></i>
701
+ <strong>Atenção:</strong> APIs de terceiros podem ficar indisponíveis.
702
+ Se uma falhar, experimente outra opção.
703
+ </p>
704
+ </div>
705
  </div>
706
  </div>
707
  </div>
 
720
  autoDownload: true,
721
  maxQuality: true,
722
  notifications: true,
723
+ parallel: false,
724
+ debug: false
725
  };
726
 
727
  let downloadQueue = [];
728
  let processedCount = 0;
729
  let successCount = 0;
730
  let errorCount = 0;
731
+ let currentApi = 'tikmate';
732
 
733
  function toggleSetting(element, setting) {
734
  element.classList.toggle('active');
735
  settings[setting] = element.classList.contains('active');
736
  }
737
 
738
+ function changeApi() {
739
+ currentApi = document.getElementById('apiSelect').value;
740
+ showNotification(`API alterada para: ${currentApi}`, 'success');
741
+ }
742
+
743
  function parseLinks() {
744
  const input = document.getElementById('linksInput').value;
745
  const urls = input.split('\n')
 
790
  updateProgressDisplay();
791
  }
792
 
793
+ // Métodos de extração alternativos
794
+ async function extractWithTikmate(url) {
795
  try {
796
+ const response = await fetch(`https://tikmate.online/download?url=${encodeURIComponent(url)}`);
797
+ const html = await response.text();
798
 
799
+ // Parser simples para extrair o link
800
+ const parser = new DOMParser();
801
+ const doc = parser.parseFromString(html, 'text/html');
802
+ const downloadLink = doc.querySelector('a[href*=".mp4"]');
803
 
804
+ if (downloadLink) {
805
+ return {
806
+ success: true,
807
+ downloadUrl: downloadLink.href,
808
+ quality: 'HD',
809
+ size: 'Unknown',
810
+ title: 'Douyin Video'
811
+ };
812
  }
813
+ throw new Error('No download link found');
814
+ } catch (error) {
815
+ return { success: false, error: error.message };
816
+ }
817
+ }
818
+
819
+ async function extractWithSnaptik(url) {
820
+ try {
821
+ const response = await fetch(`https://snaptik.app/abc?url=${encodeURIComponent(url)}`);
822
+ const data = await response.json();
823
 
824
+ if (data.status === 'success' && data.data) {
825
+ return {
826
+ success: true,
827
+ downloadUrl: data.data,
828
+ quality: 'HD',
829
+ size: 'Unknown',
830
+ title: 'Douyin Video'
831
+ };
832
+ }
833
+ throw new Error(data.message || 'Failed to extract');
834
+ } catch (error) {
835
+ return { success: false, error: error.message };
836
+ }
837
+ }
838
+
839
+ async function extractWithMusicallydown(url) {
840
+ try {
841
+ const response = await fetch(`https://musicallydown.com/download?url=${encodeURIComponent(url)}`);
842
+ const html = await response.text();
843
 
844
+ const parser = new DOMParser();
845
+ const doc = parser.parseFromString(html, 'text/html');
846
+ const downloadLink = doc.querySelector('a.download-link');
847
 
848
+ if (downloadLink) {
849
+ return {
850
+ success: true,
851
+ downloadUrl: downloadLink.href,
852
+ quality: 'HD',
853
+ size: 'Unknown',
854
+ title: 'Douyin Video'
855
+ };
856
+ }
857
+ throw new Error('No download link found');
858
+ } catch (error) {
859
+ return { success: false, error: error.message };
860
+ }
861
+ }
862
+
863
+ async function extractDirect(url) {
864
+ try {
865
+ // Tentativa direta com headers customizados
866
+ const response = await fetch(url, {
867
  headers: {
868
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
869
+ }
 
 
 
 
870
  });
871
 
872
+ const html = await response.text();
873
+
874
+ // Procurar por padrões de vídeo no HTML
875
+ const videoMatch = html.match(/"play_addr":\s*{\s*"url_list":\s*\["([^"]+)"\]/);
876
 
877
+ if (videoMatch && videoMatch[1]) {
 
 
 
 
878
  return {
879
  success: true,
880
+ downloadUrl: videoMatch[1].replace(/\\\\u002F/g, '/'),
881
+ quality: 'HD',
882
+ size: 'Unknown',
883
+ title: 'Douyin Video'
884
  };
 
 
885
  }
886
+
887
+ throw new Error('Direct extraction failed');
888
  } catch (error) {
889
+ return { success: false, error: error.message };
890
+ }
891
+ }
892
+
893
+ async function extractVideoUrl(url) {
894
+ let result = { success: false, error: 'All methods failed' };
895
+
896
+ // Tentar diferentes métodos baseado na API selecionada
897
+ switch(currentApi) {
898
+ case 'tikmate':
899
+ result = await extractWithTikmate(url);
900
+ break;
901
+ case 'snaptik':
902
+ result = await extractWithSnaptik(url);
903
+ break;
904
+ case 'musicallydown':
905
+ result = await extractWithMusicallydown(url);
906
+ break;
907
+ case 'direct':
908
+ result = await extractDirect(url);
909
+ break;
910
  }
911
+
912
+ // Se falhar, tentar fallback com outras APIs
913
+ if (!result.success) {
914
+ if (settings.debug) {
915
+ console.log(`${currentApi} failed, trying fallback...`);
916
+ }
917
+
918
+ const fallbackMethods = [extractWithTikmate, extractWithSnaptik, extractWithMusicallydown, extractDirect];
919
+
920
+ for (const method of fallbackMethods) {
921
+ if (!result.success) {
922
+ result = await method(url);
923
+ if (result.success && settings.debug) {
924
+ console.log('Success with fallback method');
925
+ }
926
+ }
927
+ }
928
+ }
929
+
930
+ return result;
931
  }
932
 
933
  function createDownloadItem(url, index) {
 
944
  <span id="quality-${index}">Aguardando...</span>
945
  <span id="size-${index}">--</span>
946
  </div>
947
+ <div id="debug-${index}" class="debug-info" style="display: none;"></div>
948
  </div>
949
  <div class="download-actions">
950
  <button class="action-btn" onclick="copyLink('${index}')" title="Copiar Link">
 
960
 
961
  function updateDownloadStatus(index, status, data = {}) {
962
  const item = document.getElementById(`download-${index}`);
963
+ if (!item) return;
964
+
965
  const statusDiv = item.querySelector('.download-status');
966
  const qualitySpan = document.getElementById(`quality-${index}`);
967
  const sizeSpan = document.getElementById(`size-${index}`);
968
+ const debugDiv = document.getElementById(`debug-${index}`);
969
 
970
  statusDiv.className = `download-status status-${status}`;
971
 
 
984
  case 'error':
985
  statusDiv.innerHTML = '<i class="fas fa-times"></i>';
986
  qualitySpan.textContent = 'Erro';
987
+ sizeSpan.textContent = 'Falha na extração';
988
+ if (settings.debug && debugDiv) {
989
+ debugDiv.style.display = 'block';
990
+ debugDiv.textContent = `Erro: ${data.error || 'Unknown error'}`;
991
+ }
992
  break;
993
  }
994
  }
 
1011
  errorCount++;
1012
 
1013
  if (settings.notifications) {
1014
+ showNotification(`Falha: ${result.error}`, 'error');
1015
  }
1016
  }
1017
 
 
1048
 
1049
  // Processar downloads
1050
  if (settings.parallel) {
 
1051
  await Promise.all(urls.map((url, index) => processDownload(url, index)));
1052
  } else {
 
1053
  for (let i = 0; i < urls.length; i++) {
1054
  await processDownload(urls[i], i);
1055
  }
 
1060
 
1061
  // Notificação final
1062
  if (settings.notifications) {
1063
+ const message = `Concluído! ${successCount} de ${urls.length} vídeos extraídos.`;
1064
+ showNotification(message, successCount > 0 ? 'success' : 'error');
1065
  }
1066
  }
1067
 
 
1083
  const data = downloadQueue[index];
1084
  if (data && data.downloadUrl) {
1085
  navigator.clipboard.writeText(data.downloadUrl);
1086
+ showNotification('Link copiado!', 'success');
1087
  }
1088
  }
1089
 
 
1095
  const a = document.createElement('a');
1096
  a.href = url;
1097
  a.download = `douyin_video_${index}.mp4`;
1098
+ a.target = '_blank';
1099
  document.body.appendChild(a);
1100
  a.click();
1101
  document.body.removeChild(a);
 
1112
  const text = document.getElementById('notificationText');
1113
 
1114
  notification.className = `notification ${type}`;
1115
+ icon.className = type === 'success' ?