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

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +910 -741
index.html CHANGED
@@ -1,811 +1,980 @@
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 Extractor - Userscript</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 {
10
- --primary: #fe2c55;
11
- --secondary: #25f4ee;
12
- --bg-dark: #161823;
13
- --bg-card: #1f1f2e;
14
- --bg-hover: #2a2a3e;
15
- --text-primary: #ffffff;
16
- --text-secondary: #a0a0b0;
17
- --border: #2a2a3e;
18
- --success: #00ff88;
19
- --warning: #ffaa00;
20
- --error: #ff3366;
21
- --gradient: linear-gradient(135deg, #fe2c55 0%, #25f4ee 100%);
22
- }
23
 
24
- * {
25
- margin: 0;
26
- padding: 0;
27
- box-sizing: border-box;
28
- }
29
 
30
- body {
31
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
32
- background: linear-gradient(135deg, #0f0f1e 0%, #1a1a2e 100%);
33
- color: var(--text-primary);
34
- min-height: 100vh;
35
- display: flex;
36
- flex-direction: column;
37
- }
38
 
39
- .container {
40
- max-width: 1400px;
41
- margin: 0 auto;
42
- padding: 20px;
43
- flex: 1;
44
- width: 100%;
45
- }
46
 
47
- header {
48
- background: rgba(31, 31, 46, 0.8);
49
- backdrop-filter: blur(10px);
50
- border-bottom: 1px solid var(--border);
51
- padding: 20px 0;
52
- position: sticky;
53
- top: 0;
54
- z-index: 100;
55
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
56
- }
57
 
58
- .header-content {
59
- display: flex;
60
- justify-content: space-between;
61
- align-items: center;
62
- max-width: 1400px;
63
- margin: 0 auto;
64
- padding: 0 20px;
65
- }
66
 
67
- .logo {
68
- display: flex;
69
- align-items: center;
70
- gap: 15px;
71
- }
72
 
73
- .logo-icon {
74
- width: 50px;
75
- height: 50px;
76
- background: var(--gradient);
77
- border-radius: 12px;
78
- display: flex;
79
- align-items: center;
80
- justify-content: center;
81
- font-size: 24px;
82
- animation: pulse 2s infinite;
83
- }
84
 
85
- @keyframes pulse {
86
- 0%, 100% { transform: scale(1); }
87
- 50% { transform: scale(1.05); }
88
- }
89
 
90
- h1 {
91
- font-size: 1.8rem;
92
- font-weight: 700;
93
- background: var(--gradient);
94
- -webkit-background-clip: text;
95
- -webkit-text-fill-color: transparent;
96
- background-clip: text;
97
- }
98
 
99
- .built-with {
100
- color: var(--text-secondary);
101
- text-decoration: none;
102
- font-size: 0.9rem;
103
- transition: color 0.3s;
104
- }
105
 
106
- .built-with:hover {
107
- color: var(--secondary);
108
- }
109
 
110
- .tabs {
111
- display: flex;
112
- gap: 10px;
113
- margin: 30px 0;
114
- background: var(--bg-card);
115
- padding: 8px;
116
- border-radius: 12px;
117
- width: fit-content;
118
- }
119
 
120
- .tab {
121
- padding: 12px 24px;
122
- background: transparent;
123
- border: none;
124
- color: var(--text-secondary);
125
- cursor: pointer;
126
- border-radius: 8px;
127
- font-size: 1rem;
128
- font-weight: 500;
129
- transition: all 0.3s;
130
- display: flex;
131
- align-items: center;
132
- gap: 8px;
133
- }
134
 
135
- .tab.active {
136
- background: var(--gradient);
137
- color: white;
138
- }
139
 
140
- .tab:hover:not(.active) {
141
- background: var(--bg-hover);
142
- color: var(--text-primary);
143
- }
 
 
144
 
145
- .content-area {
146
- display: grid;
147
- grid-template-columns: 1fr 1fr;
148
- gap: 30px;
149
- margin-bottom: 30px;
150
- }
 
 
 
 
151
 
152
- .card {
153
- background: rgba(31, 31, 46, 0.6);
154
- backdrop-filter: blur(10px);
155
- border: 1px solid var(--border);
156
- border-radius: 20px;
157
- padding: 30px;
158
- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
159
- transition: transform 0.3s, box-shadow 0.3s;
160
- }
161
 
162
- .card:hover {
163
- transform: translateY(-5px);
164
- box-shadow: 0 15px 50px rgba(254, 44, 85, 0.2);
165
- }
166
 
167
- .card-header {
168
- display: flex;
169
- align-items: center;
170
- gap: 15px;
171
- margin-bottom: 25px;
172
- }
173
 
174
- .card-icon {
175
- width: 45px;
176
- height: 45px;
177
- background: var(--gradient);
178
- border-radius: 10px;
179
- display: flex;
180
- align-items: center;
181
- justify-content: center;
182
- font-size: 20px;
183
- }
 
 
184
 
185
- .card-title {
186
- font-size: 1.3rem;
187
- font-weight: 600;
188
- }
189
 
190
- .code-block {
191
- background: #0a0a0f;
192
- border: 1px solid var(--border);
193
- border-radius: 12px;
194
- padding: 20px;
195
- margin: 20px 0;
196
- position: relative;
197
- overflow: hidden;
198
- }
199
 
200
- .code-header {
201
- display: flex;
202
- justify-content: space-between;
203
- align-items: center;
204
- margin-bottom: 15px;
205
- padding-bottom: 10px;
206
- border-bottom: 1px solid var(--border);
207
- }
 
 
 
208
 
209
- .code-lang {
210
- color: var(--secondary);
211
- font-size: 0.9rem;
212
- font-weight: 500;
213
- }
214
 
215
- .copy-btn {
216
- background: var(--gradient);
217
- color: white;
218
- border: none;
219
- padding: 8px 16px;
220
- border-radius: 6px;
221
- cursor: pointer;
222
- font-size: 0.9rem;
223
- transition: all 0.3s;
224
- display: flex;
225
- align-items: center;
226
- gap: 5px;
227
- }
228
 
229
- .copy-btn:hover {
230
- transform: scale(1.05);
231
- box-shadow: 0 4px 15px rgba(254, 44, 85, 0.4);
232
- }
233
 
234
- .copy-btn.copied {
235
- background: var(--success);
236
- }
 
 
237
 
238
- pre {
239
- color: #e6e6e6;
240
- font-family: 'Courier New', Consolas, monospace;
241
- font-size: 0.9rem;
242
- line-height: 1.6;
243
- overflow-x: auto;
244
- margin: 0;
245
- }
 
 
 
 
 
 
246
 
247
- .instructions {
248
- background: rgba(37, 244, 238, 0.1);
249
- border: 1px solid rgba(37, 244, 238, 0.3);
250
- border-radius: 12px;
251
- padding: 20px;
252
- margin: 20px 0;
253
- }
254
 
255
- .instructions h3 {
256
- color: var(--secondary);
257
- margin-bottom: 15px;
258
- display: flex;
259
- align-items: center;
260
- gap: 10px;
261
- }
262
 
263
- .instructions ol {
264
- margin-left: 20px;
265
- color: var(--text-secondary);
266
- }
267
 
268
- .instructions li {
269
- margin: 10px 0;
270
- line-height: 1.6;
271
- }
272
 
273
- .feature-list {
274
- display: grid;
275
- gap: 15px;
276
- margin: 20px 0;
277
- }
278
 
279
- .feature-item {
280
- display: flex;
281
- align-items: start;
282
- gap: 15px;
283
- padding: 15px;
284
- background: rgba(255, 255, 255, 0.02);
285
- border-radius: 10px;
286
- transition: all 0.3s;
287
- }
288
 
289
- .feature-item:hover {
290
- background: rgba(255, 255, 255, 0.05);
291
- transform: translateX(5px);
292
- }
 
 
293
 
294
- .feature-icon {
295
- width: 35px;
296
- height: 35px;
297
- background: var(--gradient);
298
- border-radius: 8px;
299
- display: flex;
300
- align-items: center;
301
- justify-content: center;
302
- font-size: 16px;
303
- flex-shrink: 0;
304
- }
305
 
306
- .demo-section {
307
- background: var(--bg-card);
308
- border-radius: 20px;
309
- padding: 40px;
310
- margin: 30px 0;
311
- text-align: center;
312
- }
313
 
314
- .demo-title {
315
- font-size: 1.5rem;
316
- margin-bottom: 20px;
317
- background: var(--gradient);
318
- -webkit-background-clip: text;
319
- -webkit-text-fill-color: transparent;
320
- background-clip: text;
321
- }
322
 
323
- .demo-buttons {
324
- display: flex;
325
- gap: 15px;
326
- justify-content: center;
327
- flex-wrap: wrap;
328
- }
 
 
 
 
 
 
329
 
330
- .demo-btn {
331
- padding: 12px 30px;
332
- background: var(--gradient);
333
- color: white;
334
- border: none;
335
- border-radius: 10px;
336
- font-size: 1rem;
337
- font-weight: 600;
338
- cursor: pointer;
339
- transition: all 0.3s;
340
- display: flex;
341
- align-items: center;
342
- gap: 10px;
343
- }
344
 
345
- .demo-btn:hover {
346
- transform: translateY(-3px);
347
- box-shadow: 0 5px 20px rgba(254, 44, 85, 0.4);
348
- }
 
349
 
350
- .preview-window {
351
- background: #0a0a0f;
352
- border: 1px solid var(--border);
353
- border-radius: 12px;
354
- padding: 20px;
355
- margin: 20px 0;
356
- font-family: monospace;
357
- font-size: 0.9rem;
358
- max-height: 300px;
359
- overflow-y: auto;
360
- }
361
 
362
- .status-indicator {
363
- display: inline-flex;
364
- align-items: center;
365
- gap: 8px;
366
- padding: 6px 12px;
367
- border-radius: 20px;
368
- font-size: 0.85rem;
369
- font-weight: 500;
370
- }
371
 
372
- .status-active {
373
- background: rgba(0, 255, 136, 0.1);
374
- color: var(--success);
375
- border: 1px solid rgba(0, 255, 136, 0.3);
376
- }
 
 
 
 
377
 
378
- .status-inactive {
379
- background: rgba(255, 170, 0, 0.1);
380
- color: var(--warning);
381
- border: 1px solid rgba(255, 170, 0, 0.3);
382
- }
383
 
384
- @media (max-width: 968px) {
385
- .content-area {
386
- grid-template-columns: 1fr;
387
- }
388
-
389
- .header-content {
390
- flex-direction: column;
391
- gap: 15px;
392
- }
393
- }
394
 
395
- .highlight {
396
- background: rgba(254, 44, 85, 0.1);
397
- border-left: 3px solid var(--primary);
398
- padding: 15px;
399
- margin: 20px 0;
400
- border-radius: 8px;
401
- }
402
 
403
- .highlight strong {
404
- color: var(--primary);
405
- }
406
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
  </head>
 
408
  <body>
409
- <header>
410
- <div class="header-content">
411
- <div class="logo">
412
- <div class="logo-icon">
413
- <i class="fas fa-code"></i>
414
- </div>
415
- <h1>Douyin Video Extractor</h1>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
  </div>
417
- <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">
418
- Built with anycoder
419
- </a>
 
 
420
  </div>
421
- </header>
422
-
423
- <div class="container">
424
- <div class="tabs">
425
- <button class="tab active" onclick="switchTab('userscript')">
426
- <i class="fas fa-file-code"></i>
427
- Userscript
428
- </button>
429
- <button class="tab" onclick="switchTab('bookmarklet')">
430
- <i class="fas fa-bookmark"></i>
431
- Bookmarklet
432
- </button>
433
- <button class="tab" onclick="switchTab('demo')">
434
- <i class="fas fa-play"></i>
435
- Demo
436
- </button>
437
  </div>
438
 
439
- <div id="userscript-tab" class="tab-content">
440
- <div class="content-area">
441
- <div class="card">
442
- <div class="card-header">
443
- <div class="card-icon">
444
- <i class="fas fa-download"></i>
445
- </div>
446
- <div class="card-title">Userscript para Douyin</div>
447
- </div>
448
-
449
- <div class="instructions">
450
- <h3><i class="fas fa-info-circle"></i> Como Instalar</h3>
451
- <ol>
452
- <li>Instale a extensão <strong>Tampermonkey</strong> no seu navegador</li>
453
- <li>Clique no botão "Copiar Código" abaixo</li>
454
- <li>Abra o Tampermonkey e clique em "Criar novo script"</li>
455
- <li>Apague o código padrão e cole o código copiado</li>
456
- <li>Salve o script (Ctrl+S)</li>
457
- <li>Acesse qualquer vídeo no Douyin e o botão de download aparecerá</li>
458
- </ol>
459
- </div>
460
-
461
- <div class="code-block">
462
- <div class="code-header">
463
- <span class="code-lang">JavaScript (Userscript)</span>
464
- <button class="copy-btn" onclick="copyCode(this, 'userscript-code')">
465
- <i class="fas fa-copy"></i>
466
- Copiar Código
467
- </button>
468
- </div>
469
- <pre id="userscript-code">// ==UserScript==
470
- // @name Douyin Video Downloader
471
- // @namespace http://tampermonkey.net/
472
- // @version 1.0
473
- // @description Download Douyin videos directly from the page
474
- // @author You
475
- // @match https://www.douyin.com/*
476
- // @match https://v.douyin.com/*
477
- // @grant none
478
- // ==/UserScript==
479
-
480
- (function() {
481
- 'use strict';
482
-
483
- // Função principal de extração
484
- async function extractVideoUrl() {
485
- try {
486
- // Passo a: Extrair URL alvo
487
- const targetUrl = window.location.href.split('?')[0];
488
-
489
- // Passo b: Identificar ID do vídeo
490
- const urlParams = new URLSearchParams(window.location.search);
491
- let videoId = urlParams.get('modal_id');
492
-
493
- if (!videoId) {
494
- // Extrair ID da URL atual
495
- const pathMatch = targetUrl.match(/\/video\/(\d+)/);
496
- if (pathMatch) {
497
- videoId = pathMatch[1];
498
- }
499
- }
500
-
501
- const finalUrl = videoId ? `https://www.douyin.com/video/${videoId}` : targetUrl;
502
-
503
- // Passo c: Gerar hash
504
- const hash = btoa(finalUrl) + (finalUrl.length + 1000) + btoa('aio-dl');
505
-
506
- // Passo d: Fazer chamada API
507
- const response = await fetch('https://api.douyin.wtf/api', {
508
- method: 'POST',
509
- headers: {
510
- 'Content-Type': 'application/json',
511
- },
512
- body: JSON.stringify({
513
- url: finalUrl,
514
- hash: hash
515
- })
516
- });
517
-
518
- const data = await response.json();
519
-
520
- if (data.success && data.data.medias && data.data.medias.length > 0) {
521
- // Passo f: Ordenar e selecionar melhor mídia
522
- const sortedMedias = data.data.medias.sort((a, b) => (b.size || 0) - (a.size || 0));
523
- const bestMedia = sortedMedias[0];
524
-
525
- // Passo g: Extrair link de download
526
- return {
527
- success: true,
528
- downloadUrl: bestMedia.url,
529
- quality: bestMedia.quality || 'Unknown',
530
- size: bestMedia.formattedSize || 'Unknown'
531
- };
532
- } else {
533
- throw new Error('No media found');
534
- }
535
- } catch (error) {
536
- // Passo h: Fallback
537
- return {
538
- success: false,
539
- fallbackUrl: `https://snapdouyin.app/#url=${encodeURIComponent(window.location.href)}`
540
- };
541
- }
542
  }
543
 
544
- // Criar botão de download
545
- function createDownloadButton() {
546
- const button = document.createElement('button');
547
- button.innerHTML = '<i class="fas fa-download"></i> Download Vídeo';
548
- button.style.cssText = `
549
- position: fixed;
550
- top: 100px;
551
- right: 20px;
552
- z-index: 9999;
553
- padding: 12px 20px;
554
- background: linear-gradient(135deg, #fe2c55 0%, #25f4ee 100%);
555
- color: white;
556
- border: none;
557
- border-radius: 10px;
558
- font-size: 14px;
559
- font-weight: 600;
560
- cursor: pointer;
561
- box-shadow: 0 4px 15px rgba(254, 44, 85, 0.3);
562
- transition: all 0.3s;
 
 
 
563
  `;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564
 
565
- button.onmouseover = () => {
566
- button.style.transform = 'translateY(-3px)';
567
- button.style.boxShadow = '0 6px 20px rgba(254, 44, 85, 0.4)';
568
- };
569
 
570
- button.onmouseout = () => {
571
- button.style.transform = 'translateY(0)';
572
- button.style.boxShadow = '0 4px 15px rgba(254, 44, 85, 0.3)';
573
- };
 
 
574
 
575
- button.onclick = async function() {
576
- button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processando...';
577
- button.disabled = true;
578
-
579
- const result = await extractVideoUrl();
580
-
581
- if (result.success) {
582
- // Criar popup com link
583
- showDownloadPopup(result.downloadUrl, result.quality, result.size);
584
- } else {
585
- window.open(result.fallbackUrl, '_blank');
586
- }
587
-
588
- button.innerHTML = '<i class="fas fa-download"></i> Download Vídeo';
589
- button.disabled = false;
590
- };
591
 
592
- return button;
593
- }
594
-
595
- // Mostrar popup de download
596
- function showDownloadPopup(url, quality, size) {
597
- const popup = document.createElement('div');
598
- popup.style.cssText = `
599
- position: fixed;
600
- top: 50%;
601
- left: 50%;
602
- transform: translate(-50%, -50%);
603
- background: #1a1a2e;
604
- border: 2px solid #fe2c55;
605
- border-radius: 15px;
606
- padding: 30px;
607
- z-index: 10000;
608
- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
609
- max-width: 500px;
610
- width: 90%;
611
- `;
612
 
613
- popup.innerHTML = `
614
- <h3 style="margin: 0 0 15px 0; color: #fe2c55;">✨ Link de Download Pronto!</h3>
615
- <div style="margin: 15px 0;">
616
- <p style="color: #25f4ee; margin: 5px 0;"><strong>Qualidade:</strong> ${quality}</p>
617
- <p style="color: #25f4ee; margin: 5px 0;"><strong>Tamanho:</strong> ${size}</p>
618
- </div>
619
- <div style="background: #0a0a0f; padding: 10px; border-radius: 8px; margin: 15px 0; word-break: break-all;">
620
- <code style="color: #ffffff; font-size: 12px;">${url}</code>
621
- </div>
622
- <div style="display: flex; gap: 10px; margin-top: 20px;">
623
- <button onclick="window.open('${url}', '_blank')" style="flex: 1; padding: 10px; background: #fe2c55; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 600;">
624
- <i class="fas fa-download"></i> Baixar
625
- </button>
626
- <button onclick="navigator.clipboard.writeText('${url}'); this.innerHTML='✓ Copiado!'; setTimeout(() => this.innerHTML='<i class="fas fa-copy"></i> Copiar', 2000)" style="flex: 1; padding: 10px; background: #25f4ee; color: #000; border: none; border-radius: 8px; cursor: pointer; font-weight: 600;">
627
- <i class="fas fa-copy"></i> Copiar Link
628
- </button>
629
- <button onclick="this.parentElement.parentElement.remove()" style="padding: 10px 20px; background: #444; color: white; border: none; border-radius: 8px; cursor: pointer;">
630
- Fechar
631
- </button>
632
- </div>
633
- `;
634
 
635
- document.body.appendChild(popup);
636
- }
637
-
638
- // Inicializar
639
- setTimeout(() => {
640
- const button = createDownloadButton();
641
- document.body.appendChild(button);
642
- }, 2000);
643
-
644
- })();</pre>
645
- </div>
646
- </div>
647
-
648
- <div class="card">
649
- <div class="card-header">
650
- <div class="card-icon">
651
- <i class="fas fa-star"></i>
652
- </div>
653
- <div class="card-title">Recursos do Script</div>
654
- </div>
655
-
656
- <div class="feature-list">
657
- <div class="feature-item">
658
- <div class="feature-icon">
659
- <i class="fas fa-bolt"></i>
660
- </div>
661
- <div>
662
- <strong>Execução Automática</strong>
663
- <p style="color: var(--text-secondary); margin-top: 5px;">O script detecta automaticamente quando você está em uma página de vídeo do Douyin</p>
664
- </div>
665
- </div>
666
-
667
- <div class="feature-item">
668
- <div class="feature-icon">
669
- <i class="fas fa-link"></i>
670
- </div>
671
- <div>
672
- <strong>Extração Inteligente</strong>
673
- <p style="color: var(--text-secondary); margin-top: 5px;">Utiliza a lógica exata do v3 para extrair a URL correta do vídeo</p>
674
- </div>
675
- </div>
676
-
677
- <div class="feature-item">
678
- <div class="feature-icon">
679
- <i class="fas fa-hd"></i>
680
- </div>
681
- <div>
682
- <strong>Melhor Qualidade</strong>
683
- <p style="color: var(--text-secondary); margin-top: 5px;">Sempre seleciona a versão de maior qualidade disponível</p>
684
- </div>
685
- </div>
686
-
687
- <div class="feature-item">
688
- <div class="feature-icon">
689
- <i class="fas fa-shield-alt"></i>
690
- </div>
691
- <div>
692
- <strong>Fallback Automático</strong>
693
- <p style="color: var(--text-secondary); margin-top: 5px;">Se falhar, redireciona para alternativa automaticamente</p>
694
- </div>
695
- </div>
696
-
697
- <div class="feature-item">
698
- <div class="feature-icon">
699
- <i class="fas fa-copy"></i>
700
- </div>
701
- <div>
702
- <strong>Copiar Link</strong>
703
- <p style="color: var(--text-secondary); margin-top: 5px;">Copia o link de download direto para a área de transferência</p>
704
- </div>
705
- </div>
706
-
707
- <div class="feature-item">
708
- <div class="feature-icon">
709
- <i class="fas fa-mobile-alt"></i>
710
- </div>
711
- <div>
712
- <strong>Interface Responsiva</strong>
713
- <p style="color: var(--text-secondary); margin-top: 5px;">Funciona perfeitamente em desktop e dispositivos móveis</p>
714
- </div>
715
- </div>
716
- </div>
717
-
718
- <div class="highlight">
719
- <strong>⚡ Dica Pro:</strong> Depois de instalar, você verá um botão "Download Vídeo" flutuante no canto superior direito de qualquer página de vídeo do Douyin. Basta clicar e aguardar o processamento!
720
- </div>
721
- </div>
722
- </div>
723
- </div>
724
 
725
- <div id="bookmarklet-tab" class="tab-content" style="display: none;">
726
- <div class="card">
727
- <div class="card-header">
728
- <div class="card-icon">
729
- <i class="fas fa-bookmark"></i>
730
- </div>
731
- <div class="card-title">Bookmarklet para Download Rápido</div>
732
- </div>
733
-
734
- <div class="instructions">
735
- <h3><i class="fas fa-info-circle"></i> Como Usar o Bookmarklet</h3>
736
- <ol>
737
- <li>Clique e arraste o botão abaixo para a sua barra de favoritos</li>
738
- <li>Ou clique com o direito e selecione "Adicionar aos favoritos"</li>
739
- <li>Acesse qualquer vídeo no Douyin</li>
740
- <li>Clique no favorito salvo na sua barra</li>
741
- <li>O script será executado e extrairá o link de download</li>
742
- </ol>
743
- </div>
744
-
745
- <div style="text-align: center; margin: 30px 0;">
746
- <a href="javascript:(function(){async function extractVideoUrl(){try{const targetUrl=window.location.href.split('?')[0];const urlParams=new URLSearchParams(window.location.search);let videoId=urlParams.get('modal_id');if(!videoId){const pathMatch=targetUrl.match(/\/video\/(\d+)/);if(pathMatch){videoId=pathMatch[1];}}const finalUrl=videoId?`https://www.douyin.com/video/${videoId}`:targetUrl;const hash=btoa(finalUrl)+(finalUrl.length+1000)+btoa('aio-dl');const response=await fetch('https://api.douyin.wtf/api',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:finalUrl,hash:hash})});const data=await response.json();if(data.success&&data.data.medias&&data.data.medias.length>0){const sortedMedias=data.data.medias.sort((a,b)=>(b.size||0)-(a.size||0));const bestMedia=sortedMedias[0];return{success:true,downloadUrl:bestMedia.url,quality:bestMedia.quality||'Unknown',size:bestMedia.formattedSize||'Unknown'};}else{throw new Error('No media found');}}catch(error){return{success:false,fallbackUrl:`https://snapdouyin.app/#url=${encodeURIComponent(window.location.href)}`};}}async function main(){const result=await extractVideoUrl();if(result.success){const popup=document.createElement('div');popup.style.cssText='position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#1a1a2e;border:2px solid #fe2c55;border-radius:15px;padding:30px;z-index:99999;box-shadow:0 10px 40px rgba(0,0,0,0.5);max-width:500px;width:90%;';popup.innerHTML=`<h3 style="margin:0 0 15px 0;color:#fe2c55;">✨ Link de Download!</h3><div style="margin:15px 0;"><p style="color:#25f4ee;margin:5px 0;"><strong>Qualidade:</strong> ${result.quality}</p><p style="color:#25f4ee;margin:5px 0;"><strong>Tamanho:</strong> ${result.size}</p></div><div style="background:#0a0a0f;padding:10px;border-radius:8px;margin:15px 0;word-break:break-all;"><code style="color:#ffffff;font-size:12px;">${result.downloadUrl}</code></div><div style="display:flex;gap:10px;margin-top:20px;"><button onclick="window.open('${result.downloadUrl}','_blank')" style="flex:1;padding:10px;background:#fe2c55;color:white;border:none;border-radius:8px;cursor:pointer;font-weight:600;">📥 Baixar</button><button onclick="navigator.clipboard.writeText('${result.downloadUrl}');this.innerHTML='✓ Copiado!';setTimeout(()=>this.innerHTML='📋 Copiar',2000)" style="flex:1;padding:10px;background:#25f4ee;color:#000;border:none;border-radius:8px;cursor:pointer;font-weight:600;">📋 Copiar</button><button onclick="this.parentElement.parentElement.remove()" style="padding:10px 20px;background:#444;color:white;border:none;border-radius:8px;cursor:pointer;">✖ Fechar</button></div>`;document.body.appendChild(popup);}else{window.open(result.fallbackUrl,'_blank');}}main();})();" class="demo-btn" style="text-decoration: none; display: inline-block;">
747
- <i class="fas fa-download"></i>
748
- Douyin Downloader
749
- </a>
750
- <p style="color: var(--text-secondary); margin-top: 15px;">
751
- <i class="fas fa-arrow-up"></i> Arraste este botão para sua barra de favoritos
752
- </p>
753
- </div>
754
-
755
- <div class="code-block">
756
- <div class="code-header">
757
- <span class="code-lang">JavaScript (Bookmarklet)</span>
758
- <button class="copy-btn" onclick="copyCode(this, 'bookmarklet-code')">
759
- <i class="fas fa-copy"></i>
760
- Copiar Código
761
- </button>
762
- </div>
763
- <pre id="bookmarklet-code">javascript:(function(){async function extractVideoUrl(){try{const targetUrl=window.location.href.split('?')[0];const urlParams=new URLSearchParams(window.location.search);let videoId=urlParams.get('modal_id');if(!videoId){const pathMatch=targetUrl.match(/\/video\/(\d+)/);if(pathMatch){videoId=pathMatch[1];}}const finalUrl=videoId?`https://www.douyin.com/video/${videoId}`:targetUrl;const hash=btoa(finalUrl)+(finalUrl.length+1000)+btoa('aio-dl');const response=await fetch('https://api.douyin.wtf/api',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:finalUrl,hash:hash})});const data=await response.json();if(data.success&&data.data.medias&&data.data.medias.length>0){const sortedMedias=data.data.medias.sort((a,b)=>(b.size||0)-(a.size||0));const bestMedia=sortedMedias[0];return{success:true,downloadUrl:bestMedia.url,quality:bestMedia.quality||'Unknown',size:bestMedia.formattedSize||'Unknown'};}else{throw new Error('No media found');}}catch(error){return{success:false,fallbackUrl:`https://snapdouyin.app/#url=${encodeURIComponent(window.location.href)}`};}}async function main(){const result=await extractVideoUrl();if(result.success){const popup=document.createElement('div');popup.style.cssText='position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#1a1a2e;border:2px solid #fe2c55;border-radius:15px;padding:30px;z-index:99999;box-shadow:0 10px 40px rgba(0,0,0,0.5);max-width:500px;width:90%;';popup.innerHTML=`<h3 style="margin:0 0 15px 0;color:#fe2c55;">✨ Link de Download!</h3><div style="margin:15px 0;"><p style="color:#25f4ee;margin:5px 0;"><strong>Qualidade:</strong> ${result.quality}</p><p style="color:#25f4ee;margin:5px 0;"><strong>Tamanho:</strong> ${result.size}</p></div><div style="background:#0a0a0f;padding:10px;border-radius:8px;margin:15px 0;word-break:break-all;"><code style="color:#ffffff;font-size:12px;">${result.downloadUrl}</code></div><div style="display:flex;gap:10px;margin-top:20px;"><button onclick="window.open('${result.downloadUrl}','_blank')" style="flex:1;padding:10px;background:#fe2c55;color:white;border:none;border-radius:8px;cursor:pointer;font-weight:600;">📥 Baixar</button><button onclick="navigator.clipboard.writeText('${result.downloadUrl}');this.innerHTML='✓ Copiado!';setTimeout(()=>this.innerHTML='📋 Copiar',2000)" style="flex:1;padding:10px;background:#25f4ee;color:#000;border:none;border-radius:8px;cursor:pointer;font-weight:600;">📋 Copiar</button><button onclick="this.parentElement.parentElement.remove()" style="padding:10px 20px;background:#444;color:white;border:none;border-radius:8px;cursor:pointer;">✖ Fechar</button></div>`;document.body.appendChild(popup);}else{window.open(result.fallbackUrl,'_blank');}}main();})();</pre>
764
- </div>
765
- </div>
766
  </div>
 
 
 
