eubottura commited on
Commit
f19a86d
·
verified ·
1 Parent(s): 3b5e60a

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +597 -702
index.html CHANGED
@@ -1,25 +1,24 @@
1
  <!DOCTYPE html>
2
- <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Douyin Video Downloader - High Quality Extractor</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: #ff0050;
11
- --primary-dark: #e00045;
12
  --secondary: #25f4ee;
13
- --bg-dark: #0f0f0f;
14
- --bg-card: #1a1a1a;
15
- --bg-hover: #252525;
16
  --text-primary: #ffffff;
17
- --text-secondary: #b0b0b0;
18
- --border: #2a2a2a;
19
  --success: #00ff88;
20
  --warning: #ffaa00;
21
  --error: #ff3366;
22
- --gradient: linear-gradient(135deg, #ff0050 0%, #25f4ee 100%);
23
  }
24
 
25
  * {
@@ -30,42 +29,56 @@
30
 
31
  body {
32
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
33
- background: var(--bg-dark);
34
  color: var(--text-primary);
35
  min-height: 100vh;
36
- background-image:
37
- radial-gradient(circle at 20% 50%, rgba(255, 0, 80, 0.1) 0%, transparent 50%),
38
- radial-gradient(circle at 80% 80%, rgba(37, 244, 238, 0.1) 0%, transparent 50%);
39
  }
40
 
41
  .container {
42
- max-width: 1200px;
43
  margin: 0 auto;
44
  padding: 20px;
 
 
45
  }
46
 
47
  header {
48
- text-align: center;
49
- padding: 40px 0 30px;
50
- position: relative;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  }
52
 
53
  .logo {
54
- display: inline-flex;
55
  align-items: center;
56
  gap: 15px;
57
- margin-bottom: 20px;
58
  }
59
 
60
  .logo-icon {
61
- width: 60px;
62
- height: 60px;
63
  background: var(--gradient);
64
- border-radius: 15px;
65
  display: flex;
66
  align-items: center;
67
  justify-content: center;
68
- font-size: 30px;
69
  animation: pulse 2s infinite;
70
  }
71
 
@@ -75,25 +88,15 @@
75
  }
76
 
77
  h1 {
78
- font-size: 2.5rem;
79
  font-weight: 700;
80
  background: var(--gradient);
81
  -webkit-background-clip: text;
82
  -webkit-text-fill-color: transparent;
83
  background-clip: text;
84
- margin-bottom: 10px;
85
- }
86
-
87
- .subtitle {
88
- color: var(--text-secondary);
89
- font-size: 1.1rem;
90
- margin-bottom: 30px;
91
  }
92
 
93
  .built-with {
94
- position: absolute;
95
- top: 20px;
96
- right: 20px;
97
  color: var(--text-secondary);
98
  text-decoration: none;
99
  font-size: 0.9rem;
@@ -104,813 +107,705 @@
104
  color: var(--secondary);
105
  }
106
 
107
- .main-card {
 
 
 
108
  background: var(--bg-card);
109
- border-radius: 20px;
110
- padding: 40px;
111
- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
112
- border: 1px solid var(--border);
113
- }
114
-
115
- .input-section {
116
- margin-bottom: 30px;
117
- }
118
-
119
- .input-label {
120
- display: block;
121
- margin-bottom: 15px;
122
- font-size: 1.1rem;
123
- color: var(--text-primary);
124
- font-weight: 500;
125
- }
126
-
127
- .url-input-container {
128
- position: relative;
129
- margin-bottom: 15px;
130
- }
131
-
132
- textarea {
133
- width: 100%;
134
- min-height: 120px;
135
- padding: 15px;
136
- background: var(--bg-dark);
137
- border: 2px solid var(--border);
138
  border-radius: 12px;
139
- color: var(--text-primary);
140
- font-size: 1rem;
141
- resize: vertical;
142
- transition: all 0.3s;
143
- font-family: 'Courier New', monospace;
144
- }
145
-
146
- textarea:focus {
147
- outline: none;
148
- border-color: var(--primary);
149
- background: #1a1a1a;
150
- box-shadow: 0 0 0 3px rgba(255, 0, 80, 0.1);
151
- }
152
-
153
- .input-hint {
154
- display: flex;
155
- justify-content: space-between;
156
- align-items: center;
157
- margin-top: 10px;
158
- }
159
-
160
- .hint-text {
161
- color: var(--text-secondary);
162
- font-size: 0.9rem;
163
  }
