class YouTubeAutomation { constructor() { this.isAuthenticated = false; this.currentTaskId = null; this.statusCheckInterval = null; this.init(); } init() { this.cacheElements(); this.attachEventListeners(); this.checkAuthentication(); } cacheElements() { // Auth elements this.authBtn = document.getElementById('authBtn'); this.authStatus = document.getElementById('authStatus'); this.logoutBtn = document.getElementById('logoutBtn'); // Channel info this.channelInfo = document.getElementById('channelInfo'); this.channelAvatar = document.getElementById('channelAvatar'); this.channelName = document.getElementById('channelName'); this.channelStats = document.getElementById('channelStats'); // Upload section this.uploadSection = document.getElementById('uploadSection'); this.reelUrl = document.getElementById('reelUrl'); this.uploadBtn = document.getElementById('uploadBtn'); this.downloadBtn = document.getElementById('downloadBtn'); this.previewBtn = document.getElementById('previewBtn'); // Progress this.progressSection = document.getElementById('progressSection'); this.progressTitle = document.getElementById('progressTitle'); this.progressPercent = document.getElementById('progressPercent'); this.progressFill = document.getElementById('progressFill'); this.progressMessage = document.getElementById('progressMessage'); // Metadata preview this.metadataPreview = document.getElementById('metadataPreview'); this.previewTitle = document.getElementById('previewTitle'); this.previewDescription = document.getElementById('previewDescription'); this.previewTags = document.getElementById('previewTags'); this.previewHashtags = document.getElementById('previewHashtags'); // Results this.successResult = document.getElementById('successResult'); this.errorResult = document.getElementById('errorResult'); this.watchBtn = document.getElementById('watchBtn'); this.errorMessage = document.getElementById('errorMessage'); this.retryBtn = document.getElementById('retryBtn'); // Overlay this.loadingOverlay = document.getElementById('loadingOverlay'); this.toast = document.getElementById('toast'); // Navbar elements this.navSignInBtn = document.getElementById('navSignInBtn'); this.navAuthButtons = document.getElementById('navAuthButtons'); this.navUserMenu = document.getElementById('navUserMenu'); this.navUserAvatar = document.getElementById('navUserAvatar'); this.navUserAvatarImg = document.getElementById('navUserAvatarImg'); this.navUserName = document.getElementById('navUserName'); this.navUserStats = document.getElementById('navUserStats'); this.navLogoutBtn = document.getElementById('navLogoutBtn'); this.navDashboard = document.getElementById('navDashboard'); this.navSettings = document.getElementById('navSettings'); // Mobile menu this.mobileMenuToggle = document.getElementById('mobileMenuToggle'); this.mobileMenu = document.getElementById('mobileMenu'); this.mobileSignInBtn = document.getElementById('mobileSignInBtn'); // Modal elements this.signInModal = document.getElementById('signInModal'); this.closeSignInModal = document.getElementById('closeSignInModal'); this.modalSignInBtn = document.getElementById('modalSignInBtn'); } attachEventListeners() { this.authBtn.addEventListener('click', () => this.handleAuthentication()); this.logoutBtn.addEventListener('click', () => this.handleLogout()); this.uploadBtn.addEventListener('click', () => this.handleUpload()); this.downloadBtn.addEventListener('click', () => this.handleDownload()); this.previewBtn.addEventListener('click', () => this.handlePreview()); this.retryBtn.addEventListener('click', () => this.resetForm()); // Enter key for URL input this.reelUrl.addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.handleUpload(); } }); // Navbar authentication if (this.navSignInBtn) { this.navSignInBtn.addEventListener('click', () => this.openSignInModal()); } if (this.navLogoutBtn) { this.navLogoutBtn.addEventListener('click', () => this.handleLogout()); } if (this.navDashboard) { this.navDashboard.addEventListener('click', () => this.scrollToUploadSection()); } // Mobile menu if (this.mobileMenuToggle) { this.mobileMenuToggle.addEventListener('click', () => this.toggleMobileMenu()); } if (this.mobileSignInBtn) { this.mobileSignInBtn.addEventListener('click', () => { this.closeMobileMenu(); this.openSignInModal(); }); } // Modal if (this.closeSignInModal) { this.closeSignInModal.addEventListener('click', () => this.closeSignInModal_()); } if (this.modalSignInBtn) { this.modalSignInBtn.addEventListener('click', () => { this.closeSignInModal_(); this.handleAuthentication(); }); } if (this.signInModal) { this.signInModal.addEventListener('click', (e) => { if (e.target === this.signInModal) { this.closeSignInModal_(); } }); } // Close mobile menu when clicking outside document.addEventListener('click', (e) => { if (this.mobileMenu && this.mobileMenu.classList.contains('active') && !this.mobileMenu.contains(e.target) && !this.mobileMenuToggle.contains(e.target)) { this.closeMobileMenu(); } }); } async checkAuthentication() { try { const response = await fetch('/check-auth'); const data = await response.json(); if (data.authenticated) { this.isAuthenticated = true; await this.loadChannelInfo(); this.showUploadSection(); this.updateNavbarAuth(true); } else { this.showAuthButton(); this.updateNavbarAuth(false); } } catch (error) { console.error('Auth check failed:', error); this.showAuthButton(); this.updateNavbarAuth(false); } } updateNavbarAuth(isAuthenticated) { if (isAuthenticated) { this.navAuthButtons.style.display = 'none'; this.navUserMenu.style.display = 'block'; } else { this.navAuthButtons.style.display = 'flex'; this.navUserMenu.style.display = 'none'; } } async loadChannelInfo() { try { const response = await fetch('/get-channel-info'); const data = await response.json(); if (data.authenticated && data.channel) { const channel = data.channel; // Update main channel info this.channelAvatar.src = channel.thumbnail || 'https://via.placeholder.com/80'; this.channelName.textContent = channel.title; this.channelStats.textContent = `${this.formatNumber(channel.subscriberCount)} subscribers • ${this.formatNumber(channel.videoCount)} videos`; this.channelInfo.style.display = 'block'; // Update navbar user info this.navUserAvatarImg.src = channel.thumbnail || 'https://via.placeholder.com/40'; this.navUserName.textContent = channel.title; this.navUserStats.textContent = `${this.formatNumber(channel.subscriberCount)} subscribers`; } } catch (error) { console.error('Failed to load channel info:', error); } } openSignInModal() { this.signInModal.classList.add('active'); document.body.style.overflow = 'hidden'; } closeSignInModal_() { this.signInModal.classList.remove('active'); document.body.style.overflow = 'auto'; } toggleMobileMenu() { this.mobileMenu.classList.toggle('active'); } closeMobileMenu() { this.mobileMenu.classList.remove('active'); } scrollToUploadSection() { if (this.uploadSection && this.uploadSection.style.display !== 'none') { this.uploadSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); } } async handleAuthentication() { this.showLoading(); try { // First try to start OAuth flow for server environments const response = await fetch('/auth/start'); const data = await response.json(); if (data.auth_url) { // Open auth URL in new window const authWindow = window.open(data.auth_url, 'YouTube Authentication', 'width=600,height=700'); // Poll for authentication completion const checkAuth = setInterval(async () => { if (authWindow.closed) { clearInterval(checkAuth); this.hideLoading(); await this.checkAuthentication(); } }, 1000); } else { // Fallback to local authentication const authResponse = await fetch('/authenticate', { method: 'POST', headers: { 'Content-Type': 'application/json' } }); const authData = await authResponse.json(); if (authData.success) { this.showToast('Authentication successful!', 'success'); await this.checkAuthentication(); } else { throw new Error(authData.error || 'Authentication failed'); } } } catch (error) { console.error('Authentication error:', error); this.showToast('Authentication failed: ' + error.message, 'error'); } finally { this.hideLoading(); } } async handleLogout() { if (!confirm('Are you sure you want to logout?')) return; this.showLoading(); try { const response = await fetch('/logout', { method: 'POST' }); const data = await response.json(); if (data.success) { this.showToast('Logged out successfully', 'success'); this.isAuthenticated = false; this.channelInfo.style.display = 'none'; this.uploadSection.style.display = 'none'; this.showAuthButton(); this.updateNavbarAuth(false); } } catch (error) { this.showToast('Logout failed: ' + error.message, 'error'); } finally { this.hideLoading(); } } async handlePreview() { const url = this.reelUrl.value.trim(); if (!url) { this.showToast('Please enter a valid Instagram Reel URL', 'error'); return; } this.showLoading(); this.hideResults(); try { const response = await fetch('/generate-preview', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }) }); const data = await response.json(); if (data.success) { this.displayMetadataPreview(data); this.showToast('AI metadata generated successfully!', 'success'); } else { throw new Error(data.error || 'Preview generation failed'); } } catch (error) { this.showToast('Preview failed: ' + error.message, 'error'); } finally { this.hideLoading(); } } async handleUpload() { if (!this.isAuthenticated) { this.showToast('Please sign in with YouTube first', 'error'); return; } const url = this.reelUrl.value.trim(); if (!url) { this.showToast('Please enter a valid Instagram Reel URL', 'error'); return; } this.hideResults(); this.showProgress(); try { const response = await fetch('/auto-upload-async', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }) }); const data = await response.json(); if (data.success) { this.currentTaskId = data.task_id; this.startStatusPolling(); } else { throw new Error(data.error || 'Upload failed'); } } catch (error) { this.showError(error.message); } } async handleDownload() { const url = this.reelUrl.value.trim(); if (!url) { this.showToast('Please enter a valid Instagram Reel URL', 'error'); return; } this.showLoading(); try { const response = await fetch('/download', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }) }); const data = await response.json(); if (data.success) { this.showToast('Download completed! Check your downloads folder.', 'success'); // Trigger file download window.location.href = `/get-video/${data.filename}`; } else { throw new Error(data.error || 'Download failed'); } } catch (error) { this.showToast('Download failed: ' + error.message, 'error'); } finally { this.hideLoading(); } } startStatusPolling() { this.statusCheckInterval = setInterval(async () => { await this.checkTaskStatus(); }, 2000); } async checkTaskStatus() { if (!this.currentTaskId) return; try { const response = await fetch(`/task-status/${this.currentTaskId}`); const data = await response.json(); if (data.success) { const task = data.task; this.updateProgress(task); if (task.status === 'completed') { this.stopStatusPolling(); this.showSuccess(task); } else if (task.status === 'failed') { this.stopStatusPolling(); this.showError(task.error || 'Upload failed'); } } } catch (error) { console.error('Status check failed:', error); } } stopStatusPolling() { if (this.statusCheckInterval) { clearInterval(this.statusCheckInterval); this.statusCheckInterval = null; } } updateProgress(task) { this.progressTitle.textContent = this.getStatusTitle(task.status); this.progressPercent.textContent = `${task.progress}%`; this.progressFill.style.width = `${task.progress}%`; this.progressMessage.textContent = task.message; // Show metadata if available if (task.metadata && task.status === 'uploading') { this.displayMetadataPreview(task.metadata); } } getStatusTitle(status) { const titles = { 'started': 'Starting...', 'downloading': 'Downloading Reel', 'generating_metadata': 'AI Analyzing Video', 'uploading': 'Uploading to YouTube', 'completed': 'Upload Complete', 'failed': 'Upload Failed' }; return titles[status] || 'Processing...'; } displayMetadataPreview(metadata) { this.previewTitle.textContent = metadata.title || '-'; this.previewDescription.textContent = metadata.description || '-'; // Display tags this.previewTags.innerHTML = ''; if (metadata.tags && metadata.tags.length > 0) { metadata.tags.slice(0, 15).forEach(tag => { const span = document.createElement('span'); span.textContent = tag; this.previewTags.appendChild(span); }); } // Display hashtags this.previewHashtags.innerHTML = ''; if (metadata.hashtags && metadata.hashtags.length > 0) { metadata.hashtags.slice(0, 20).forEach(hashtag => { const span = document.createElement('span'); span.textContent = hashtag; this.previewHashtags.appendChild(span); }); } this.metadataPreview.style.display = 'block'; } showProgress() { this.progressSection.style.display = 'block'; this.metadataPreview.style.display = 'none'; this.successResult.style.display = 'none'; this.errorResult.style.display = 'none'; } showSuccess(task) { this.progressSection.style.display = 'none'; this.successResult.style.display = 'block'; if (task.youtube_url) { this.watchBtn.href = task.youtube_url; } this.showToast('Video uploaded successfully! 🎉', 'success'); } showError(message) { this.progressSection.style.display = 'none'; this.errorResult.style.display = 'block'; this.errorMessage.textContent = message; this.showToast('Upload failed: ' + message, 'error'); } hideResults() { this.successResult.style.display = 'none'; this.errorResult.style.display = 'none'; this.metadataPreview.style.display = 'none'; } resetForm() { this.reelUrl.value = ''; this.hideResults(); this.progressSection.style.display = 'none'; this.currentTaskId = null; this.stopStatusPolling(); } showUploadSection() { if (this.authBtn) this.authBtn.style.display = 'none'; this.uploadSection.style.display = 'block'; } showAuthButton() { if (this.authBtn) this.authBtn.style.display = 'inline-flex'; this.uploadSection.style.display = 'none'; } showLoading() { this.loadingOverlay.style.display = 'flex'; } hideLoading() { this.loadingOverlay.style.display = 'none'; } showToast(message, type = 'info') { this.toast.textContent = message; this.toast.className = 'toast show'; if (type === 'success') { this.toast.style.borderLeft = '4px solid var(--success)'; } else if (type === 'error') { this.toast.style.borderLeft = '4px solid var(--error)'; } else { this.toast.style.borderLeft = '4px solid var(--secondary)'; } setTimeout(() => { this.toast.classList.remove('show'); }, 4000); } formatNumber(num) { if (num >= 1000000) { return (num / 1000000).toFixed(1) + 'M'; } else if (num >= 1000) { return (num / 1000).toFixed(1) + 'K'; } return num; } } // Initialize app when DOM is ready document.addEventListener('DOMContentLoaded', () => { new YouTubeAutomation(); });