767
 
768
- <div id="demo-tab" class="tab-content" style="display: none;">
769
- <div class="demo-section">
770
- <h2 class="demo-title">Demonstração do Script</h2>
771
- <p style="color: var(--text-secondary); margin-bottom: 30px;">
772
- Teste a funcionalidade do script diretamente aqui. Simule a extração de um vídeo do Douyin.
773
- </p>
774
-
775
- <div class="demo-buttons">
776
- <button class="demo-btn" onclick="runDemo('success')">
777
- <i class="fas fa-check"></i>
778
- Simular Extração Sucesso
779
- </button>
780
- <button class="demo-btn" onclick="runDemo('fallback')">
781
- <i class="fas fa-exclamation-triangle"></i>
782
- Simular Fallback
783
- </button>
784
- <button class="demo-btn" onclick="runDemo('batch')">
785
- <i class="fas fa-layer-group"></i>
786
- Simular Lote
787
- </button>
788
- </div>
789
-
790
- <div id="demo-output" class="preview-window" style="display: none; margin-top: 30px;">
791
- <div style="color: var(--secondary); margin-bottom: 10px;">
792
- <i class="fas fa-terminal"></i> Saída da Simulação
793
- </div>
794
- <div id="demo-content"></div>
795
- </div>
796
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
797
 