164
 
165
- .url-count {
166
- background: var(--primary);
167
- color: white;
168
- padding: 4px 12px;
169
- border-radius: 20px;
170
- font-size: 0.85rem;
171
- font-weight: 600;
172
- }
173
-
174
- .action-buttons {
175
- display: flex;
176
- gap: 15px;
177
- flex-wrap: wrap;
178
- }
179
-
180
- .btn {
181
- padding: 12px 30px;
182
  border: none;
183
- border-radius: 10px;
184
- font-size: 1rem;
185
- font-weight: 600;
186
  cursor: pointer;
 
 
 
187
  transition: all 0.3s;
188
- display: inline-flex;
189
  align-items: center;
190
- gap: 10px;
191
- position: relative;
192
- overflow: hidden;
193
  }
194
 
195
- .btn::before {
196
- content: '';
197
- position: absolute;
198
- top: 50%;
199
- left: 50%;
200
- width: 0;
201
- height: 0;
202
- border-radius: 50%;
203
- background: rgba(255, 255, 255, 0.2);
204
- transform: translate(-50%, -50%);
205
- transition: width 0.6s, height 0.6s;
206
- }
207
-
208
- .btn:active::before {
209
- width: 300px;
210
- height: 300px;
211
- }
212
-
213
- .btn-primary {
214
  background: var(--gradient);
215
  color: white;
216
- flex: 1;
217
- }
218
-
219
- .btn-primary:hover {
220
- transform: translateY(-2px);
221
- box-shadow: 0 5px 20px rgba(255, 0, 80, 0.4);
222
- }
223
-
224
- .btn-secondary {
225
- background: transparent;
226
- color: var(--text-secondary);
227
- border: 2px solid var(--border);
228
- }
229
-
230
- .btn-secondary:hover {
231
- border-color: var(--primary);
232
- color: var(--primary);
233
- }
234
-
235
- .btn:disabled {
236
- opacity: 0.5;
237
- cursor: not-allowed;
238
- }
239
-
240
- .loading-section {
241
- display: none;
242
- margin: 30px 0;
243
- text-align: center;
244
- }
245
-
246
- .loading-section.active {
247
- display: block;
248
  }
249
 
250
- .spinner {
251
- width: 50px;
252
- height: 50px;
253
- border: 3px solid var(--border);
254
- border-top-color: var(--primary);
255
- border-radius: 50%;
256
- animation: spin 1s linear infinite;
257
- margin: 0 auto 20px;
258
- }
259
-
260
- @keyframes spin {
261
- to { transform: rotate(360deg); }
262
- }
263
-
264
- .progress-bar {
265
- width: 100%;
266
- height: 4px;
267
- background: var(--border);
268
- border-radius: 2px;
269
- overflow: hidden;
270
- margin-top: 20px;
271
- }
272
-
273
- .progress-fill {
274
- height: 100%;
275
- background: var(--gradient);
276
- border-radius: 2px;
277
- transition: width 0.3s;
278
- animation: shimmer 1.5s infinite;
279
  }
280
 
281
- @keyframes shimmer {
282
- 0% { opacity: 0.8; }
283
- 50% { opacity: 1; }
284
- 100% { opacity: 0.8; }
 
285
  }
286
 
287
- .results-section {
288
- margin-top: 40px;
289
- display: none;
 
 
 
 
 
290
  }
291
 
292
- .results-section.active {
293
- display: block;
 
294
  }
295
 
296
- .results-header {
297
  display: flex;
298
- justify-content: space-between;
299
  align-items: center;
 
300
  margin-bottom: 25px;
301
- padding-bottom: 15px;
302
- border-bottom: 1px solid var(--border);
303
  }
304
 
305
- .results-title {
306
- font-size: 1.3rem;
307
- font-weight: 600;
 
 
308
  display: flex;
309
  align-items: center;
310
- gap: 10px;
 
311
  }
