nitubhai commited on
Commit
43ab156
·
verified ·
1 Parent(s): 0b342ce

Upload app.js

Browse files
Files changed (1) hide show
  1. static/app.js +573 -0
static/app.js ADDED
@@ -0,0 +1,573 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class YouTubeAutomation {
2
+ constructor() {
3
+ this.isAuthenticated = false;
4
+ this.currentTaskId = null;
5
+ this.statusCheckInterval = null;
6
+ this.init();
7
+ }
8
+
9
+ init() {
10
+ this.cacheElements();
11
+ this.attachEventListeners();
12
+ this.checkAuthentication();
13
+ }
14
+
15
+ cacheElements() {
16
+ // Auth elements
17
+ this.authBtn = document.getElementById('authBtn');
18
+ this.authStatus = document.getElementById('authStatus');
19
+ this.logoutBtn = document.getElementById('logoutBtn');
20
+
21
+ // Channel info
22
+ this.channelInfo = document.getElementById('channelInfo');
23
+ this.channelAvatar = document.getElementById('channelAvatar');
24
+ this.channelName = document.getElementById('channelName');
25
+ this.channelStats = document.getElementById('channelStats');
26
+
27
+ // Upload section
28
+ this.uploadSection = document.getElementById('uploadSection');
29
+ this.reelUrl = document.getElementById('reelUrl');
30
+ this.uploadBtn = document.getElementById('uploadBtn');
31
+ this.downloadBtn = document.getElementById('downloadBtn');
32
+ this.previewBtn = document.getElementById('previewBtn');
33
+
34
+ // Progress
35
+ this.progressSection = document.getElementById('progressSection');
36
+ this.progressTitle = document.getElementById('progressTitle');
37
+ this.progressPercent = document.getElementById('progressPercent');
38
+ this.progressFill = document.getElementById('progressFill');
39
+ this.progressMessage = document.getElementById('progressMessage');
40
+
41
+ // Metadata preview
42
+ this.metadataPreview = document.getElementById('metadataPreview');
43
+ this.previewTitle = document.getElementById('previewTitle');
44
+ this.previewDescription = document.getElementById('previewDescription');
45
+ this.previewTags = document.getElementById('previewTags');
46
+ this.previewHashtags = document.getElementById('previewHashtags');
47
+
48
+ // Results
49
+ this.successResult = document.getElementById('successResult');
50
+ this.errorResult = document.getElementById('errorResult');
51
+ this.watchBtn = document.getElementById('watchBtn');
52
+ this.errorMessage = document.getElementById('errorMessage');
53
+ this.retryBtn = document.getElementById('retryBtn');
54
+
55
+ // Overlay
56
+ this.loadingOverlay = document.getElementById('loadingOverlay');
57
+ this.toast = document.getElementById('toast');
58
+
59
+ // Navbar elements
60
+ this.navSignInBtn = document.getElementById('navSignInBtn');
61
+ this.navAuthButtons = document.getElementById('navAuthButtons');
62
+ this.navUserMenu = document.getElementById('navUserMenu');
63
+ this.navUserAvatar = document.getElementById('navUserAvatar');
64
+ this.navUserAvatarImg = document.getElementById('navUserAvatarImg');
65
+ this.navUserName = document.getElementById('navUserName');
66
+ this.navUserStats = document.getElementById('navUserStats');
67
+ this.navLogoutBtn = document.getElementById('navLogoutBtn');
68
+ this.navDashboard = document.getElementById('navDashboard');
69
+ this.navSettings = document.getElementById('navSettings');
70
+
71
+ // Mobile menu
72
+ this.mobileMenuToggle = document.getElementById('mobileMenuToggle');
73
+ this.mobileMenu = document.getElementById('mobileMenu');
74
+ this.mobileSignInBtn = document.getElementById('mobileSignInBtn');
75
+
76
+ // Modal elements
77
+ this.signInModal = document.getElementById('signInModal');
78
+ this.closeSignInModal = document.getElementById('closeSignInModal');
79
+ this.modalSignInBtn = document.getElementById('modalSignInBtn');
80
+ }
81
+
82
+ attachEventListeners() {
83
+ this.authBtn.addEventListener('click', () => this.handleAuthentication());
84
+ this.logoutBtn.addEventListener('click', () => this.handleLogout());
85
+ this.uploadBtn.addEventListener('click', () => this.handleUpload());
86
+ this.downloadBtn.addEventListener('click', () => this.handleDownload());
87
+ this.previewBtn.addEventListener('click', () => this.handlePreview());
88
+ this.retryBtn.addEventListener('click', () => this.resetForm());
89
+
90
+ // Enter key for URL input
91
+ this.reelUrl.addEventListener('keypress', (e) => {
92
+ if (e.key === 'Enter') {
93
+ this.handleUpload();
94
+ }
95
+ });
96
+
97
+ // Navbar authentication
98
+ if (this.navSignInBtn) {
99
+ this.navSignInBtn.addEventListener('click', () => this.openSignInModal());
100
+ }
101
+ if (this.navLogoutBtn) {
102
+ this.navLogoutBtn.addEventListener('click', () => this.handleLogout());
103
+ }
104
+ if (this.navDashboard) {
105
+ this.navDashboard.addEventListener('click', () => this.scrollToUploadSection());
106
+ }
107
+
108
+ // Mobile menu
109
+ if (this.mobileMenuToggle) {
110
+ this.mobileMenuToggle.addEventListener('click', () => this.toggleMobileMenu());
111
+ }
112
+ if (this.mobileSignInBtn) {
113
+ this.mobileSignInBtn.addEventListener('click', () => {
114
+ this.closeMobileMenu();
115
+ this.openSignInModal();
116
+ });
117
+ }
118
+
119
+ // Modal
120
+ if (this.closeSignInModal) {
121
+ this.closeSignInModal.addEventListener('click', () => this.closeSignInModal_());
122
+ }
123
+ if (this.modalSignInBtn) {
124
+ this.modalSignInBtn.addEventListener('click', () => {
125
+ this.closeSignInModal_();
126
+ this.handleAuthentication();
127
+ });
128
+ }
129
+ if (this.signInModal) {
130
+ this.signInModal.addEventListener('click', (e) => {
131
+ if (e.target === this.signInModal) {
132
+ this.closeSignInModal_();
133
+ }
134
+ });
135
+ }
136
+
137
+ // Close mobile menu when clicking outside
138
+ document.addEventListener('click', (e) => {
139
+ if (this.mobileMenu &&
140
+ this.mobileMenu.classList.contains('active') &&
141
+ !this.mobileMenu.contains(e.target) &&
142
+ !this.mobileMenuToggle.contains(e.target)) {
143
+ this.closeMobileMenu();
144
+ }
145
+ });
146
+ }
147
+
148
+ async checkAuthentication() {
149
+ try {
150
+ const response = await fetch('/check-auth');
151
+ const data = await response.json();
152
+
153
+ if (data.authenticated) {
154
+ this.isAuthenticated = true;
155
+ await this.loadChannelInfo();
156
+ this.showUploadSection();
157
+ this.updateNavbarAuth(true);
158
+ } else {
159
+ this.showAuthButton();
160
+ this.updateNavbarAuth(false);
161
+ }
162
+ } catch (error) {
163
+ console.error('Auth check failed:', error);
164
+ this.showAuthButton();
165
+ this.updateNavbarAuth(false);
166
+ }
167
+ }
168
+
169
+ updateNavbarAuth(isAuthenticated) {
170
+ if (isAuthenticated) {
171
+ this.navAuthButtons.style.display = 'none';
172
+ this.navUserMenu.style.display = 'block';
173
+ } else {
174
+ this.navAuthButtons.style.display = 'flex';
175
+ this.navUserMenu.style.display = 'none';
176
+ }
177
+ }
178
+
179
+ async loadChannelInfo() {
180
+ try {
181
+ const response = await fetch('/get-channel-info');
182
+ const data = await response.json();
183
+
184
+ if (data.authenticated && data.channel) {
185
+ const channel = data.channel;
186
+
187
+ // Update main channel info
188
+ this.channelAvatar.src = channel.thumbnail || 'https://via.placeholder.com/80';
189
+ this.channelName.textContent = channel.title;
190
+ this.channelStats.textContent = `${this.formatNumber(channel.subscriberCount)} subscribers • ${this.formatNumber(channel.videoCount)} videos`;
191
+ this.channelInfo.style.display = 'block';
192
+
193
+ // Update navbar user info
194
+ this.navUserAvatarImg.src = channel.thumbnail || 'https://via.placeholder.com/40';
195
+ this.navUserName.textContent = channel.title;
196
+ this.navUserStats.textContent = `${this.formatNumber(channel.subscriberCount)} subscribers`;
197
+ }
198
+ } catch (error) {
199
+ console.error('Failed to load channel info:', error);
200
+ }
201
+ }
202
+
203
+ openSignInModal() {
204
+ this.signInModal.classList.add('active');
205
+ document.body.style.overflow = 'hidden';
206
+ }
207
+
208
+ closeSignInModal_() {
209
+ this.signInModal.classList.remove('active');
210
+ document.body.style.overflow = 'auto';
211
+ }
212
+
213
+ toggleMobileMenu() {
214
+ this.mobileMenu.classList.toggle('active');
215
+ }
216
+
217
+ closeMobileMenu() {
218
+ this.mobileMenu.classList.remove('active');
219
+ }
220
+
221
+ scrollToUploadSection() {
222
+ if (this.uploadSection && this.uploadSection.style.display !== 'none') {
223
+ this.uploadSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
224
+ }
225
+ }
226
+
227
+ async handleAuthentication() {
228
+ this.showLoading();
229
+
230
+ try {
231
+ // First try to start OAuth flow for server environments
232
+ const response = await fetch('/auth/start');
233
+ const data = await response.json();
234
+
235
+ if (data.auth_url) {
236
+ // Open auth URL in new window
237
+ const authWindow = window.open(data.auth_url, 'YouTube Authentication', 'width=600,height=700');
238
+
239
+ // Poll for authentication completion
240
+ const checkAuth = setInterval(async () => {
241
+ if (authWindow.closed) {
242
+ clearInterval(checkAuth);
243
+ this.hideLoading();
244
+ await this.checkAuthentication();
245
+ }
246
+ }, 1000);
247
+ } else {
248
+ // Fallback to local authentication
249
+ const authResponse = await fetch('/authenticate', {
250
+ method: 'POST',
251
+ headers: { 'Content-Type': 'application/json' }
252
+ });
253
+
254
+ const authData = await authResponse.json();
255
+
256
+ if (authData.success) {
257
+ this.showToast('Authentication successful!', 'success');
258
+ await this.checkAuthentication();
259
+ } else {
260
+ throw new Error(authData.error || 'Authentication failed');
261
+ }
262
+ }
263
+ } catch (error) {
264
+ console.error('Authentication error:', error);
265
+ this.showToast('Authentication failed: ' + error.message, 'error');
266
+ } finally {
267
+ this.hideLoading();
268
+ }
269
+ }
270
+
271
+ async handleLogout() {
272
+ if (!confirm('Are you sure you want to logout?')) return;
273
+
274
+ this.showLoading();
275
+
276
+ try {
277
+ const response = await fetch('/logout', { method: 'POST' });
278
+ const data = await response.json();
279
+
280
+ if (data.success) {
281
+ this.showToast('Logged out successfully', 'success');
282
+ this.isAuthenticated = false;
283
+ this.channelInfo.style.display = 'none';
284
+ this.uploadSection.style.display = 'none';
285
+ this.showAuthButton();
286
+ this.updateNavbarAuth(false);
287
+ }
288
+ } catch (error) {
289
+ this.showToast('Logout failed: ' + error.message, 'error');
290
+ } finally {
291
+ this.hideLoading();
292
+ }
293
+ }
294
+
295
+ async handlePreview() {
296
+ const url = this.reelUrl.value.trim();
297
+
298
+ if (!url) {
299
+ this.showToast('Please enter a valid Instagram Reel URL', 'error');
300
+ return;
301
+ }
302
+
303
+ this.showLoading();
304
+ this.hideResults();
305
+
306
+ try {
307
+ const response = await fetch('/generate-preview', {
308
+ method: 'POST',
309
+ headers: { 'Content-Type': 'application/json' },
310
+ body: JSON.stringify({ url })
311
+ });
312
+
313
+ const data = await response.json();
314
+
315
+ if (data.success) {
316
+ this.displayMetadataPreview(data);
317
+ this.showToast('AI metadata generated successfully!', 'success');
318
+ } else {
319
+ throw new Error(data.error || 'Preview generation failed');
320
+ }
321
+ } catch (error) {
322
+ this.showToast('Preview failed: ' + error.message, 'error');
323
+ } finally {
324
+ this.hideLoading();
325
+ }
326
+ }
327
+
328
+ async handleUpload() {
329
+ if (!this.isAuthenticated) {
330
+ this.showToast('Please sign in with YouTube first', 'error');
331
+ return;
332
+ }
333
+
334
+ const url = this.reelUrl.value.trim();
335
+
336
+ if (!url) {
337
+ this.showToast('Please enter a valid Instagram Reel URL', 'error');
338
+ return;
339
+ }
340
+
341
+ this.hideResults();
342
+ this.showProgress();
343
+
344
+ try {
345
+ const response = await fetch('/auto-upload-async', {
346
+ method: 'POST',
347
+ headers: { 'Content-Type': 'application/json' },
348
+ body: JSON.stringify({ url })
349
+ });
350
+
351
+ const data = await response.json();
352
+
353
+ if (data.success) {
354
+ this.currentTaskId = data.task_id;
355
+ this.startStatusPolling();
356
+ } else {
357
+ throw new Error(data.error || 'Upload failed');
358
+ }
359
+ } catch (error) {
360
+ this.showError(error.message);
361
+ }
362
+ }
363
+
364
+ async handleDownload() {
365
+ const url = this.reelUrl.value.trim();
366
+
367
+ if (!url) {
368
+ this.showToast('Please enter a valid Instagram Reel URL', 'error');
369
+ return;
370
+ }
371
+
372
+ this.showLoading();
373
+
374
+ try {
375
+ const response = await fetch('/download', {
376
+ method: 'POST',
377
+ headers: { 'Content-Type': 'application/json' },
378
+ body: JSON.stringify({ url })
379
+ });
380
+
381
+ const data = await response.json();
382
+
383
+ if (data.success) {
384
+ this.showToast('Download completed! Check your downloads folder.', 'success');
385
+
386
+ // Trigger file download
387
+ window.location.href = `/get-video/${data.filename}`;
388
+ } else {
389
+ throw new Error(data.error || 'Download failed');
390
+ }
391
+ } catch (error) {
392
+ this.showToast('Download failed: ' + error.message, 'error');
393
+ } finally {
394
+ this.hideLoading();
395
+ }
396
+ }
397
+
398
+ startStatusPolling() {
399
+ this.statusCheckInterval = setInterval(async () => {
400
+ await this.checkTaskStatus();
401
+ }, 2000);
402
+ }
403
+
404
+ async checkTaskStatus() {
405
+ if (!this.currentTaskId) return;
406
+
407
+ try {
408
+ const response = await fetch(`/task-status/${this.currentTaskId}`);
409
+ const data = await response.json();
410
+
411
+ if (data.success) {
412
+ const task = data.task;
413
+ this.updateProgress(task);
414
+
415
+ if (task.status === 'completed') {
416
+ this.stopStatusPolling();
417
+ this.showSuccess(task);
418
+ } else if (task.status === 'failed') {
419
+ this.stopStatusPolling();
420
+ this.showError(task.error || 'Upload failed');
421
+ }
422
+ }
423
+ } catch (error) {
424
+ console.error('Status check failed:', error);
425
+ }
426
+ }
427
+
428
+ stopStatusPolling() {
429
+ if (this.statusCheckInterval) {
430
+ clearInterval(this.statusCheckInterval);
431
+ this.statusCheckInterval = null;
432
+ }
433
+ }
434
+
435
+ updateProgress(task) {
436
+ this.progressTitle.textContent = this.getStatusTitle(task.status);
437
+ this.progressPercent.textContent = `${task.progress}%`;
438
+ this.progressFill.style.width = `${task.progress}%`;
439
+ this.progressMessage.textContent = task.message;
440
+
441
+ // Show metadata if available
442
+ if (task.metadata && task.status === 'uploading') {
443
+ this.displayMetadataPreview(task.metadata);
444
+ }
445
+ }
446
+
447
+ getStatusTitle(status) {
448
+ const titles = {
449
+ 'started': 'Starting...',
450
+ 'downloading': 'Downloading Reel',
451
+ 'generating_metadata': 'AI Analyzing Video',
452
+ 'uploading': 'Uploading to YouTube',
453
+ 'completed': 'Upload Complete',
454
+ 'failed': 'Upload Failed'
455
+ };
456
+ return titles[status] || 'Processing...';
457
+ }
458
+
459
+ displayMetadataPreview(metadata) {
460
+ this.previewTitle.textContent = metadata.title || '-';
461
+ this.previewDescription.textContent = metadata.description || '-';
462
+
463
+ // Display tags
464
+ this.previewTags.innerHTML = '';
465
+ if (metadata.tags && metadata.tags.length > 0) {
466
+ metadata.tags.slice(0, 15).forEach(tag => {
467
+ const span = document.createElement('span');
468
+ span.textContent = tag;
469
+ this.previewTags.appendChild(span);
470
+ });
471
+ }
472
+
473
+ // Display hashtags
474
+ this.previewHashtags.innerHTML = '';
475
+ if (metadata.hashtags && metadata.hashtags.length > 0) {
476
+ metadata.hashtags.slice(0, 20).forEach(hashtag => {
477
+ const span = document.createElement('span');
478
+ span.textContent = hashtag;
479
+ this.previewHashtags.appendChild(span);
480
+ });
481
+ }
482
+
483
+ this.metadataPreview.style.display = 'block';
484
+ }
485
+
486
+ showProgress() {
487
+ this.progressSection.style.display = 'block';
488
+ this.metadataPreview.style.display = 'none';
489
+ this.successResult.style.display = 'none';
490
+ this.errorResult.style.display = 'none';
491
+ }
492
+
493
+ showSuccess(task) {
494
+ this.progressSection.style.display = 'none';
495
+ this.successResult.style.display = 'block';
496
+
497
+ if (task.youtube_url) {
498
+ this.watchBtn.href = task.youtube_url;
499
+ }
500
+
501
+ this.showToast('Video uploaded successfully! 🎉', 'success');
502
+ }
503
+
504
+ showError(message) {
505
+ this.progressSection.style.display = 'none';
506
+ this.errorResult.style.display = 'block';
507
+ this.errorMessage.textContent = message;
508
+ this.showToast('Upload failed: ' + message, 'error');
509
+ }
510
+
511
+ hideResults() {
512
+ this.successResult.style.display = 'none';
513
+ this.errorResult.style.display = 'none';
514
+ this.metadataPreview.style.display = 'none';
515
+ }
516
+
517
+ resetForm() {
518
+ this.reelUrl.value = '';
519
+ this.hideResults();
520
+ this.progressSection.style.display = 'none';
521
+ this.currentTaskId = null;
522
+ this.stopStatusPolling();
523
+ }
524
+
525
+ showUploadSection() {
526
+ if (this.authBtn) this.authBtn.style.display = 'none';
527
+ this.uploadSection.style.display = 'block';
528
+ }
529
+
530
+ showAuthButton() {
531
+ if (this.authBtn) this.authBtn.style.display = 'inline-flex';
532
+ this.uploadSection.style.display = 'none';
533
+ }
534
+
535
+ showLoading() {
536
+ this.loadingOverlay.style.display = 'flex';
537
+ }
538
+
539
+ hideLoading() {
540
+ this.loadingOverlay.style.display = 'none';
541
+ }
542
+
543
+ showToast(message, type = 'info') {
544
+ this.toast.textContent = message;
545
+ this.toast.className = 'toast show';
546
+
547
+ if (type === 'success') {
548
+ this.toast.style.borderLeft = '4px solid var(--success)';
549
+ } else if (type === 'error') {
550
+ this.toast.style.borderLeft = '4px solid var(--error)';
551
+ } else {
552
+ this.toast.style.borderLeft = '4px solid var(--secondary)';
553
+ }
554
+
555
+ setTimeout(() => {
556
+ this.toast.classList.remove('show');
557
+ }, 4000);
558
+ }
559
+
560
+ formatNumber(num) {
561
+ if (num >= 1000000) {
562
+ return (num / 1000000).toFixed(1) + 'M';
563
+ } else if (num >= 1000) {
564
+ return (num / 1000).toFixed(1) + 'K';
565
+ }
566
+ return num;
567
+ }
568
+ }
569
+
570
+ // Initialize app when DOM is ready
571
+ document.addEventListener('DOMContentLoaded', () => {
572
+ new YouTubeAutomation();
573
+ });