798
- <div class="card">
799
- <div class="card-header">
800
- <div class="card-icon">
801
- <i class="fas fa-cog"></i>
802
- </div>
803
- <div class="card-title">Configurações Avançadas</div>
804
- </div>
805
-
806
- <div style="display: grid; gap: 20px;">
807
- <div>
808
- <label style="display: block; margin-bottom: 10px; color: var(--text-secondary);">
809
- URL da API Personalizada
810
- </label>
811
- <input type="text" id="apiUrl" value="https://api.douyin.wtf/api" style="width: 100%; padding: 10px; background: var(--bg-dark); border: 1px solid var
 
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 {
11
+ --primary: #fe2c55;
12
+ --secondary: #25f4ee;
13
+ --bg-dark: #161823;
14
+ --bg-card: #1f1f2e;
15
+ --bg-hover: #2a2a3e;
16
+ --text-primary: #ffffff;
17
+ --text-secondary: #a0a0b0;
18
+ --border: #2a2a3e;
19
+ --success: #00ff88;
20
+ --warning: #ffaa00;
21
+ --error: #ff3366;
22
+ --gradient: linear-gradient(135deg, #fe2c55 0%, #25f4ee 100%);
23
+ }
24
 
25
+ * {
26
+ margin: 0;
27
+ padding: 0;
28
+ box-sizing: border-box;
29
+ }
30
 
31
+ body {
32
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
33
+ background: linear-gradient(135deg, #0f0f1e 0%, #1a1a2e 100%);
34
+ color: var(--text-primary);
35
+ min-height: 100vh;
36
+ display: flex;
37
+ flex-direction: column;
38
+ }
39
 
40
+ .container {
41
+ max-width: 1400px;
42
+ margin: 0 auto;
43
+ padding: 20px;
44
+ flex: 1;
45
+ width: 100%;
46
+ }
47
 
48
+ header {
49
+ background: rgba(31, 31, 46, 0.8);
50
+ backdrop-filter: blur(10px);
51
+ border-bottom: 1px solid var(--border);
52
+ padding: 20px 0;
53
+ position: sticky;
54
+ top: 0;
55
+ z-index: 100;
56
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
57
+ }
58
 
59
+ .header-content {
60
+ display: flex;
61
+ justify-content: space-between;
62
+ align-items: center;
63
+ max-width: 1400px;
64
+ margin: 0 auto;
65
+ padding: 0 20px;
66
+ }
67
 
68
+ .logo {
69
+ display: flex;
70
+ align-items: center;
71
+ gap: 15px;
72
+ }
73
 
74
+ .logo-icon {
75
+ width: 50px;
76
+ height: 50px;
77
+ background: var(--gradient);
78
+ border-radius: 12px;
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: center;
82
+ font-size: 24px;
83
+ animation: pulse 2s infinite;
84
+ }
85
 
86
+ @keyframes pulse {
87
+ 0%, 100% { transform: scale(1); }
88
+ 50% { transform: scale(1.05); }
89
+ }
90
 
91
+ h1 {
92
+ font-size: 1.8rem;
93
+ font-weight: 700;
94
+ background: var(--gradient);
95
+ -webkit-background-clip: text;
96
+ -webkit-text-fill-color: transparent;
97
+ background-clip: text;
98
+ }
99
 
100
+ .built-with {
101
+ color: var(--text-secondary);
102
+ text-decoration: none;
103
+ font-size: 0.9rem;
104
+ transition: color 0.3s;
105
+ }
106
 
107
+ .built-with:hover {
108
+ color: var(--secondary);
109
+ }
110
 
111
+ .main-content {
112
+ display: grid;
113
+ grid-template-columns: 1fr 1fr;
114
+ gap: 30px;
115
+ margin-top: 30px;
116
+ }
 
 
 
117
 
118
+ .card {
119
+ background: rgba(31, 31, 46, 0.6);
120
+ backdrop-filter: blur(10px);
121
+ border: 1px solid var(--border);
122
+ border-radius: 20px;
123
+ padding: 30px;
124
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
125
+ transition: transform 0.3s, box-shadow 0.3s;
126
+ }
 
 
 
 
 
127
 
128
+ .card:hover {
129
+ transform: translateY(-5px);
130
+ box-shadow: 0 15px 50px rgba(254, 44, 85, 0.2);
131
+ }
132
 
133
+ .card-header {
134
+ display: flex;
135
+ align-items: center;
136
+ gap: 15px;
137
+ margin-bottom: 25px;
138
+ }
139
 
140
+ .card-icon {
141
+ width: 45px;
142
+ height: 45px;
143
+ background: var(--gradient);
144
+ border-radius: 10px;
145
+ display: flex;
146
+ align-items: center;
147
+ justify-content: center;
148
+ font-size: 20px;
149
+ }
150
 
151
+ .card-title {
152
+ font-size: 1.3rem;
153
+ font-weight: 600;
154
+ }
 
 
 
 
 
155
 
156
+ .input-section {
157
+ margin-bottom: 25px;
158
+ }
 
159
 
160
+ .input-label {
161
+ display: block;
162
+ margin-bottom: 10px;
163
+ color: var(--text-secondary);
164
+ font-weight: 500;
165
+ }
166
 
167
+ .links-textarea {
168
+ width: 100%;
169
+ min-height: 200px;
170
+ padding: 15px;
171
+ background: var(--bg-dark);
172
+ border: 2px solid var(--border);
173
+ border-radius: 12px;
174
+ color: var(--text-primary);
175
+ font-size: 14px;
176
+ resize: vertical;
177
+ transition: border-color 0.3s;
178
+ }
179
 
180
+ .links-textarea:focus {
181
+ outline: none;
182
+ border-color: var(--primary);
183
+ }
184
 
185
+ .url-chips {
186
+ display: flex;
187
+ flex-wrap: wrap;
188
+ gap: 8px;
189
+ margin-top: 10px;
190
+ }
 
 
 
191
 
192
+ .url-chip {
193
+ background: var(--bg-hover);
194
+ padding: 6px 12px;
195
+ border-radius: 20px;
196
+ font-size: 12px;
197
+ color: var(--text-secondary);
198
+ display: flex;
199
+ align-items: center;
200
+ gap: 8px;
201
+ animation: fadeIn 0.3s;
202
+ }
203
 
204
+ @keyframes fadeIn {
205
+ from { opacity: 0; transform: scale(0.8); }
206
+ to { opacity: 1; transform: scale(1); }
207
+ }
 
208
 
209
+ .url-chip .remove {
210
+ cursor: pointer;
211
+ color: var(--error);
212
+ transition: transform 0.2s;
213
+ }
 
 
 
 
 
 
 
 
214
 
215
+ .url-chip .remove:hover {
216
+ transform: scale(1.2);
217
+ }
 
218
 
219
+ .action-buttons {
220
+ display: flex;
221
+ gap: 15px;
222
+ margin-top: 20px;
223
+ }
224
 
225
+ .btn {
226
+ flex: 1;
227
+ padding: 14px 24px;
228
+ border: none;
229
+ border-radius: 10px;
230
+ font-size: 1rem;
231
+ font-weight: 600;
232
+ cursor: pointer;
233
+ transition: all 0.3s;
234
+ display: flex;
235
+ align-items: center;
236
+ justify-content: center;
237
+ gap: 10px;
238
+ }
239
 
240
+ .btn-primary {
241
+ background: var(--gradient);
242
+ color: white;
243
+ }
 
 
 
244
 
245
+ .btn-primary:hover:not(:disabled) {
246
+ transform: translateY(-3px);
247
+ box-shadow: 0 5px 20px rgba(254, 44, 85, 0.4);
248
+ }
 
 
 
249
 
250
+ .btn-secondary {
251
+ background: var(--bg-hover);
252
+ color: var(--text-primary);
253
+ }
254
 
255
+ .btn-secondary:hover:not(:disabled) {
256
+ background: #3a3a4e;
257
+ }
 
258
 
259
+ .btn:disabled {
260
+ opacity: 0.5;
261
+ cursor: not-allowed;
262
+ }
 
263
 
264
+ .progress-section {
265
+ margin-top: 30px;
266
+ display: none;
267
+ }
 
 
 
 
 
268
 
269
+ .progress-header {
270
+ display: flex;
271
+ justify-content: space-between;
272
+ align-items: center;
273
+ margin-bottom: 15px;
274
+ }
275
 
276
+ .progress-title {
277
+ font-size: 1.1rem;
278
+ color: var(--text-primary);
279
+ }
 
 
 
 
 
 
 
280
 
281
+ .progress-stats {
282
+ display: flex;
283
+ gap: 20px;
284
+ font-size: 0.9rem;
285
+ }
 
 
286
 
287
+ .stat {
288
+ display: flex;
289
+ align-items: center;
290
+ gap: 5px;
291
+ }
 
 
 
292
 
293
+ .stat.success { color: var(--success); }
294
+ .stat.error { color: var(--error); }
295
+ .stat.pending { color: var(--warning); }
296
+
297
+ .progress-bar {
298
+ width: 100%;
299
+ height: 8px;
300
+ background: var(--bg-dark);
301
+ border-radius: 10px;
302
+ overflow: hidden;
303
+ margin-bottom: 20px;
304
+ }
305
 
306
+ .progress-fill {
307
+ height: 100%;
308
+ background: var(--gradient);
309
+ border-radius: 10px;
310
+ transition: width 0.3s ease;
311
+ width: 0%;
312
+ }
 
 
 
 
 
 
 
313
 
314
+ .downloads-list {
315
+ max-height: 400px;
316
+ overflow-y: auto;
317
+ margin-top: 20px;
318
+ }
319
 
320
+ .download-item {
321
+ background: var(--bg-dark);
322
+ border: 1px solid var(--border);
323
+ border-radius: 12px;
324
+ padding: 15px;
325
+ margin-bottom: 15px;
326
+ display: flex;
327
+ align-items: center;
328
+ gap: 15px;
329
+ transition: all 0.3s;
330
+ }
331
 
332
+ .download-item:hover {
333
+ border-color: var(--primary);
334
+ transform: translateX(5px);
335
+ }
 
 
 
 
 
336
 
337
+ .download-status {
338
+ width: 40px;
339
+ height: 40px;
340
+ border-radius: 50%;
341
+ display: flex;
342
+ align-items: center;
343
+ justify-content: center;
344
+ flex-shrink: 0;
345
+ }
346
 
347
+ .status-pending {
348
+ background: rgba(255, 170, 0, 0.2);
349
+ color: var(--warning);
350
+ }
 
351
 
352
+ .status-processing {
353
+ background: rgba(37, 244, 238, 0.2);
354
+ color: var(--secondary);
355
+ animation: spin 1s linear infinite;
356
+ }
 
 
 
 
 
357
 
358
+ @keyframes spin {
359
+ from { transform: rotate(0deg); }
360
+ to { transform: rotate(360deg); }
361
+ }
 
 
 
362
 
363
+ .status-success {
364
+ background: rgba(0, 255, 136, 0.2);
365
+ color: var(--success);
366
+ }
367
+
368
+ .status-error {
369
+ background: rgba(255, 51, 102, 0.2);
370
+ color: var(--error);
371
+ }
372
+
373
+ .download-info {
374
+ flex: 1;
375
+ }
376
+
377
+ .download-url {
378
+ font-size: 0.9rem;
379
+ color: var(--text-secondary);
380
+ margin-bottom: 5px;
381
+ word-break: break-all;
382
+ }
383
+
384
+ .download-details {
385
+ display: flex;
386
+ gap: 15px;
387
+ font-size: 0.85rem;
388
+ color: var(--text-secondary);
389
+ }
390
+
391
+ .download-actions {
392
+ display: flex;
393
+ gap: 10px;
394
+ }
395
+
396
+ .action-btn {
397
+ width: 35px;
398
+ height: 35px;
399
+ border-radius: 8px;
400
+ border: none;
401
+ background: var(--bg-hover);
402
+ color: var(--text-secondary);
403
+ cursor: pointer;
404
+ display: flex;
405
+ align-items: center;
406
+ justify-content: center;
407
+ transition: all 0.3s;
408
+ }
409
+
410
+ .action-btn:hover {
411
+ background: var(--primary);
412
+ color: white;
413
+ transform: scale(1.1);
414
+ }
415
+
416
+ .settings-section {
417
+ margin-top: 20px;
418
+ }
419
+
420
+ .setting-item {
421
+ display: flex;
422
+ justify-content: space-between;
423
+ align-items: center;
424
+ padding: 15px;
425
+ background: var(--bg-dark);
426
+ border-radius: 10px;
427
+ margin-bottom: 10px;
428
+ }
429
+
430
+ .setting-label {
431
+ color: var(--text-secondary);
432
+ font-size: 0.9rem;
433
+ }
434
+
435
+ .toggle-switch {
436
+ position: relative;
437
+ width: 50px;
438
+ height: 26px;
439
+ background: var(--bg-hover);
440
+ border-radius: 13px;
441
+ cursor: pointer;
442
+ transition: background 0.3s;
443
+ }
444
+
445
+ .toggle-switch.active {
446
+ background: var(--gradient);
447
+ }
448
+
449
+ .toggle-switch::after {
450
+ content: '';
451
+ position: absolute;
452
+ width: 22px;
453
+ height: 22px;
454
+ background: white;
455
+ border-radius: 50%;
456
+ top: 2px;
457
+ left: 2px;
458
+ transition: transform 0.3s;
459
+ }
460
+
461
+ .toggle-switch.active::after {
462
+ transform: translateX(24px);
463
+ }
464
+
465
+ .notification {
466
+ position: fixed;
467
+ bottom: 20px;
468
+ right: 20px;
469
+ background: var(--bg-card);
470
+ border: 1px solid var(--border);
471
+ border-radius: 12px;
472
+ padding: 15px 20px;
473
+ display: flex;
474
+ align-items: center;
475
+ gap: 15px;
476
+ box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
477
+ transform: translateX(400px);
478
+ transition: transform 0.3s;
479
+ z-index: 1000;
480
+ }
481
+
482
+ .notification.show {
483
+ transform: translateX(0);
484
+ }
485
+
486
+ .notification-icon {
487
+ width: 30px;
488
+ height: 30px;
489
+ border-radius: 50%;
490
+ display: flex;
491
+ align-items: center;
492
+ justify-content: center;
493
+ }
494
+
495
+ .notification.success .notification-icon {
496
+ background: rgba(0, 255, 136, 0.2);
497
+ color: var(--success);
498
+ }
499
+
500
+ .notification.error .notification-icon {
501
+ background: rgba(255, 51, 102, 0.2);
502
+ color: var(--error);
503
+ }
504
+
505
+ @media (max-width: 968px) {
506
+ .main-content {
507
+ grid-template-columns: 1fr;
508
+ }
509
+ }
510
+
511
+ .empty-state {
512
+ text-align: center;
513
+ padding: 40px;
514
+ color: var(--text-secondary);
515
+ }
516
+
517
+ .empty-state i {
518
+ font-size: 48px;
519
+ margin-bottom: 15px;
520
+ opacity: 0.5;
521
+ }
522
+ </style>
523
  </head>
524
+
525
  <body>
526
+ <header>
527
+ <div class="header-content">
528
+ <div class="logo">
529
+ <div class="logo-icon">
530
+ <i class="fas fa-download"></i>
531
+ </div>
532
+ <h1>Douyin Video Downloader</h1>
533
+ </div>
534
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">
535
+ Built with anycoder
536
+ </a>
537
+ </div>
538
+ </header>
539
+
540
+ <div class="container">
541
+ <div class="main-content">
542
+ <div class="card">
543
+ <div class="card-header">
544
+ <div class="card-icon">
545
+ <i class="fas fa-link"></i>
546
+ </div>
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>
558
+ </div>
559
+
560
+ <div class="action-buttons">
561
+ <button class="btn btn-primary" onclick="startDownloads()" id="startBtn">
562
+ <i class="fas fa-play"></i>
563
+ Iniciar Downloads
564
+ </button>
565
+ <button class="btn btn-secondary" onclick="clearLinks()">
566
+ <i class="fas fa-trash"></i>
567
+ Limpar
568
+ </button>
569
+ </div>
570
+
571
+ <div class="progress-section" id="progressSection">
572
+ <div class="progress-header">
573
+ <div class="progress-title">Progresso dos Downloads</div>
574
+ <div class="progress-stats">
575
+ <div class="stat success">
576
+ <i class="fas fa-check-circle"></i>
577
+ <span id="successCount">0</span>
578
+ </div>
579
+ <div class="stat error">
580
+ <i class="fas fa-times-circle"></i>
581
+ <span id="errorCount">0</span>
582
+ </div>
583
+ <div class="stat pending">
584
+ <i class="fas fa-clock"></i>
585
+ <span id="pendingCount">0</span>
586
+ </div>
587
  </div>
588
+ </div>
589
+ <div class="progress-bar">
590
+ <div class="progress-fill" id="progressFill"></div>
591
+ </div>
592
+ <div class="downloads-list" id="downloadsList"></div>
593
  </div>
594
+ </div>
595
+
596
+ <div class="card">
597
+ <div class="card-header">
598
+ <div class="card-icon">
599
+ <i class="fas fa-cog"></i>
600
+ </div>
601
+ <div class="card-title">Configurações</div>
 
 
 
 
 
 
 
 
602
  </div>
603
 
604
+ <div class="settings-section">
605
+ <div class="setting-item">
606
+ <span class="setting-label">Baixar automaticamente após extração</span>
607
+ <div class="toggle-switch active" onclick="toggleSetting(this, 'autoDownload')"></div>
608
+ </div>
609
+ <div class="setting-item">
610
+ <span class="setting-label">Selecionar máxima qualidade</span>
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;">
624
+ <h3 style="color: var(--primary); margin-bottom: 10px;">
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
+
636
+ <div style="margin-top: 20px;">
637
+ <h3 style="color: var(--text-primary); margin-bottom: 15px;">
638
+ <i class="fas fa-chart-line"></i> Estatísticas
639
+ </h3>
640
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
641
+ <div style="background: var(--bg-dark); padding: 15px; border-radius: 10px;">
642
+ <div style="color: var(--text-secondary); font-size: 0.85rem;">Total Processado</div>
643
+ <div style="font-size: 1.5rem; font-weight: bold; color: var(--primary);" id="totalProcessed">0</div>
644
+ </div>
645
+ <div style="background: var(--bg-dark); padding: 15px; border-radius: 10px;">
646
+ <div style="color: var(--text-secondary); font-size: 0.85rem;">Taxa de Sucesso</div>
647
+ <div style="font-size: 1.5rem; font-weight: bold; color: var(--success);" id="successRate">0%</div>
648
+ </div>
649
+ </div>
650
+ </div>
651
+ </div>
652
+ </div>
653
+ </div>
654
+
655
+ <div class="notification" id="notification">
656
+ <div class="notification-icon">
657
+ <i class="fas fa-check"></i>
658
+ </div>
659
+ <div class="notification-content">
660
+ <div id="notificationText" style="font-weight: 600;"></div>
661
+ </div>
662
+ </div>
663
+
664
+ <script>
665
+ let settings = {
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')
685
+ .map(url => url.trim())
686
+ .filter(url => url && (url.includes('douyin.com') || url.includes('v.douyin.com')));
687
+
688
+ const uniqueUrls = [...new Set(urls)];
689
+ updateUrlChips(uniqueUrls);
690
+ return uniqueUrls;
691
+ }
692
+
693
+ function updateUrlChips(urls) {
694
+ const chipsContainer = document.getElementById('urlChips');
695
+ chipsContainer.innerHTML = '';
696
+
697
+ urls.forEach((url, index) => {
698
+ const chip = document.createElement('div');
699
+ chip.className = 'url-chip';
700
+ chip.innerHTML = `
701
+ <i class="fas fa-link"></i>
702
+ <span>${url.substring(0, 40)}${url.length > 40 ? '...' : ''}</span>
703
+ <i class="fas fa-times remove" onclick="removeUrl(${index})"></i>
704
  `;
705
+ chipsContainer.appendChild(chip);
706
+ });
707
+ }
708
+
709
+ function removeUrl(index) {
710
+ const input = document.getElementById('linksInput');
711
+ const urls = input.value.split('\n');
712
+ urls.splice(index, 1);
713
+ input.value = urls.join('\n');
714
+ parseLinks();
715
+ }
716
+
717
+ function clearLinks() {
718
+ document.getElementById('linksInput').value = '';
719
+ document.getElementById('urlChips').innerHTML = '';
720
+ document.getElementById('progressSection').style.display = 'none';
721
+ resetStats();
722
+ }
723
+
724
+ function resetStats() {
725
+ processedCount = 0;
726
+ successCount = 0;
727
+ errorCount = 0;
728
+ downloadQueue = [];
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) {
792
+ const item = document.createElement('div');
793
+ item.className = 'download-item';
794
+ item.id = `download-${index}`;
795
+ item.innerHTML = `
796
+ <div class="download-status status-pending">
797
+ <i class="fas fa-clock"></i>
798
+ </div>
799
+ <div class="download-info">
800
+ <div class="download-url">${url}</div>
801
+ <div class="download-details">
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">
808
+ <i class="fas fa-copy"></i>
809
+ </button>
810
+ <button class="action-btn" onclick="downloadVideo('${index}')" title="Baixar">
811
+ <i class="fas fa-download"></i>
812
+ </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
813
  </div>
814
+ `;
815
+ return item;
816
+ }
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
+
826
+ switch(status) {
827
+ case 'processing':
828
+ statusDiv.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
829
+ break;
830
+ case 'success':
831
+ statusDiv.innerHTML = '<i class="fas fa-check"></i>';
832
+ qualitySpan.textContent = data.quality || 'HD';
833
+ sizeSpan.textContent = data.size || '--';
834
+ if (settings.autoDownload) {
835
+ downloadVideo(index, data.downloadUrl);
836
+ }
837
+ break;
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
+ }
845
+
846
+ async function processDownload(url, index) {
847
+ updateDownloadStatus(index, 'processing');
848
+
849
+ const result = await extractVideoUrl(url);
850
+
851
+ if (result.success) {
852
+ downloadQueue[index] = result;
853
+ updateDownloadStatus(index, 'success', result);
854
+ successCount++;
855
+
856
+ if (settings.notifications) {
857
+ showNotification('Download extraído com sucesso!', 'success');
858
+ }
859
+ } else {
860
+ updateDownloadStatus(index, 'error', result);
861
+ errorCount++;
862
+
863
+ if (settings.notifications) {
864
+ showNotification('Falha ao extrair vídeo', 'error');
865
+ }
866
+ }
867
+
868
+ processedCount++;
869
+ updateProgressDisplay();
870
+ }
871
+
872
+ async function startDownloads() {
873
+ const urls = parseLinks();
874
+
875
+ if (urls.length === 0) {
876
+ showNotification('Por favor, adicione pelo menos um link', 'error');
877
+ return;
878
+ }
879
+
880
+ // Resetar contadores
881
+ processedCount = 0;
882
+ successCount = 0;
883
+ errorCount = 0;
884
+ downloadQueue = new Array(urls.length);
885
+
886
+ // Mostrar seção de progresso
887
+ document.getElementById('progressSection').style.display = 'block';
888
+ document.getElementById('downloadsList').innerHTML = '';
889
+
890
+ // Criar itens de download
891
+ urls.forEach((url, index) => {
892
+ const item = createDownloadItem(url, index);
893
+ document.getElementById('downloadsList').appendChild(item);
894
+ });
895
+
896
+ // Desabilitar botão
897
+ document.getElementById('startBtn').disabled = true;
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
+ }
908
+ }
909
+
910
+ // Reabilitar botão
911
+ document.getElementById('startBtn').disabled = false;
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
+
919
+ function updateProgressDisplay() {
920
+ const total = downloadQueue.length;
921
+ const progress = total > 0 ? (processedCount / total) * 100 : 0;
922
+
923
+ document.getElementById('progressFill').style.width = `${progress}%`;
924
+ document.getElementById('successCount').textContent = successCount;
925
+ document.getElementById('errorCount').textContent = errorCount;
926
+ document.getElementById('pendingCount').textContent = total - processedCount;
927
+ document.getElementById('totalProcessed').textContent = processedCount;
928
+
929
+ const successRate = total > 0 ? Math.round((successCount / total) * 100) : 0;
930
+ document.getElementById('successRate').textContent = `${successRate}%`;
931
+ }
932
+
933
+ function copyLink(index) {
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
+
941
+ function downloadVideo(index, directUrl = null) {
942
+ const data = downloadQueue[index];
943
+ const url = directUrl || (data ? data.downloadUrl : null);
944
+
945
+ if (url) {
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);
952
+
953
+ if (!directUrl && settings.notifications) {
954
+ showNotification('Download iniciado!', 'success');
955
+ }
956
+ }
957
+ }
958
+
959
+ function showNotification(message, type = 'success') {
960
+ const notification = document.getElementById('notification');
961
+ const icon = notification.querySelector('.notification-icon i');
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>