312
 
313
- .success-badge {
314
- background: var(--success);
315
- color: var(--bg-dark);
316
- padding: 4px 12px;
317
- border-radius: 20px;
318
- font-size: 0.85rem;
319
  font-weight: 600;
320
  }
321
 
322
- .result-item {
323
- background: var(--bg-dark);
324
  border: 1px solid var(--border);
325
  border-radius: 12px;
326
  padding: 20px;
327
- margin-bottom: 15px;
328
- transition: all 0.3s;
329
  position: relative;
330
  overflow: hidden;
331
  }
332
 
333
- .result-item::before {
334
- content: '';
335
- position: absolute;
336
- left: 0;
337
- top: 0;
338
- height: 100%;
339
- width: 3px;
340
- background: var(--gradient);
341
- transform: scaleY(0);
342
- transition: transform 0.3s;
343
- }
344
-
345
- .result-item:hover {
346
- border-color: var(--primary);
347
- transform: translateX(5px);
348
- }
349
-
350
- .result-item:hover::before {
351
- transform: scaleY(1);
352
- }
353
-
354
- .result-header {
355
  display: flex;
356
  justify-content: space-between;
357
- align-items: flex-start;
358
  margin-bottom: 15px;
 
 
359
  }
360
 
361
- .result-index {
362
- background: var(--gradient);
363
- color: white;
364
- width: 30px;
365
- height: 30px;
366
- border-radius: 50%;
367
- display: flex;
368
- align-items: center;
369
- justify-content: center;
370
- font-weight: 600;
371
  font-size: 0.9rem;
 
372
  }
373
 
374
- .result-status {
375
- display: flex;
376
- gap: 10px;
377
- align-items: center;
378
- }
379
-
380
- .status-badge {
381
- padding: 4px 10px;
382
  border-radius: 6px;
383
- font-size: 0.85rem;
384
- font-weight: 500;
 
385
  display: flex;
386
  align-items: center;
387
  gap: 5px;
388
  }
389
 
390
- .status-success {
391
- background: rgba(0, 255, 136, 0.1);
392
- color: var(--success);
393
- border: 1px solid rgba(0, 255, 136, 0.3);
394
  }
395
 
396
- .status-fallback {
397
- background: rgba(255, 170, 0, 0.1);
398
- color: var(--warning);
399
- border: 1px solid rgba(255, 170, 0, 0.3);
400
  }
401
 
402
- .original-url {
403
- background: rgba(255, 255, 255, 0.03);
404
- padding: 10px;
405
- border-radius: 8px;
406
- font-family: 'Courier New', monospace;
407
  font-size: 0.9rem;
408
- margin-bottom: 15px;
409
- word-break: break-all;
410
- color: var(--text-secondary);
411
  }
412
 
413
- .download-link-container {
414
- display: flex;
415
- gap: 10px;
416
- align-items: center;
 
 
417
  }
418
 
419
- .download-link {
420
- flex: 1;
421
- background: rgba(37, 244, 238, 0.05);
422
- border: 1px solid rgba(37, 244, 238, 0.2);
423
- padding: 10px 15px;
424
- border-radius: 8px;
425
- font-family: 'Courier New', monospace;
426
- font-size: 0.9rem;
427
- word-break: break-all;
428
  color: var(--secondary);
429
- }
430
-
431
- .btn-icon {
432
- padding: 10px;
433
- border-radius: 8px;
434
- background: var(--gradient);
435
- color: white;
436
- border: none;
437
- cursor: pointer;
438
- transition: all 0.3s;
439
  display: flex;
440
  align-items: center;
441
- justify-content: center;
442
  }
443
 
444
- .btn-icon:hover {
445
- transform: scale(1.1);
446
- box-shadow: 0 3px 15px rgba(255, 0, 80, 0.4);
447
  }
448
 
449
- .stats-container {
 
 
 
 
 
450
  display: grid;
451
- grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
452
  gap: 15px;
453
- margin-top: 30px;
454
- padding-top: 30px;
455
- border-top: 1px solid var(--border);
456
  }
457
 
458
- .stat-item {
459
- text-align: center;
 
 
460
  padding: 15px;
461
- background: var(--bg-dark);
462
  border-radius: 10px;
463
- border: 1px solid var(--border);
464
  }
465
 
466
- .stat-value {
467
- font-size: 1.8rem;
468
- font-weight: 700;
469
- color: var(--primary);
470
- margin-bottom: 5px;
471
  }
472
 
473
- .stat-label {
474
- color: var(--text-secondary);
475
- font-size: 0.9rem;
 
 
 
 
 
 
 
476
  }
477
 
478
- .toast {
479
- position: fixed;
480
- bottom: 30px;
481
- right: 30px;
482
  background: var(--bg-card);
483
- border: 1px solid var(--border);
484
- border-radius: 10px;
485
- padding: 15px 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
486
  display: flex;
487
- align-items: center;
488
  gap: 15px;
489
- transform: translateX(400px);
490
- transition: transform 0.3s;
491
- z-index: 1000;
492
- box-shadow: 0 5px 20px rgba(0, 0, 0, 0.5);
493
  }
494
 
495
- .toast.show {
496
- transform: translateX(0);
 
 
 
 
 
 
 
 
 
 
 
497
  }
498
 
499
- .toast-icon {
500
- font-size: 1.2rem;
 
501
  }
502
 
503
- .toast-success .toast-icon {
504
- color: var(--success);
 
 
 
 
 
 
 
 
505
  }
506
 
507
- .toast-error .toast-icon {
508
- color: var(--error);
 
 
 
 
 
 
509
  }
510
 
511
- @media (max-width: 768px) {
512
- h1 {
513
- font-size: 2rem;
514
- }
515
-
516
- .main-card {
517
- padding: 25px;
518
- }
519
 
520
- .action-buttons {
521
- flex-direction: column;
522
- }
 
 
523
 
524
- .btn {
525
- width: 100%;
526
- justify-content: center;
527
  }
528
-
529
- .results-header {
530
  flex-direction: column;
531
  gap: 15px;
532
- align-items: flex-start;
533
- }
534
-
535
- .stats-container {
536
- grid-template-columns: repeat(2, 1fr);
537
  }
538
  }
539
 
540
- .fade-in {
541
- animation: fadeIn 0.5s ease-in;
 
 
 
 
542
  }
543
 
544
- @keyframes fadeIn {
545
- from { opacity: 0; transform: translateY(20px); }
546
- to { opacity: 1; transform: translateY(0); }
547
  }
548
  </style>
549
  </head>
550
  <body>
551
- <div class="container">
552
- <header>
553
  <div class="logo">
554
  <div class="logo-icon">
555
- <i class="fas fa-download"></i>
556
  </div>
557
- <h1>Douyin Video Downloader</h1>
558
  </div>
559
- <p class="subtitle">Extract high-quality video download links from Douyin URLs</p>
560
  <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">
561
  Built with anycoder
562
  </a>
563
- </header>
564
-
565
- <main>
566
- <div class="main-card">
567
- <div class="input-section">
568
- <label class="input-label">
569
- <i class="fas fa-link"></i> Enter Douyin Video URLs
570
- </label>
571
- <div class="url-input-container">
572
- <textarea
573
- id="urlInput"
574
- placeholder="https://www.douyin.com/video/1234567890123456789?region=CN&#10;https://v.douyin.com/abcdefg/&#10;https://www.douyin.com/user/123456789?modal_id=987654321"
575
- ></textarea>
576
- </div>
577
- <div class="input-hint">
578
- <span class="hint-text">
579
- <i class="fas fa-info-circle"></i> Enter one URL per line
580
- </span>
581
- <span class="url-count" id="urlCount">0 URLs</span>
582
- </div>
583
- </div>
584
-
585
- <div class="action-buttons">
586
- <button class="btn btn-primary" id="processBtn">
587
- <i class="fas fa-play"></i>
588
- Process URLs
589
- </button>
590
- <button class="btn btn-secondary" id="clearBtn">
591
- <i class="fas fa-trash"></i>
592
- Clear
593
- </button>
594
- <button class="btn btn-secondary" id="sampleBtn">
595
- <i class="fas fa-magic"></i>
596
- Load Sample
597
- </button>
598
- </div>
599
 
600
- <div class="loading-section" id="loadingSection">
601
- <div class="spinner"></div>
602
- <p>Processing URLs...</p>
603
- <div class="progress-bar">
604
- <div class="progress-fill" id="progressFill" style="width: 0%"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
605
  </div>
606
- </div>
607
 
608
- <div class="results-section" id="resultsSection">
609
- <div class="results-header">
610
- <div class="results-title">
611
- <i class="fas fa-list-check"></i>
612
- Download Links
613
- <span class="success-badge" id="successCount">0</span>
 
614
  </div>
615
- <button class="btn btn-secondary" id="downloadAllBtn">
616
- <i class="fas fa-download"></i>
617
- Download All
618
- </button>
619
- </div>
620
- <div id="resultsContainer"></div>
621
- <div class="stats-container" id="statsContainer"></div>
622
- </div>
623
- </div>
624
- </main>
625
- </div>
626
-
627
- <div class="toast" id="toast">
628
- <i class="toast-icon fas fa-check-circle"></i>
629
- <span class="toast-message"></span>
630
- </div>
631
-
632
- <script>
633
- class DouyinDownloader {
634
- constructor() {
635
- this.initElements();
636
- this.bindEvents();
637
- this.processedResults = [];
 
 
 
 
 
 
 
638
  }
639
-
640
- initElements() {
641
- this.urlInput = document.getElementById('urlInput');
642
- this.urlCount = document.getElementById('urlCount');
643
- this.processBtn = document.getElementById('processBtn');
644
- this.clearBtn = document.getElementById('clearBtn');
645
- this.sampleBtn = document.getElementById('sampleBtn');
646
- this.loadingSection = document.getElementById('loadingSection');
647
- this.progressFill = document.getElementById('progressFill');
648
- this.resultsSection = document.getElementById('resultsSection');
649
- this.resultsContainer = document.getElementById('resultsContainer');
650
- this.successCount = document.getElementById('successCount');
651
- this.downloadAllBtn = document.getElementById('downloadAllBtn');
652
- this.statsContainer = document.getElementById('statsContainer');
653
- this.toast = document.getElementById('toast');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
654
  }
655
-
656
- bindEvents() {
657
- this.urlInput.addEventListener('input', () => this.updateUrlCount());
658
- this.processBtn.addEventListener('click', () => this.processUrls());
659
- this.clearBtn.addEventListener('click', () => this.clearInput());
660
- this.sampleBtn.addEventListener('click', () => this.loadSample());
661
- this.downloadAllBtn.addEventListener('click', () => this.downloadAll());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
662
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
663
 
664
- updateUrlCount() {
665
- const urls = this.extractUrls();
666
- const count = urls.length;
667
- this.urlCount.textContent = `${count} URL${count !== 1 ? 's' : ''}`;
668
- this.processBtn.disabled = count === 0;
669
- }
 
670
 
671
- extractUrls() {
672
- const text = this.urlInput.value.trim();
673
- if (!text) return [];
674
- return text.split('\n').filter(url => url.trim()).map(url => url.trim());
675
- }
 
 
 
 
 
676
 
677
- async processUrls() {
678
- const urls = this.extractUrls();
679
- if (urls.length === 0) return;
 
 
 
 
 
 
680
 
681
- this.showLoading(true);
682
- this.processedResults = [];
683
-
684
- let completed = 0;
685
- const total = urls.length;
686
-
687
- for (const [index, originalUrl] of urls.entries()) {
688
- const result = await this.processSingleUrl(originalUrl);
689
- this.processedResults.push(result);
690
- completed++;
691
- this.updateProgress((completed / total) * 100);
692
-
693
- // Add result to UI with animation
694
- if (index === 0) {
695
- this.showResults();
696
- }
697
- this.addResultToUI(result, index + 1);
698
- }
699
 
700
- this.updateStats();
701
- this.showLoading(false);
702
- this.showToast('Processing complete!', 'success');
703
- }
 
 
 
 
 
704
 
705
- async processSingleUrl(originalUrl) {
706
- try {
707
- // Step a: Extract Target URL
708
- const baseUrl = originalUrl.split('?')[0];
709
-
710
- // Step b: Identify Video ID
711
- const urlParams = new URLSearchParams(originalUrl.split('?')[1] || '');
712
- const modalId = urlParams.get('modal_id');
713
- const targetUrl = modalId ? `https://www.douyin.com/video/${modalId}` : baseUrl;
714
-
715
- // Step c: Generate API Hash
716
- const hash = this.generateHash(targetUrl);
717
-
718
- // Step d: Simulate API Call
719
- const response = await this.simulateApiCall(targetUrl, hash);
720
-
721
- if (response.success && response.data.medias && response.data.medias.length > 0) {
722
- // Step f: Sort and Select Best Media
723
- const sortedMedias = response.data.medias.sort((a, b) => (b.size || 0) - (a.size || 0));
724
- const bestMedia = sortedMedias[0];
725
-
726
- // Step g: Extract Download Link
727
- return {
728
- originalUrl,
729
- targetUrl,
730
- downloadUrl: bestMedia.url,
731
- quality: bestMedia.quality || 'Unknown',
732
- size: bestMedia.formattedSize || 'Unknown',
733
- status: 'success'
734
- };
735
- } else {
736
- throw new Error('No media found');
737
- }
738
- } catch (error) {
739
- // Step h: Handle Errors
740
- const targetUrl = originalUrl.split('?')[0];
741
- const fallbackUrl = `https://snapdouyin.app/#url=${encodeURIComponent(targetUrl)}`;
742
- return {
743
- originalUrl,
744
- targetUrl,
745
- downloadUrl: fallbackUrl,
746
- quality: 'Fallback',
747
- size: 'Unknown',
748
- status: 'fallback'
749
- };
750
- }
751
- }
752
 
753
- generateHash(targetUrl) {
754
- return btoa(targetUrl) + (targetUrl.length + 1000) + btoa('aio-dl');
755
- }
 
 
 
 
 
 
 
756
 
757
- async simulateApiCall(targetUrl, hash) {
758
- // Simulate API response with realistic data
759
- await new Promise(resolve => setTimeout(resolve, 300 + Math.random() * 500));
760
-
761
- // Simulate different response scenarios
762
- if (Math.random() > 0.1) { // 90% success rate
763
- const medias = [
764
- {
765
- url: `https://v3-dy-o.zjcdn.com/${Date.now()}_1080p.mp4`,
766
- quality: '1080p',
767
- size: 106 * 1024 * 1024,
768
- formattedSize: '106MB'
769
- },
770
- {
771
- url: `https://v3-dy-o.zjcdn.com/${Date.now()}_720p.mp4`,
772
- quality: '720p',
773
- size: 96 * 1024 * 1024,
774
- formattedSize: '96MB'
775
- },
776
- {
777
- url: `https://v3-dy-o.zjcdn.com/${Date.now()}_480p.mp4`,
778
- quality: '480p',
779
- size: 56 * 1024 * 1024,
780
- formattedSize: '56MB'
781
- }
782
- ];
783
-
784
- return {
785
- success: true,
786
- data: { medias }
787
- };
788
- } else {
789
- throw new Error('API Error');
790
- }
791
- }
792
 
793
- showLoading(show) {
794
- this.loadingSection.classList.toggle('active', show);
795
- this.processBtn.disabled = show;
796
- if (!show) {
797
- this.progressFill.style.width = '0%';
798
- }
799
- }
 
800
 
801
- updateProgress(percentage) {
802
- this.progressFill.style.width = `${percentage}%`;
803
- }
 
 
 
 
 
 
 
804
 
805
- showResults() {
806
- this.resultsSection.classList.add('active');
807
- this.resultsSection.classList.add('fade-in');
808
- }
 
 
 
 
 
809
 
810
- addResultToUI(result, index) {
811
- const resultItem = document.createElement('div');
812
- resultItem.className = 'result-item fade-in';
813
- resultItem.style.animationDelay = `${index * 0.1}s`;
814
-
815
- resultItem.innerHTML = `
816
- <div class="result-header">
817
- <div class="result-index">${index}</div>
818
- <div class="result-status">
819
- <span class="status-badge ${result.status === 'success' ? 'status-success' : 'status-fallback'}">
820
- <i class="fas ${result.status === 'success' ? 'fa-check' : 'fa-exclamation-triangle'}"></i>
821
- ${result.status === 'success' ? 'Success' : 'Fallback'}
822
- </span>
823
- ${result.quality !== 'Unknown' ? `<span style="color: var(--text-secondary)">${result.quality}</span>` : ''}
824
- ${result.size !== 'Unknown' ? `<span style="color: var(--text-secondary)">${result.size}</span>` : ''}
825
- </div>
826
- </div>
827
- <div class="original-url">
828
- <i class="fas fa-link"></i> ${result.originalUrl}
829
- </div>
830
- <div class="download-link-container">
831
- <div class="download-link">${result.downloadUrl}</div>
832
- <button class="btn-icon" onclick="window.open('${result.downloadUrl}', '_blank')">
833
- <i class="fas fa-external-link-alt"></i>
834
- </button>
835
- <button class="btn-icon" onclick="navigator.clipboard.writeText('${result.downloadUrl}'); window.downloader.showToast('Copied!', 'success')">
836
  <i class="fas fa-copy"></i>
 
837
  </button>
838
  </div>
839
- `;
840
-
841
- this.resultsContainer.appendChild(resultItem);
842
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
843
 
844
- updateStats() {
845
- const total = this.processedResults.length;
846
- const success = this.processedResults.filter(r => r.status === 'success').length;
847
- const fallback = total - success;
848
-
849
- this.successCount.textContent = success;
850
-
851
- this.statsContainer.innerHTML = `
852
- <div class="stat-item">
853
- <div class="stat-value">${total}</div>
854
- <div class="stat-label">Total URLs</div>
855
- </div>
856
- <div class="stat-item">
857
- <div class="stat-value">${success}</div>
858
- <div class="stat-label">Success</div>
859
  </div>
860
- <div class="stat-item">
861
- <div class="stat-value">${fallback}</div>
862
- <div class="stat-label">Fallback</div>
863
- </div>
864
- <div class="stat-item">
865
- <div class="stat-value">${Math.round((success / total) * 100)}%</div>
866
- <div class="stat-label">Success Rate</div>
867
- </div>
868
- `;
869
- }
870
-
871
- downloadAll() {
872
- this.processedResults.forEach(result => {
873
- window.open(result.downloadUrl, '_blank');
874
- });
875
- this.showToast('Opening all download links...', 'success');
876
- }
877
-
878
- clearInput() {
879
- this.urlInput.value = '';
880
- this.updateUrlCount();
881
- this.resultsSection.classList.remove('active');
882
- this.resultsContainer.innerHTML = '';
883
- this.processedResults = [];
884
- }
885
-
886
- loadSample() {
887
- const sampleUrls = `https://www.douyin.com/video/7123456789012345678?region=CN&mid=7123456789012345678
888
- https://v.douyin.com/abcdef123456/
889
- https://www.douyin.com/user/123456789?modal_id=9876543210123456789
890
- https://www.douyin.com/video/7234567890123456789?u_code=0&did=0&iid=0`;
891
- this.urlInput.value = sampleUrls;
892
- this.updateUrlCount();
893
- this.showToast('Sample URLs loaded!', 'success');
894
- }
895
 
896
- showToast(message, type = 'success') {
897
- const toastIcon = this.toast.querySelector('.toast-icon');
898
- const toastMessage = this.toast.querySelector('.toast-message');
899
-
900
- toastIcon.className = `toast-icon fas ${type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle'}`;
901
- toastMessage.textContent = message;
902
- this.toast.className = `toast ${type === 'success' ? 'toast-success' : 'toast-error'}`;
903
-
904
- this.toast.classList.add('show');
905
-
906
- setTimeout(() => {
907
- this.toast.classList.remove('show');
908
- }, 3000);
909
- }
910
- }
911
 
912
- // Initialize the application
913
- window.downloader = new DouyinDownloader();
914
- </script>
915
- </body>
916
- </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 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
  * {
 
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
 
 
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;
 
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