(function () { if (window.__YAKTUBE_ENGINE_LOADED__) return; window.__YAKTUBE_ENGINE_LOADED__ = true; console.log('[YakTube] YakNet SSO, Custom Branding, A11y & On-Demand Engine Active'); // Dynamic Instance Name Helper function getInstanceName() { if ( window.PeertubeServerConfig && window.PeertubeServerConfig.instance && window.PeertubeServerConfig.instance.name ) { return window.PeertubeServerConfig.instance.name; } var metaSite = document.querySelector('meta[property="og:site_name"]'); if (metaSite && metaSite.content) return metaSite.content; var brandEl = document.querySelector('.header-left .brand-name, .instance-name, a.brand'); if (brandEl && brandEl.textContent) return brandEl.textContent.trim(); return 'YakTube'; } // 1. Screen Reader Live Announcer var liveRegion = null; function getLiveRegion() { if (!liveRegion || !document.body.contains(liveRegion)) { liveRegion = document.getElementById('yaktube-a11y-live-region'); if (!liveRegion) { liveRegion = document.createElement('div'); liveRegion.id = 'yaktube-a11y-live-region'; liveRegion.setAttribute('aria-live', 'polite'); liveRegion.setAttribute('aria-atomic', 'true'); liveRegion.className = 'yaktube-sr-only'; document.body.appendChild(liveRegion); } } return liveRegion; } function announce(text, assertive) { var region = getLiveRegion(); if (assertive) { region.setAttribute('aria-live', 'assertive'); } else { region.setAttribute('aria-live', 'polite'); } region.textContent = ''; setTimeout(function () { region.textContent = text; }, 50); } function triggerYakNetOAuth() { sessionStorage.removeItem('yaknet_manual_logout'); var callbackUrl = 'https://yaktube.yakhub.com.tr/plugins/peertube-plugin-auth-yaknet/router/auth-callback'; window.location.href = 'https://auth.yakhub.com.tr/oauth/authorize?client_id=01a03c41-f758-721e-b927-619bffde5c23&redirect_uri=' + encodeURIComponent(callbackUrl) + '&response_type=code&scope='; } // Handle direct postMessage from YakNet SSO frame window.addEventListener('message', function (ev) { if (ev.data && ev.data.type === 'YAKNET_SSO_STATUS') { if (ev.data.authenticated && ev.data.user) { var token = localStorage.getItem('access_token'); if ( !token && !window.location.search.includes('local=true') && !window.location.search.includes('externalAuthError') ) { sessionStorage.removeItem('yaknet_manual_logout'); console.log('[YakTube] Live YakNet session detected via SSO frame! Connecting...', ev.data.user); triggerYakNetOAuth(); } } else if (ev.data.authenticated === false) { var hasToken = localStorage.getItem('access_token') || localStorage.getItem('username'); if (hasToken) { console.log('[YakTube] YakNet session ended elsewhere. Clearing YakTube...'); try { localStorage.clear(); sessionStorage.clear(); } catch (e) {} window.location.reload(); } } } }); // Global listener for YakNet SSO broadcast from auth.yakhub.com.tr window.addEventListener('yaknet-auth', function (e) { if (e.detail && e.detail.authenticated && e.detail.user) { var token = localStorage.getItem('access_token'); if ( !token && !window.location.search.includes('local=true') && !window.location.search.includes('externalAuthError') ) { sessionStorage.removeItem('yaknet_manual_logout'); console.log('[YakTube] Initiating automatic single-sign-on handshake for:', e.detail.user); triggerYakNetOAuth(); } } else if (e.detail && e.detail.authenticated === false) { var hasToken = localStorage.getItem('access_token') || localStorage.getItem('username'); if (hasToken) { try { localStorage.clear(); sessionStorage.clear(); } catch (err) {} window.location.reload(); } } }); // Unified single sign-out for YakTube and YakNet function performUnifiedLogout() { try { localStorage.clear(); sessionStorage.clear(); sessionStorage.setItem('yaknet_manual_logout', 'true'); } catch (err) {} window.location.replace( 'https://auth.yakhub.com.tr/sdk/logout?redirect_uri=' + encodeURIComponent('https://yaktube.yakhub.com.tr') ); } document.addEventListener( 'click', function (e) { var el = e.target; if (el && el.closest) { var isLogout = el.closest( 'a[href*="logout"], button[title*="Log out"], button[title*="Çıkış"], .logout-button, [class*="logout"], [aria-label*="Çıkış"], [aria-label*="Log out"]' ); if (isLogout) { e.preventDefault(); e.stopPropagation(); performUnifiedLogout(); return; } var isLogin = el.closest( 'my-login-link, .login-button, a[href*="/login"]:not(#yaktube-show-admin-login), a[aria-label*="Giriş"], a[title*="Giriş"], a[title*="login"], a[aria-label*="login"], .yaknet-sso-login-btn' ); if (isLogin && !window.location.search.includes('local=true')) { e.preventDefault(); e.stopPropagation(); sessionStorage.removeItem('yaknet_manual_logout'); triggerYakNetOAuth(); return; } } }, true ); // 2. YakNet SSO Header & Login Flow function injectYakNetSSO() { // 2.1 Header: Place YakNet Account widget in top-right .buttons-container var headerRight = document.querySelector('my-header .buttons-container') || document.querySelector('.buttons-container') || document.querySelector('my-header .header-right') || document.querySelector('my-header .user-menu') || document.querySelector('my-header .action-buttons') || document.querySelector('.header-right') || document.querySelector('header .right'); var token = localStorage.getItem('access_token'); var uName = localStorage.getItem('username'); var uEmail = localStorage.getItem('email'); var displayName = localStorage.getItem('displayName') || uName || ''; var emailStr = uEmail || ''; var isAuth = !!token && !!uName; if (headerRight) { var liveWidget = headerRight.querySelector('#yaknet-header-account-widget'); if (!liveWidget) { var widgetWrap = document.createElement('div'); widgetWrap.id = 'yaknet-header-account-widget'; widgetWrap.className = 'yaknet-header-widget'; widgetWrap.style.display = 'inline-flex'; widgetWrap.style.alignItems = 'center'; widgetWrap.style.marginRight = '8px'; widgetWrap.innerHTML = '' + ''; var targetSibling = headerRight.querySelector('my-user-menu') || headerRight.querySelector('my-login-link') || headerRight.querySelector('.user-menu') || headerRight.querySelector('.settings-button') || headerRight.querySelector('my-user-notifications') || headerRight.querySelector('my-notification-bell'); if (targetSibling && targetSibling.parentElement === headerRight) { headerRight.insertBefore(widgetWrap, targetSibling); } else { headerRight.prepend(widgetWrap); } var defaultLoginLink = headerRight.querySelector('my-login-link'); if (defaultLoginLink) { defaultLoginLink.setAttribute('aria-hidden', 'true'); defaultLoginLink.setAttribute('tabindex', '-1'); defaultLoginLink.style.display = 'none'; } } else { // Sync widget attributes dynamically if auth state changed var el = liveWidget.querySelector('yaknet-account'); if (el) { if (el.shadowRoot) { var shadowLogoutBtn = el.shadowRoot.getElementById('logoutBtn') || el.shadowRoot.querySelector('.yn-logout-btn') || el.shadowRoot.querySelector('button[title*="Çıkış"], button[aria-label*="Çıkış"]'); if (shadowLogoutBtn && !shadowLogoutBtn.hasAttribute('data-yaktube-hooked')) { shadowLogoutBtn.setAttribute('data-yaktube-hooked', 'true'); shadowLogoutBtn.addEventListener( 'click', function (ev) { ev.preventDefault(); ev.stopPropagation(); performUnifiedLogout(); }, true ); } } var expectedAuth = isAuth ? 'true' : 'false'; var expectedName = isAuth ? displayName : ''; var expectedEmail = isAuth ? emailStr : ''; if (el.getAttribute('authenticated') !== expectedAuth || el.getAttribute('user-name') !== expectedName) { el.setAttribute('authenticated', expectedAuth); el.setAttribute('user-name', expectedName); el.setAttribute('user-email', expectedEmail); if (typeof el.syncStateFromAttributes === 'function') { el.syncStateFromAttributes(); el.render(); } } } } } // 2.2 Login Modal / Page var loginCard = document.querySelector('my-login') || document.querySelector('.login-page') || document.querySelector('.login-container') || document.querySelector('form.login') || document.querySelector('form[action*="login"]') || document.querySelector('my-login-modal'); if (loginCard && !document.getElementById('yaknet-login-section')) { var isLocalLogin = window.location.search.includes('local=true') || window.location.pathname.includes('/admin'); var ssoBox = document.createElement('div'); ssoBox.id = 'yaknet-login-section'; ssoBox.className = 'yaknet-login-banner'; ssoBox.innerHTML = '
YakNet ile YakTube\'a Katılın
' + '

' + 'YouTube gibi tek bir YakNet hesabıyla güvenle giriş yapabilir, kendi video ve müzik kanallarınızı oluşturabilirsiniz.' + '

' + '' + '' + 'YakNet ile Giriş Yap' + '' + '
' + 'YakNet hesabınız yok mu? YakNet Hesabı Oluştur' + '
' + '
' + 'Giriş yaparak Kullanım Koşulları\'nı ve Gizlilik Politikası\'nı kabul etmiş sayılırsınız.' + '
' + ''; var formFirstChild = loginCard.firstElementChild; if (formFirstChild) { loginCard.insertBefore(ssoBox, formFirstChild); } else { loginCard.appendChild(ssoBox); } var registerLinks = document.querySelectorAll('a[href*="/signup"], a[href*="/register"]'); registerLinks.forEach(function (l) { l.href = 'https://auth.yakhub.com.tr/register'; l.target = '_blank'; l.innerText = 'YakNet Hesabı Oluştur'; }); var formInputs = loginCard.querySelector('.form-group, .inputs, form') || loginCard.querySelector('input'); var containerToHide = formInputs ? formInputs.closest('form') || formInputs.parentElement : null; if (containerToHide && !isLocalLogin) { var subElements = Array.from(loginCard.children).filter(function (el) { return ( el.id !== 'yaknet-login-section' && !el.classList.contains('title') && !el.classList.contains('page-title') ); }); subElements.forEach(function (el) { el.style.display = 'none'; }); var showAdminBtn = document.getElementById('yaktube-show-admin-login'); if (showAdminBtn) { showAdminBtn.addEventListener('click', function (e) { e.preventDefault(); subElements.forEach(function (el) { el.style.display = ''; }); showAdminBtn.style.display = 'none'; announce('Yönetici kullanıcı adı ve şifre giriş formu açıldı.'); }); } } } } // 3. Intelligent Branding & A11y DOM Auto-Fixer function autoFixDOM() { injectYakNetSSO(); // Aggressively remove generic Framasoft Open-in-App / Play Store banners var genericAppBanners = document.querySelectorAll( 'my-open-app, my-client-app-banner, .open-app-container, .app-banner, [class*="open-app"], [class*="open-in-app"], .mobile-app-banner, my-open-in-app, .app-promotion, a[href*="play.google.com/store/apps/details?id=app.joinpeertube"], a[href*="f-droid.org"]' ); genericAppBanners.forEach(function (banner) { banner.setAttribute('aria-hidden', 'true'); banner.setAttribute('tabindex', '-1'); if (banner.parentElement) { try { banner.remove(); } catch (e) { banner.style.display = 'none'; } } }); // Replace leftover PeerTube branding in visible UI texts var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false); var node; while ((node = walker.nextNode())) { var val = node.nodeValue; if (val && (val.includes('PeerTube') || val.includes('Peertube'))) { var parentTag = node.parentElement ? node.parentElement.tagName : ''; var parentElem = node.parentElement; if ( parentElem && (parentElem.closest('#yaktube-ondemand-container') || parentElem.closest('#yaktube-live-watch-container') || parentElem.tagName === 'SCRIPT' || parentElem.tagName === 'STYLE') ) { continue; } if (true) { node.nodeValue = val .replace(/PeerTube hakkında/gi, 'YakTube Hakkında') .replace(/PeerTube'a katılın/gi, "YakTube'a Katılın") .replace(/PeerTube örneği/gi, 'YakTube Sunucusu') .replace(/PeerTube/g, 'YakTube'); } } } // Semantic Headings (H3 for video titles, H2 for sections) document .querySelectorAll('.video-miniature, my-video-miniature, .video-card, .yaktube-card, [class*="miniature"]') .forEach(function (card) { var titleElement = card.querySelector( '.video-miniature-name, .video-name, .yaktube-video-name, a[title], .title' ); if (titleElement) { if (!['H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(titleElement.tagName)) { titleElement.setAttribute('role', 'heading'); titleElement.setAttribute('aria-level', '3'); } } }); document .querySelectorAll('.section-title, .sub-header, .videos-header, .page-header, .yaktube-title-h3') .forEach(function (sec) { if (!['H1', 'H2'].includes(sec.tagName)) { sec.setAttribute('role', 'heading'); sec.setAttribute('aria-level', '2'); } }); // Make native PeerTube comment authors accessible as H3 headings for screen readers document .querySelectorAll('my-video-comment, .video-comment, my-video-comment-thread, .comment, .comment-thread') .forEach(function (commentEl) { var authorEl = commentEl.querySelector( '.display-name, .actor-name, .comment-author, a.actor-link, .video-comment-header-left a, .video-comment-header-left span' ); if ( authorEl && !['H1', 'H2', 'H3', 'H4', 'H5', 'H6'].includes(authorEl.tagName) && !authorEl.getAttribute('role') ) { authorEl.setAttribute('role', 'heading'); authorEl.setAttribute('aria-level', '3'); } }); // Auto-fix unlabeled buttons document.querySelectorAll('button:not([data-a11y-fixed])').forEach(function (btn) { btn.setAttribute('data-a11y-fixed', 'true'); var text = (btn.textContent || '').trim(); var hasAria = btn.getAttribute('aria-label') || btn.getAttribute('title'); if (!text && !hasAria) { var html = btn.innerHTML.toLowerCase(); var className = (btn.className || '').toLowerCase(); var icon = btn.querySelector('my-global-icon, i, svg, [class*="icon-"]'); var iconName = icon ? icon.className || icon.getAttribute('name') || '' : ''; if (html.includes('search') || className.includes('search') || iconName.includes('search')) { btn.setAttribute('aria-label', 'Ara'); } else if (html.includes('bell') || className.includes('notification') || iconName.includes('bell')) { btn.setAttribute('aria-label', 'Bildirimler'); } else if (html.includes('cog') || html.includes('setting') || className.includes('setting')) { btn.setAttribute('aria-label', 'Ayarlar'); } else if ( html.includes('menu') || html.includes('bars') || className.includes('burger') || className.includes('menu') ) { btn.setAttribute('aria-label', 'Ana Gezinme Menüsü'); } else if (html.includes('play') || className.includes('play')) { btn.setAttribute('aria-label', 'Oynat'); } else if (html.includes('pause') || className.includes('pause')) { btn.setAttribute('aria-label', 'Duraklat'); } else if (html.includes('volume') || html.includes('mute') || className.includes('volume')) { btn.setAttribute('aria-label', 'Ses Kontrolü'); } else if (html.includes('fullscreen') || className.includes('fullscreen')) { btn.setAttribute('aria-label', 'Tam Ekran'); } else if ( html.includes('ellipsis') || html.includes('more') || className.includes('more') || className.includes('action') ) { btn.setAttribute('aria-label', 'Diğer İşlemler ve Seçenekler'); } else if (html.includes('trash') || html.includes('delete') || className.includes('delete')) { btn.setAttribute('aria-label', 'Sil'); } else if (html.includes('edit') || className.includes('edit')) { btn.setAttribute('aria-label', 'Düzenle'); } else if (html.includes('share') || className.includes('share')) { btn.setAttribute('aria-label', 'Paylaş'); } else if (html.includes('thumb-up') || html.includes('like') || className.includes('like')) { btn.setAttribute('aria-label', 'Beğen'); } else if (html.includes('thumb-down') || html.includes('dislike') || className.includes('dislike')) { btn.setAttribute('aria-label', 'Beğenme'); } else if (html.includes('filter') || className.includes('filter')) { btn.setAttribute('aria-label', 'Filtreler'); } else if (html.includes('close') || html.includes('times') || className.includes('close')) { btn.setAttribute('aria-label', 'Kapat'); } else if (btn.closest('.vjs-control-bar')) { btn.setAttribute('aria-label', 'Oynatıcı Düğmesi'); } else { var parent = btn.parentElement; if (parent && parent.title) { btn.setAttribute('aria-label', parent.title); } else { btn.setAttribute('aria-label', 'Düğme'); } } } }); // Auto-fix unlabeled images document.querySelectorAll('img:not([data-a11y-fixed])').forEach(function (img) { img.setAttribute('data-a11y-fixed', 'true'); var alt = img.getAttribute('alt'); if (!alt || alt.trim() === '') { var card = img.closest('.video-miniature, .video-card, .yaktube-card, [class*="miniature"]'); if (card) { var titleEl = card.querySelector('.video-miniature-name, .video-name, .yaktube-video-name, h3, h4, a[title]'); var titleText = titleEl ? (titleEl.textContent || titleEl.getAttribute('title') || '').trim() : ''; if (titleText) { img.setAttribute('alt', titleText + ' video kapağı'); return; } } var avatarContainer = img.closest('.actor-avatar, .channel-avatar, .avatar, [class*="avatar"]'); if (avatarContainer) { var nameEl = avatarContainer.parentElement ? avatarContainer.parentElement.querySelector('.name, .actor-name, .channel-name') : null; var nameText = nameEl ? (nameEl.textContent || '').trim() : ''; img.setAttribute('alt', (nameText ? nameText + ' ' : '') + 'Profil Resmi'); return; } if (img.src && (img.src.includes('logo') || img.className.includes('logo'))) { img.setAttribute('alt', getInstanceName() + ' Logo'); return; } var parentLink = img.closest('a'); if (parentLink && parentLink.textContent && parentLink.textContent.trim().length > 0) { img.setAttribute('alt', ''); } else if (parentLink && parentLink.getAttribute('title')) { img.setAttribute('alt', parentLink.getAttribute('title')); } else { img.setAttribute('alt', 'Görsel'); } } }); // Auto-fix unlabeled links document.querySelectorAll('a:not([data-a11y-fixed])').forEach(function (a) { a.setAttribute('data-a11y-fixed', 'true'); var text = (a.textContent || '').trim(); var hasAria = a.getAttribute('aria-label') || a.getAttribute('title'); if (!text && !hasAria) { var img = a.querySelector('img'); if (img && img.alt) { a.setAttribute('aria-label', img.alt); } else if (a.href) { if (a.href.includes('/videos/watch/')) { a.setAttribute('aria-label', 'Videoyu İzle'); } else if (a.href.includes('/video-channels/')) { a.setAttribute('aria-label', 'Kanal Sayfasına Git'); } else if (a.href.includes('/accounts/')) { a.setAttribute('aria-label', 'Hesap Profiline Git'); } else if (a.href.includes('/videos/search')) { a.setAttribute('aria-label', 'Arama Sayfası'); } else { a.setAttribute('aria-label', 'Bağlantı'); } } } }); // Auto-fix search inputs document.querySelectorAll('input:not([data-a11y-fixed])').forEach(function (inp) { inp.setAttribute('data-a11y-fixed', 'true'); if (!inp.getAttribute('aria-label') && !inp.getAttribute('placeholder') && !(inp.labels && inp.labels.length)) { var type = (inp.getAttribute('type') || 'text').toLowerCase(); if (type === 'search') { inp.setAttribute('aria-label', getInstanceName() + ' Video Arama'); } else if (type === 'checkbox') { inp.setAttribute('aria-label', 'Seçim Kutusu'); } else if (type === 'password') { inp.setAttribute('aria-label', 'Şifre'); } } }); } setInterval(autoFixDOM, 1000); // 3. Keyboard Shortcuts Modal function toggleShortcutsModal() { var modal = document.getElementById('yaktube-shortcuts-dialog'); if (modal) { if (modal.style.display === 'none' || !modal.style.display) { modal.style.display = 'flex'; announce('Klavye kısayolları yardım penceresi açıldı. Kapatmak i�in Escape tuşuna basın.'); var closeBtn = document.getElementById('yaktube-shortcuts-close'); if (closeBtn) closeBtn.focus(); } else { modal.style.display = 'none'; announce('Yardım penceresi kapatıldı.'); } } else { modal = document.createElement('div'); modal.id = 'yaktube-shortcuts-dialog'; modal.className = 'yaktube-modal-overlay'; modal.setAttribute('role', 'dialog'); modal.setAttribute('aria-modal', 'true'); modal.setAttribute('aria-labelledby', 'yaktube-shortcuts-title'); modal.innerHTML = '
' + '

?? YakTube Klavye K�sayollar�

' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
Kısayolİşlev
Space veya KOynat / Duraklat
Sol Ok veya J5 saniye geri sar
Sağ Ok veya L5 saniye ileri sar
Yukarı / Aşağı OkSesi %5 artır / azalt
MSesi Kapat / A� (Mute)
FTam Ekran
CAltyazıları A� / Kapat
/ (Bölü)Arama kutusuna hızlı git
0 - 9Videonun %0 - %90 noktasına atla
? (Shift+/)Bu yardım kılavuzunu aç / kapat
EscPencereleri kapat
' + '' + '
'; document.body.appendChild(modal); document.getElementById('yaktube-shortcuts-close').addEventListener('click', function () { modal.style.display = 'none'; }); var closeBtn = document.getElementById('yaktube-shortcuts-close'); if (closeBtn) closeBtn.focus(); announce('Klavye kısayolları yardım penceresi açıldı. Kapatmak i�in Escape tuşuna basın.'); } } function isInputFocused() { var el = document.activeElement; if (!el) return false; var tag = el.tagName ? el.tagName.toUpperCase() : ''; return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || el.isContentEditable; } function getVideoElement() { return document.querySelector('video'); } function formatTime(seconds) { if (isNaN(seconds) || seconds < 0) return '0 saniye'; var mins = Math.floor(seconds / 60); var secs = Math.floor(seconds % 60); if (mins === 0) return secs + ' saniye'; return mins + ' dakika ' + secs + ' saniye'; } // 4. Global Keydown Listener window.addEventListener('keydown', function (e) { if (e.key === 'Escape') { var shortcutsModal = document.getElementById('yaktube-shortcuts-dialog'); if (shortcutsModal && shortcutsModal.style.display !== 'none') { shortcutsModal.style.display = 'none'; announce('Pencere kapatıldı.'); return; } var importModal = document.getElementById('yaktube-import-modal'); if (importModal && importModal.style.display !== 'none') { importModal.style.display = 'none'; announce('İndirme penceresi kapatıldı.'); return; } if (isInputFocused()) { document.activeElement.blur(); } return; } if (e.key === '?' || (e.shiftKey && e.key === '/')) { if (!isInputFocused()) { e.preventDefault(); toggleShortcutsModal(); return; } } if (e.key === '/' && !isInputFocused()) { e.preventDefault(); var searchInput = document.querySelector('input[type="search"]') || document.querySelector('input[name="search"]') || document.querySelector('.search-input') || document.querySelector('input'); if (searchInput) { searchInput.focus(); if (searchInput.select) searchInput.select(); announce('Arama kutusuna odaklanıldı.'); } return; } if (isInputFocused()) { return; } var video = getVideoElement(); if (!video) return; var key = e.key.toLowerCase(); if (key === ' ' || key === 'k') { e.preventDefault(); if (video.paused) { video.play(); announce('Video oynatılıyor.'); } else { video.pause(); announce('Video duraklatıldı.'); } } else if (key === 'j' || e.key === 'ArrowLeft') { e.preventDefault(); video.currentTime = Math.max(0, video.currentTime - 5); announce('5 saniye geri sarıldı. Şu an: ' + formatTime(video.currentTime)); } else if (key === 'l' || e.key === 'ArrowRight') { e.preventDefault(); video.currentTime = Math.min(video.duration || 99999, video.currentTime + 5); announce('5 saniye ileri sarıldı. Şu an: ' + formatTime(video.currentTime)); } else if (e.key === 'ArrowUp') { e.preventDefault(); video.volume = Math.min(1, video.volume + 0.05); announce('Ses seviyesi yüzde ' + Math.round(video.volume * 100)); } else if (e.key === 'ArrowDown') { e.preventDefault(); video.volume = Math.max(0, video.volume - 0.05); announce('Ses seviyesi yüzde ' + Math.round(video.volume * 100)); } else if (key === 'm') { e.preventDefault(); video.muted = !video.muted; announce(video.muted ? 'Ses kapatıldı (Sessiz).' : 'Ses açıldı.'); } else if (key === 'f') { e.preventDefault(); if (!document.fullscreenElement) { var container = video.closest('.video-js') || video.parentElement || video; if (container.requestFullscreen) container.requestFullscreen(); announce('Tam ekran moduna geçildi.'); } else { if (document.exitFullscreen) document.exitFullscreen(); announce('Tam ekrandan çıkıldı.'); } } else if (key === 'c') { e.preventDefault(); var tracks = video.textTracks; if (tracks && tracks.length > 0) { var isShowing = tracks[0].mode === 'showing'; tracks[0].mode = isShowing ? 'disabled' : 'showing'; announce(isShowing ? 'Altyazılar kapatıldı.' : 'Altyazılar açıldı.'); } else { announce('Bu videoda altyazı bulunmuyor.'); } } else if (key >= '0' && key <= '9' && !e.ctrlKey && !e.altKey && !e.metaKey) { e.preventDefault(); var percent = parseInt(key, 10) * 10; if (video.duration) { video.currentTime = (video.duration * percent) / 100; announce('Videonun yüzde ' + percent + ' noktasına atlandı. Şu an: ' + formatTime(video.currentTime)); } } }); // 5. On-Demand Search & Import with Duplicate Awareness var lastQuery = null; var isSearching = false; function showModal(title, desc) { var overlay = document.getElementById('yaktube-import-modal'); if (!overlay) { overlay = document.createElement('div'); overlay.id = 'yaktube-import-modal'; overlay.className = 'yaktube-modal-overlay'; overlay.setAttribute('role', 'dialog'); overlay.setAttribute('aria-modal', 'true'); overlay.setAttribute('aria-labelledby', 'yaktube-modal-title'); overlay.innerHTML = '
' + '' + '

' + (title || "YakTube'a Aktarılıyor...") + '

' + '

' + (desc || 'Seçtiğiniz video D: diskinize aktarılıyor...') + '

' + '
'; document.body.appendChild(overlay); } else { var titleElem = document.getElementById('yaktube-modal-title'); var descElem = document.getElementById('yaktube-modal-desc'); if (titleElem) titleElem.innerText = title; if (descElem) descElem.innerText = desc; overlay.style.display = 'flex'; } announce((title || '') + '. ' + (desc || ''), true); } function hideModal() { var overlay = document.getElementById('yaktube-import-modal'); if (overlay) overlay.style.display = 'none'; } function handleLiveStream(url, videoTitle, isRestoring) { var videoIdMatch = url.match(/[?&]v=([^&]+)/) || url.match(/youtu\.be\/([^?]+)/); var videoId = videoIdMatch ? videoIdMatch[1] : ''; if (!videoId) { alert('Canl\u0131 yay\u0131n ID bulunamad\u0131.'); return; } // Persist active live stream state in sessionStorage try { sessionStorage.setItem( 'yaktube_active_live', JSON.stringify({ url: url, title: videoTitle, videoId: videoId }) ); } catch (e) {} var liveContainer = document.getElementById('yaktube-live-watch-container'); if (liveContainer) liveContainer.remove(); liveContainer = document.createElement('div'); liveContainer.id = 'yaktube-live-watch-container'; liveContainer.className = 'yaktube-live-watch-section'; liveContainer.setAttribute('role', 'region'); liveContainer.setAttribute('tabindex', '-1'); liveContainer.setAttribute('aria-label', videoTitle + ' Canl\u0131 Yay\u0131n\u0131'); liveContainer.innerHTML = '
' + '
' + '
' + '\ud83d\udd34 CANLI YAYIN' + '

' + videoTitle + '

' + '
' + '' + '
' + '
' + '' + '
' + '
' + '
\ud83d\udce1 YakTube Canl\u0131 Yay\u0131n Ak\u0131\u015f\u0131 \u2022 Reklams\u0131z ve Kesintisiz
' + '
ESC tu\u015funa basarak yay\u0131n\u0131 kapatabilirsiniz.
' + '
' + '
' + '
' + '

' + '\ud83d\udcac YouTube Topluluk Yorumlar\u0131' + '

' + '' + '
' + '' + '' + '
' + '
'; var mainMount = document.querySelector('my-search, my-search-results, router-outlet, main, .main-col') || document.body; if (mainMount && mainMount.firstChild) { mainMount.insertBefore(liveContainer, mainMount.firstChild); } else if (mainMount) { mainMount.appendChild(liveContainer); } if (!isRestoring) { liveContainer.scrollIntoView({ behavior: 'smooth', block: 'start' }); setTimeout(function () { var liveTitle = document.getElementById('yaktube-live-title'); if (liveTitle) liveTitle.focus(); else liveContainer.focus(); }, 150); announce( videoTitle + ' canl\u0131 yay\u0131n\u0131 ba\u015flat\u0131ld\u0131. Canl\u0131 yay\u0131n alan\u0131na odaklan\u0131ld\u0131.', true ); } var commentNextToken = null; var commentsList = document.getElementById('yaktube-comments-list'); var toggleCommentsBtn = document.getElementById('yaktube-toggle-comments-btn'); var moreCommentsWrap = document.getElementById('yaktube-more-comments-wrap'); var moreCommentsBtn = document.getElementById('yaktube-load-more-comments-btn'); function renderCommentCard(c) { var card = document.createElement('div'); card.className = 'yaktube-comment-item'; card.setAttribute('role', 'article'); card.setAttribute('tabindex', '0'); card.setAttribute('aria-label', c.author + ' isimli kullan\u0131c\u0131n\u0131n yorumu: ' + c.content); card.style.cssText = 'background: #1e293b; padding: 12px 14px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.05); display: flex; gap: 12px; align-items: flex-start;'; var avatarLetter = (c.author || 'U').replace('@', '').charAt(0).toUpperCase(); var avatarHtml = '
' + avatarLetter + '
'; var safeContent = c.content .replace(/&/g, '&') .replace(//g, '>') .replace(/\n/g, '
'); card.innerHTML = avatarHtml + '
' + '
' + '

' + c.author + '

' + '' + c.published + '' + (c.likeCount && c.likeCount !== '0' ? '👍 ' + c.likeCount + '' : '') + '
' + '
' + safeContent + '
' + '
'; return card; } function fetchComments(token) { if (!token) { toggleCommentsBtn.disabled = true; toggleCommentsBtn.textContent = '⏳ Yorumlar Y\u00fckleniyor...'; } else { moreCommentsBtn.disabled = true; moreCommentsBtn.textContent = '⏳ Y\u00fckleniyor...'; } var fetchUrl = '/api-custom/youtube-comments?id=' + encodeURIComponent(videoId) + (token ? '&token=' + encodeURIComponent(token) : ''); fetch(fetchUrl) .then(function (r) { return r.json(); }) .then(function (res) { if (!token) { commentsList.innerHTML = ''; commentsList.style.display = 'flex'; toggleCommentsBtn.textContent = '🔄 Yorumlar\u0131 Yenile'; toggleCommentsBtn.disabled = false; } else { moreCommentsBtn.textContent = '➕ Daha Fazla Yorum G\u00f6ster (+20 Yorum)'; moreCommentsBtn.disabled = false; } var list = res.comments || []; if (list.length === 0 && !token) { commentsList.innerHTML = '
Bu videoda/yay\u0131nda hen\u00fcz yorum bulunmuyor.
'; moreCommentsWrap.style.display = 'none'; announce('Bu videoda henüz yorum bulunmuyor.'); return; } list.forEach(function (c) { commentsList.appendChild(renderCommentCard(c)); }); commentNextToken = res.nextToken; if (commentNextToken) { moreCommentsWrap.style.display = 'block'; } else { moreCommentsWrap.style.display = 'none'; } announce(list.length + ' adet YouTube yorumu yüklendi.'); }) .catch(function (err) { if (!token) { toggleCommentsBtn.disabled = false; toggleCommentsBtn.textContent = '⚠️ Tekrar Dene'; } else { moreCommentsBtn.disabled = false; moreCommentsBtn.textContent = '⚠️ Tekrar Dene'; } alert('Yorumlar yüklenirken hata oluştu: ' + err.message); }); } if (toggleCommentsBtn) { toggleCommentsBtn.addEventListener('click', function () { fetchComments(null); }); } if (moreCommentsBtn) { moreCommentsBtn.addEventListener('click', function () { if (commentNextToken) fetchComments(commentNextToken); }); } function closeLiveTheater() { try { sessionStorage.removeItem('yaktube_active_live'); } catch (e) {} if (liveContainer && liveContainer.parentNode) { var ifr = liveContainer.querySelector('iframe'); if (ifr) ifr.src = ''; liveContainer.remove(); } document.removeEventListener('keydown', handleEsc); announce('Canl\u0131 yay\u0131n izleme alan\u0131 kapat\u0131ld\u0131.'); } function handleEsc(e) { if (e.key === 'Escape' || e.keyCode === 27) { closeLiveTheater(); } } var closeBtn = document.getElementById('yaktube-close-live-theater'); if (closeBtn) closeBtn.addEventListener('click', closeLiveTheater); document.addEventListener('keydown', handleEsc); } function restoreActiveLiveStream() { try { var saved = sessionStorage.getItem('yaktube_active_live'); if (!saved) return; var data = JSON.parse(saved); if (data && data.url && data.title && !document.getElementById('yaktube-live-watch-container')) { handleLiveStream(data.url, data.title, true); } } catch (e) {} } function handleImport(url, btn, videoTitle) { btn.disabled = true; btn.innerHTML = '⏳ Aktarılıyor...'; showModal( '[1/2] 📥 Video İndiriliyor...', (videoTitle ? '"' + videoTitle + '" ' : 'Video ') + "YouTube'dan sunucunuza aktarılıyor. Lütfen bekleyin..." ); fetch('/api-custom/ondemand-import', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ targetUrl: url, title: videoTitle }) }) .then(function (res) { return res.json(); }) .then(function (data) { if (!data || !data.ok) { throw new Error(data && data.error ? data.error : 'İçe aktarma başlatılamadı.'); } var videoId = data.video.id; var videoUuid = data.video.uuid; var attempts = 0; var pollInterval = setInterval(function () { attempts++; fetch('/api-custom/status/' + videoId) .then(function (sRes) { return sRes.json(); }) .then(function (sData) { if (sData.isImporting) { showModal('[1/2] 📥 Video İndiriliyor...', 'Video sunucuya indiriliyor (' + attempts * 2 + ' sn)...'); } else if (sData.isTranscoding) { showModal('[2/2] ⚙️ Oynatıcı Hazırlanıyor...', 'Video işleniyor ve yüksek kalite akış hazırlanıyor...'); } if (sData.isPublished || (sData.hasFiles && attempts >= 8)) { clearInterval(pollInterval); showModal('🎉 Video Hazır!', 'Video kütüphanenize eklendi. Oynatıcı açılıyor...'); setTimeout(function () { window.location.href = '/videos/watch/' + (sData.uuid || videoUuid); }, 600); } else if (sData.isFailed) { clearInterval(pollInterval); hideModal(); btn.disabled = false; btn.innerHTML = "📥 YakTube'a Aktar & İzle"; alert('Video indirme sırasında bir hata oluştu.'); announce('Video indirme sırasında bir hata oluştu.', true); } }) .catch(function (e) { console.error('[YakTube] Status poll error:', e); }); if (attempts > 35) { clearInterval(pollInterval); hideModal(); btn.disabled = false; btn.innerHTML = "📥 YakTube'a Aktar & İzle"; alert( 'İndirme arka planda tamamlanıyor. Ana sayfada veya Videolarım sayfasında kısa süre içinde hazır olacaktır.' ); announce('İndirme arka planda tamamlanıyor. Ana sayfada kısa süre içinde hazır olacaktır.'); } }, 1500); }) .catch(function (err) { hideModal(); btn.disabled = false; btn.innerHTML = "📥 YakTube'a Aktar & İzle"; alert('İndirme hatası: ' + err.message); announce('İndirme hatası: ' + err.message, true); }); } function checkAndInjectSearchResults() { var url = new URL(window.location.href); var searchParam = url.searchParams.get('search'); if (!window.location.pathname.includes('/search') || !searchParam || searchParam.trim().length < 2) { var existing = document.getElementById('yaktube-ondemand-container'); if (existing) existing.remove(); lastQuery = null; return; } var query = searchParam.trim(); if (query === lastQuery) return; lastQuery = query; var container = document.getElementById('yaktube-ondemand-container'); if (!container) { container = document.createElement('div'); container.id = 'yaktube-ondemand-container'; container.className = 'yaktube-ondemand-section'; container.setAttribute('role', 'region'); container.setAttribute('aria-label', 'Canl\u0131 YouTube Arama Sonu\u00e7lar\u0131'); var mainContent = document.querySelector('my-search, my-search-results, router-outlet, main, .main-col') || document.body; if (mainContent && mainContent.firstChild) { mainContent.insertBefore(container, mainContent.firstChild); } else if (mainContent) { mainContent.appendChild(container); } } container.innerHTML = '
' + '
' + '\u26a1 Canl\u0131 YouTube & Web Arama' + '

"' + query + '" i\u00e7in YouTube Sonu\u00e7lar\u0131 Taran\u0131yor...

' + '
' + '' + '
' + '
' + '' + '
YouTube\'daki en pop\u00fcler e\u015fle\u015fmeler taran\u0131yor...
' + '
'; var shortcutsBtn = document.getElementById('yaktube-open-shortcuts'); if (shortcutsBtn) { shortcutsBtn.addEventListener('click', toggleShortcutsModal); } announce(query + ' i\u00e7in canl\u0131 YouTube aramas\u0131 ba\u015flat\u0131ld\u0131.'); isSearching = true; fetch('/api-custom/youtube-search?q=' + encodeURIComponent(query)) .then(function (res) { return res.json(); }) .then(function (data) { var rawResults = data.results || []; var uniqueResults = []; var seenIds = {}; rawResults.forEach(function (item) { if (item && item.id && !seenIds[item.id] && item.title) { seenIds[item.id] = true; uniqueResults.push(item); } }); var count = uniqueResults.length; if (count === 0) { container.innerHTML = '
' + '
' + '⚡ Canlı YouTube & Web Arama' + '

"' + query + '" için YouTube\'da Sonuç Bulunamadı

' + '
' + '
'; announce(query + " için YouTube\'da eşleşen video bulunamadı."); } else { var countText = count === 1 ? '1 Video Bulundu' : count + ' Video Bulundu'; announce(query + ' araması için ' + count + ' video listelendi.'); var visibleCount = Math.min(12, count); function render() { var visibleResults = uniqueResults.slice(0, visibleCount); var cardsHtml = ''; visibleResults.forEach(function (item) { var isLocal = !!item.isDownloaded; var isLive = !!item.isLive; var statusBadge = ''; if (isLive) { statusBadge = '🔴 CANLI YAYIN'; } else if (isLocal) { statusBadge = '✅ ' + getInstanceName() + "'da Mevcut"; } var actionButton = ''; if (isLive) { actionButton = ''; } else if (isLocal) { actionButton = '▶️ ' + getInstanceName() + "'dan İzle"; } else { actionButton = '"; } var durationText = isLive ? 'CANLI' : item.durationFormatted; var cardLabel = item.title + (isLive ? ' (Canlı Yayın)' : isLocal ? ' (' + getInstanceName() + ' kütüphanenizde mevcut)' : '') + ', ' + item.channel + ' kanalı, Süre: ' + durationText; cardsHtml += '
' + '
' + '' +
                item.title.replace(/' + '' + durationText + '' + '
' + '
' + '
' + statusBadge + '

' + item.title + '

' + '
' + item.channel + '
' + '
' + actionButton + '
' + '
'; }); var loadMoreHtml = ''; if (visibleCount < count) { loadMoreHtml = '
' + '' + '
'; } container.innerHTML = '
' + '
' + '⚡ Canlı YouTube & Web Arama' + '

"' + query + '" için ' + countText + '

' + '
' + '' + '
' + '
' + cardsHtml + '
' + loadMoreHtml; var sBtn = document.getElementById('yaktube-open-shortcuts'); if (sBtn) { sBtn.addEventListener('click', toggleShortcutsModal); } var loadMoreBtn = document.getElementById('yaktube-load-more-btn'); if (loadMoreBtn) { loadMoreBtn.addEventListener('click', function () { visibleCount = Math.min(visibleCount + 6, count); render(); announce(visibleCount + ' adet video gösteriliyor.'); }); } container.querySelectorAll('.yaktube-live-btn').forEach(function (btn) { btn.addEventListener('click', function (e) { var liveUrl = e.currentTarget.getAttribute('data-url'); var liveTitle = e.currentTarget.getAttribute('data-title'); handleLiveStream(liveUrl, liveTitle); }); }); container.querySelectorAll('.yaktube-import-btn').forEach(function (btn) { btn.addEventListener('click', function (e) { var videoUrl = e.currentTarget.getAttribute('data-url'); var videoTitle = e.currentTarget.getAttribute('data-title'); handleImport(videoUrl, e.currentTarget, videoTitle); }); }); } render(); } }) .catch(function (err) { console.error('[YakTube] Search fetch error:', err); container.innerHTML = '
' + '
' + '\u26a1 Canl\u0131 YouTube & Web Arama' + '

Arama s\u0131ras\u0131nda bir ba\u011flant\u0131 sorunu olu\u015ftu.

' + '
' + '
'; announce('Arama s\u0131ras\u0131nda bir ba\u011flant\u0131 sorunu olu\u015ftu.'); }) .finally(function () { isSearching = false; }); } // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- // -------------------------------------------------------------------------- // 8. FULL YOUTUBE SEARCH & VOICE MODAL ENGINE (v1.3.2 - Clean Generic Labels) // -------------------------------------------------------------------------- var activeVoiceRec = null; var isVoiceListening = false; function createFullSearchModal() { var existingModal = document.getElementById('yaktube-full-search-modal'); if (existingModal) return existingModal; var modal = document.createElement('div'); modal.id = 'yaktube-full-search-modal'; modal.className = 'yaktube-full-search-modal'; modal.setAttribute('role', 'dialog'); modal.setAttribute('aria-modal', 'true'); modal.setAttribute('aria-labelledby', 'yaktube-modal-search-title'); modal.style.display = 'none'; modal.innerHTML = '
' + '' + '
' + '
' + '
🎙️ Dinleniyor...
' + '
Lütfen aramak istediğiniz videoyu söyleyin...
' + '' + '
' + '
🔥 Hızlı ve Popüler Aramalar
' + '
' + '' + '' + '' + '' + '' + '' + '' + '' + '
' + '' + '
' + '
'; document.body.appendChild(modal); var inp = modal.querySelector('#yaktube-modal-input'); var clearBtn = modal.querySelector('#yaktube-modal-clear-btn'); var backBtn = modal.querySelector('#yaktube-modal-back-btn'); var submitBtn = modal.querySelector('#yaktube-modal-submit-btn'); var voiceBtn = modal.querySelector('#yaktube-modal-voice-btn'); var micPulseBtn = modal.querySelector('#yaktube-modal-mic-pulse'); // Input typing & clear inp.addEventListener('input', function () { if (inp.value && inp.value.trim().length > 0) { clearBtn.style.display = 'inline-flex'; } else { clearBtn.style.display = 'none'; } }); clearBtn.addEventListener('click', function () { inp.value = ''; clearBtn.style.display = 'none'; inp.focus(); announce('Arama metni temizlendi.'); }); // Close on Back button or Escape backBtn.addEventListener('click', function () { closeFullSearchModal(); }); modal.addEventListener('click', function (e) { if (e.target === modal) { closeFullSearchModal(); } }); // Search execute function executeSearch(query) { var q = (query || inp.value || '').trim(); if (q) { announce('"' + q + '" için arama yapılıyor...', true); closeFullSearchModal(); window.location.href = '/search/videos?search=' + encodeURIComponent(q); } } submitBtn.addEventListener('click', function () { executeSearch(); }); inp.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); executeSearch(); } else if (e.key === 'Escape') { e.preventDefault(); closeFullSearchModal(); } }); // Chips click modal.querySelectorAll('.yaktube-modal-chip').forEach(function (chip) { chip.addEventListener('click', function () { var query = chip.getAttribute('data-query'); executeSearch(query); }); }); // Voice button click voiceBtn.addEventListener('click', function () { toggleVoiceInsideModal(); }); micPulseBtn.addEventListener('click', function () { toggleVoiceInsideModal(); }); return modal; } function openFullSearchModal(initialQuery, autoStartVoice) { var modal = createFullSearchModal(); modal.style.display = 'flex'; var inp = modal.querySelector('#yaktube-modal-input'); var clearBtn = modal.querySelector('#yaktube-modal-clear-btn'); if (inp) { inp.value = initialQuery || ''; if (inp.value) clearBtn.style.display = 'inline-flex'; setTimeout(function () { inp.focus(); inp.select(); }, 50); } announce('Arama penceresi açıldı. Arayacağınız konuyu yazın veya sesle arayın.', true); if (autoStartVoice) { startVoiceInsideModal(); } } function closeFullSearchModal() { var modal = document.getElementById('yaktube-full-search-modal'); if (modal) modal.style.display = 'none'; stopVoiceInsideModal(); announce('Arama penceresi kapatıldı.'); } function toggleVoiceInsideModal() { if (isVoiceListening) { stopVoiceInsideModal(); } else { startVoiceInsideModal(); } } function startVoiceInsideModal() { var SpeechRec = window.SpeechRecognition || window.webkitSpeechRecognition; var modal = document.getElementById('yaktube-full-search-modal'); if (!modal) return; var voiceSec = modal.querySelector('#yaktube-modal-voice-section'); var statusText = modal.querySelector('#yaktube-modal-voice-status'); var transcriptEl = modal.querySelector('#yaktube-modal-voice-transcript'); var inp = modal.querySelector('#yaktube-modal-input'); if (voiceSec) voiceSec.style.display = 'block'; if (statusText) statusText.textContent = '🎙️ Dinleniyor...'; if (transcriptEl) transcriptEl.textContent = 'Lütfen aramak istediğiniz videoyu söyleyin...'; if (!SpeechRec) { if (statusText) statusText.textContent = 'Tarayıcıda Ses Tanıma Desteklenmiyor'; if (transcriptEl) transcriptEl.textContent = 'Lütfen Google Chrome veya Microsoft Edge kullanın.'; announce('Tarayıcınız ses tanımayı desteklemiyor. Lütfen Chrome veya Edge kullanın.'); return; } announce('Sesli arama dinleniyor, lütfen aramak istediğiniz videoyu söyleyin.', true); try { activeVoiceRec = new SpeechRec(); activeVoiceRec.lang = navigator.language || 'tr-TR'; activeVoiceRec.continuous = false; activeVoiceRec.interimResults = true; activeVoiceRec.maxAlternatives = 1; isVoiceListening = true; activeVoiceRec.onresult = function (ev) { if (ev.results && ev.results[0]) { var transcript = ev.results[0][0].transcript.trim(); if (transcriptEl) transcriptEl.textContent = '"' + transcript + '"'; if (inp) inp.value = transcript; if (ev.results[0].isFinal) { announce('"' + transcript + '" için arama başlatılıyor...', true); setTimeout(function () { closeFullSearchModal(); window.location.href = '/search/videos?search=' + encodeURIComponent(transcript); }, 500); } } }; activeVoiceRec.onerror = function (ev) { if (statusText) statusText.textContent = 'Ses Algılanamadı'; if (transcriptEl) transcriptEl.textContent = 'Tekrar denemek için mikrofona dokunun.'; stopVoiceInsideModal(); }; activeVoiceRec.onend = function () { stopVoiceInsideModal(); }; activeVoiceRec.start(); } catch (err) { stopVoiceInsideModal(); } } function stopVoiceInsideModal() { isVoiceListening = false; if (activeVoiceRec) { try { activeVoiceRec.stop(); } catch (e) {} activeVoiceRec = null; } var modal = document.getElementById('yaktube-full-search-modal'); if (modal) { var voiceSec = modal.querySelector('#yaktube-modal-voice-section'); if (voiceSec) voiceSec.style.display = 'none'; } } function transformToYouTubeSearchBar(searchInp, searchContainer) { if (searchContainer.hasAttribute('data-yaktube-trigger-styled')) return; searchContainer.setAttribute('data-yaktube-trigger-styled', 'true'); // Hide original native inputs searchInp.style.display = 'none'; searchInp.setAttribute('tabindex', '-1'); searchInp.setAttribute('aria-hidden', 'true'); var nativeInputs = searchContainer.querySelectorAll('input, button'); nativeInputs.forEach(function (el) { if (!el.classList.contains('yaktube-search-trigger-pill') && !el.classList.contains('yaktube-voice-search-btn')) { el.style.display = 'none'; el.setAttribute('tabindex', '-1'); el.setAttribute('aria-hidden', 'true'); } }); // 1. Create Outer Wrapper var outerWrap = searchContainer.querySelector('.yaktube-search-outer-wrap'); if (!outerWrap) { outerWrap = document.createElement('div'); outerWrap.className = 'yaktube-search-outer-wrap'; searchContainer.appendChild(outerWrap); } // 2. Create Single Sleek Pill Trigger var triggerPill = outerWrap.querySelector('.yaktube-search-trigger-pill'); if (!triggerPill) { triggerPill = document.createElement('button'); triggerPill.type = 'button'; triggerPill.className = 'yaktube-search-trigger-pill'; triggerPill.setAttribute('aria-label', 'Ara'); triggerPill.setAttribute('title', 'Arama Yap (/)'); triggerPill.innerHTML = '🔍' + 'Ara...' + '/'; outerWrap.appendChild(triggerPill); triggerPill.addEventListener('click', function (e) { e.preventDefault(); openFullSearchModal('', false); }); } // 3. YouTube Circular Voice Button (🎙️) Beside the Pill Box var voiceBtn = outerWrap.querySelector('.yaktube-voice-search-btn'); if (!voiceBtn) { voiceBtn = document.createElement('button'); voiceBtn.type = 'button'; voiceBtn.className = 'yaktube-voice-search-btn'; voiceBtn.setAttribute('aria-label', 'Sesle Ara (Shift+V)'); voiceBtn.setAttribute('title', 'Sesle Ara (Shift+V)'); voiceBtn.innerHTML = '🎙️'; outerWrap.appendChild(voiceBtn); voiceBtn.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); openFullSearchModal('', true); }); } } function injectSearchEnhancements() { var searchContainers = document.querySelectorAll( 'my-search-typeahead, .search-container, .header-search, my-header .search' ); searchContainers.forEach(function (container) { var searchInp = container.querySelector('input[type="search"], input[type="text"], input'); if (searchInp) { transformToYouTubeSearchBar(searchInp, container); } }); // Clean up repetitive card titles and strange relative date buttons document.querySelectorAll('my-video-miniature, .video-miniature').forEach(function (card) { var thumbLink = card.querySelector('a.thumbnail-link, .video-thumbnail a, a.thumbnail'); if (thumbLink && !thumbLink.hasAttribute('aria-hidden')) { thumbLink.setAttribute('aria-hidden', 'true'); thumbLink.setAttribute('tabindex', '-1'); } var dateBtn = card.querySelector('.video-date-line button, .video-date button, button[title*="tarih"]'); if (dateBtn && !dateBtn.hasAttribute('data-cleaned')) { dateBtn.setAttribute('data-cleaned', 'true'); var dateText = dateBtn.textContent.trim(); dateBtn.setAttribute('aria-label', dateText); } }); } // Global Keyboard Shortcuts document.addEventListener('keydown', function (e) { var activeTag = document.activeElement ? document.activeElement.tagName.toLowerCase() : ''; var isInputActive = activeTag === 'input' || activeTag === 'textarea'; // Shift + V: Open YouTube Voice Search Modal if (e.shiftKey && (e.key === 'V' || e.key === 'v') && !isInputActive) { e.preventDefault(); openFullSearchModal('', true); } // Escape: Close Full Search Modal else if (e.key === 'Escape' || e.keyCode === 27) { var modal = document.getElementById('yaktube-full-search-modal'); if (modal && modal.style.display === 'flex') { e.preventDefault(); closeFullSearchModal(); } } // / or S: Open Search Modal else if ((e.key === '/' || e.key === 's' || e.key === 'S') && !isInputActive && !e.ctrlKey && !e.metaKey) { e.preventDefault(); openFullSearchModal('', false); } }); // 7.5. Reactive Hybrid Related Videos Engine (Watch Page) var currentActiveVideoKey = null; var isFetchingRelated = false; var relatedShowAll = false; var cachedRelatedResults = []; var cachedRelatedQuery = ''; function renderRelatedCards(container, results, query) { var count = results ? results.length : 0; if (count === 0) { container.style.display = 'none'; return; } container.style.display = 'block'; var limit = relatedShowAll ? count : Math.min(count, 12); var displayed = results.slice(0, limit); var cardsHtml = ''; displayed.forEach(function (item) { var isLocal = !!item.isDownloaded; var statusBadge = isLocal ? '✅ ' + getInstanceName() + "'da İzle" : '⚡ Canlı Keşif'; var actionButton = isLocal ? '▶️ Hemen İzle' : ''; var cardLabel = item.title + (isLocal ? ' (' + getInstanceName() + ' kütüphanenizde mevcut)' : ' (Canlı YouTube keşfi)') + ', ' + item.channel + ' kanalı, Süre: ' + item.durationFormatted; cardsHtml += '
' + '
' + '' +
        item.title.replace(/' + '' + item.durationFormatted + '' + '
' + '
' + '
' + statusBadge + '

' + item.title + '

' + '
' + item.channel + '
' + '
' + actionButton + '
' + '
'; }); var loadMoreBtn = ''; if (count > 12) { if (!relatedShowAll) { loadMoreBtn = ''; } else { loadMoreBtn = ''; } } var headerCountText = count > 12 && !relatedShowAll ? displayed.length + ' / ' + count : count; container.innerHTML = '
' + '
' + '🔥 ' + getInstanceName() + ' Akıllı Önerilenler' + '

"' + query + '" ile İlgili Öneriler (' + headerCountText + ')

' + '
' + '' + '
' + '
' + cardsHtml + '
' + loadMoreBtn; var toggleBtn = document.getElementById('yaktube-related-toggle-more'); if (toggleBtn) { toggleBtn.addEventListener('click', function () { relatedShowAll = !relatedShowAll; renderRelatedCards(container, cachedRelatedResults, cachedRelatedQuery); announce( relatedShowAll ? 'Tüm ' + cachedRelatedResults.length + ' önerilen video açıldı.' : 'Öneriler daraltıldı.' ); }); } var refreshBtn = document.getElementById('yaktube-related-refresh-btn'); if (refreshBtn) { refreshBtn.addEventListener('click', function () { currentActiveVideoKey = null; checkAndInjectRelatedVideos(true); }); } container.querySelectorAll('.yaktube-related-import').forEach(function (btn) { btn.addEventListener('click', function (e) { var videoUrl = e.currentTarget.getAttribute('data-url'); var videoTitle = e.currentTarget.getAttribute('data-title'); handleImport(videoUrl, e.currentTarget, videoTitle); }); }); container.querySelectorAll('.yaktube-related-play-link').forEach(function (link) { link.addEventListener('click', function () { currentActiveVideoKey = null; }); }); } function checkAndInjectRelatedVideos(forceReload) { var isWatchPage = window.location.pathname.includes('/videos/watch/') || window.location.pathname.startsWith('/w/'); if (!isWatchPage) { var existing = document.getElementById('yaktube-hybrid-related-container'); if (existing) existing.remove(); currentActiveVideoKey = null; return; } var titleElem = document.querySelector('h1.video-name') || document.querySelector('.video-info h1') || document.querySelector('.video-watch-heading h1') || document.querySelector('h1') || document.querySelector('.title'); var rawTitle = titleElem ? (titleElem.textContent || titleElem.innerText || '').trim() : ''; if (!rawTitle || rawTitle.length < 3) { return; } var currentPath = window.location.pathname; var thisVideoKey = currentPath + '::' + rawTitle.toLowerCase(); if ( !forceReload && thisVideoKey === currentActiveVideoKey && document.getElementById('yaktube-hybrid-related-container') ) { return; } if (isFetchingRelated) return; isFetchingRelated = true; var cleanTopic = rawTitle .replace(/YakTube/gi, '') .replace(/[\(\[].*?[\)\]]/g, '') .replace(/official video|video klip|klip|hd|4k|lyric video|audio|remix|feat\.?|ft\.?/gi, '') .trim(); var words = cleanTopic.split(/\s+/).filter(function (w) { return w.length > 2; }); var searchQuery = words.slice(0, 3).join(' ') || cleanTopic; var container = document.getElementById('yaktube-hybrid-related-container'); if (!container) { container = document.createElement('div'); container.id = 'yaktube-hybrid-related-container'; container.className = 'yaktube-ondemand-section'; container.setAttribute('role', 'region'); container.setAttribute('aria-label', 'YakTube Akıllı Önerilen Videolar'); } var recVideosMount = document.querySelector('my-recommended-videos, .other-videos'); var commentsMount = document.querySelector('#yaktube-watch-comments-panel, my-video-comments, .video-comments'); var videoBottomMount = document.querySelector('.video-bottom, .video-info'); var watchMount = document.querySelector('my-video-watch') || document.querySelector('main'); if (recVideosMount) { if (container.parentElement !== recVideosMount) { recVideosMount.prepend(container); } } else if (commentsMount) { if (container.previousElementSibling !== commentsMount) { commentsMount.insertAdjacentElement('afterend', container); } } else if (videoBottomMount) { if (container.previousElementSibling !== videoBottomMount) { videoBottomMount.insertAdjacentElement('afterend', container); } } else if (watchMount) { if (container.parentElement !== watchMount) { watchMount.appendChild(container); } } else { isFetchingRelated = false; return; } container.innerHTML = '
' + '
' + '🔥 ' + getInstanceName() + ' Akıllı Önerilenler' + '

"' + searchQuery + '" için Öneriler Taranıyor...

' + '
' + '
' + '
' + '' + '
YouTube ve Fediverse\'deki en alakalı videolar getiriliyor...
' + '
'; announce(searchQuery + ' için ilgili video önerileri yükleniyor.'); fetch('/api-custom/youtube-search?q=' + encodeURIComponent(searchQuery)) .then(function (res) { return res.json(); }) .then(function (data) { var rawResults = data.results || []; var normalizedCurrentTitle = rawTitle.toLowerCase().trim(); var uniqueResults = []; var seenIds = {}; rawResults.forEach(function (item) { if (item && item.id && !seenIds[item.id] && item.title) { var itemTitle = item.title.toLowerCase().trim(); if (itemTitle !== normalizedCurrentTitle && !itemTitle.includes(normalizedCurrentTitle)) { seenIds[item.id] = true; uniqueResults.push(item); } } }); cachedRelatedResults = uniqueResults; cachedRelatedQuery = searchQuery; currentActiveVideoKey = thisVideoKey; relatedShowAll = false; renderRelatedCards(container, uniqueResults, searchQuery); var initialCount = Math.min(uniqueResults.length, 12); if (uniqueResults.length === 0) { announce(searchQuery + ' için ilgili öneri bulunamadı.'); } else { announce( searchQuery + ' için ' + initialCount + ' öneri gösteriliyor' + (uniqueResults.length > 12 ? ', toplam ' + uniqueResults.length + ' video bulundu' : '') + '.' ); } }) .catch(function (err) { console.error('[YakTube] Related fetch error:', err); container.style.display = 'none'; }) .finally(function () { isFetchingRelated = false; }); } var lastWatchUuid = null; function checkAndInjectWatchPageComments() { var pathname = window.location.pathname; var match = pathname.match(/\/videos\/watch\/([a-zA-Z0-9_-]+)/) || pathname.match(/\/w\/([a-zA-Z0-9_-]+)/); if (!match) { lastWatchUuid = null; var existingWatchComments = document.getElementById('yaktube-watch-comments-panel'); if (existingWatchComments) existingWatchComments.remove(); return; } var uuid = match[1]; var existingPanel = document.getElementById('yaktube-watch-comments-panel'); if (uuid === lastWatchUuid && existingPanel) { var nativeComments = document.querySelector('my-video-comments, .video-comments, .comments-block, #comments'); if (nativeComments && existingPanel.previousElementSibling !== nativeComments) { nativeComments.insertAdjacentElement('afterend', existingPanel); } return; } lastWatchUuid = uuid; var mountTarget = document.querySelector('my-video-comments, .video-comments, my-video-watch, .video-bottom') || document.body; if (!mountTarget) return; fetch('/api-custom/video-origin/' + encodeURIComponent(uuid)) .then(function (r) { return r.json(); }) .then(function (originData) { if (!originData || !originData.ok || !originData.hasYouTubeOrigin || !originData.youtubeId) { var old = document.getElementById('yaktube-watch-comments-panel'); if (old) old.remove(); return; } var youtubeId = originData.youtubeId; var existing = document.getElementById('yaktube-watch-comments-panel'); if (existing) existing.remove(); var panel = document.createElement('div'); panel.id = 'yaktube-watch-comments-panel'; panel.className = 'yaktube-comments-panel'; panel.setAttribute('role', 'region'); panel.setAttribute('aria-label', 'YouTube Orijinal Yorumlar\u0131'); panel.style.cssText = 'margin-top: 24px; margin-bottom: 24px; background: #0f172a; border-radius: 12px; padding: 20px; border: 1px solid rgba(255,255,255,0.08); box-shadow: 0 8px 24px rgba(0,0,0,0.4);'; panel.innerHTML = '
' + '
' + '

' + "\ud83d\udcac YouTube'daki Orijinal Yorumlar" + '

' + '
Bu video YouTube\'dan aktar\u0131lm\u0131\u015ft\u0131r. Orijinal topluluk yorumlar\u0131n\u0131 a\u015fa\u011f\u0131dan anl\u0131k olarak y\u00fckleyebilirsiniz.
' + '
' + '' + '
' + '' + ''; var nativeComments = document.querySelector('my-video-comments, .video-comments, .comments-block, #comments'); if (nativeComments && nativeComments.parentElement) { nativeComments.insertAdjacentElement('afterend', panel); } else { var watchCol = document.querySelector('.video-info, .video-bottom, my-video-watch, .main-col'); if (watchCol) { watchCol.appendChild(panel); } else { mountTarget.appendChild(panel); } } var watchCommentToken = null; var listContainer = document.getElementById('yaktube-watch-comments-list'); var toggleBtn = document.getElementById('yaktube-watch-toggle-comments-btn'); var moreWrap = document.getElementById('yaktube-watch-more-comments-wrap'); var moreBtn = document.getElementById('yaktube-watch-more-comments-btn'); function fetchWatchComments(token) { if (!token) { toggleBtn.disabled = true; toggleBtn.textContent = '⏳ Yorumlar Y\u00fckleniyor...'; } else { moreBtn.disabled = true; moreBtn.textContent = '⏳ Y\u00fckleniyor...'; } var fUrl = '/api-custom/youtube-comments?id=' + encodeURIComponent(youtubeId) + (token ? '&token=' + encodeURIComponent(token) : ''); fetch(fUrl) .then(function (r) { return r.json(); }) .then(function (res) { if (!token) { listContainer.innerHTML = ''; listContainer.style.display = 'flex'; toggleBtn.textContent = '🔄 Yorumlar\u0131 Yenile'; toggleBtn.disabled = false; } else { moreBtn.textContent = '➕ Daha Fazla Yorum G\u00f6ster (+20 Yorum)'; moreBtn.disabled = false; } var cList = res.comments || []; if (cList.length === 0 && !token) { listContainer.innerHTML = '
Bu videoda hen\u00fcz YouTube yorumu bulunmuyor.
'; moreWrap.style.display = 'none'; announce('Bu videoda henüz YouTube yorumu bulunmuyor.'); return; } cList.forEach(function (c) { var card = document.createElement('div'); card.className = 'yaktube-comment-item'; card.setAttribute('role', 'article'); card.setAttribute('tabindex', '0'); card.setAttribute('aria-label', c.author + ' isimli kullan\u0131c\u0131n\u0131n yorumu: ' + c.content); card.style.cssText = 'background: #1e293b; padding: 14px 16px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.05); display: flex; gap: 12px; align-items: flex-start;'; var avatarLetter = (c.author || 'U').replace('@', '').charAt(0).toUpperCase(); var avatarHtml = '
' + avatarLetter + '
'; var safeContent = c.content .replace(/&/g, '&') .replace(//g, '>') .replace(/\n/g, '
'); card.innerHTML = avatarHtml + '
' + '
' + '

' + c.author + '

' + '' + c.published + '' + (c.likeCount && c.likeCount !== '0' ? '👍 ' + c.likeCount + '' : '') + '
' + '
' + safeContent + '
' + '
'; listContainer.appendChild(card); }); watchCommentToken = res.nextToken; if (watchCommentToken) { moreWrap.style.display = 'block'; } else { moreWrap.style.display = 'none'; } announce(cList.length + ' adet YouTube yorumu y\u00fcklendi.'); }) .catch(function (err) { if (!token) { toggleBtn.disabled = false; toggleBtn.textContent = '⚠️ Tekrar Dene'; } else { moreBtn.disabled = false; moreBtn.textContent = '⚠️ Tekrar Dene'; } alert('Yorumlar y\u00fcklenirken hata olu\u015ftu: ' + err.message); }); } if (toggleBtn) { toggleBtn.addEventListener('click', function () { fetchWatchComments(null); }); } if (moreBtn) { moreBtn.addEventListener('click', function () { if (watchCommentToken) fetchWatchComments(watchCommentToken); }); } }) .catch(function () {}); } // 6. PWA Mobile App Install Engine & Standalone Detection var isInsideYakTubeApp = navigator.userAgent.indexOf('YakTubeMobileApp') !== -1 || window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true; var isMobileDevice = /Android|iPhone|iPad|iPod|Mobile|Tablet/i.test(navigator.userAgent); var deferredInstallPrompt = null; window.addEventListener('beforeinstallprompt', function (e) { if (isInsideYakTubeApp) return; e.preventDefault(); deferredInstallPrompt = e; injectPWAInstallButton(); }); function injectPWAInstallButton() { if (isInsideYakTubeApp) { var existBtn = document.getElementById('yaktube-pwa-install-btn'); if (existBtn) { existBtn.setAttribute('aria-hidden', 'true'); existBtn.remove(); } return; } if (!deferredInstallPrompt || document.getElementById('yaktube-pwa-install-btn')) return; var headerButtons = document.querySelector('my-header .buttons-container') || document.querySelector('.buttons-container') || document.querySelector('my-header'); if (!headerButtons) return; var installBtn = document.createElement('button'); installBtn.id = 'yaktube-pwa-install-btn'; installBtn.className = 'yaktube-pwa-btn'; installBtn.setAttribute('type', 'button'); installBtn.setAttribute('aria-label', 'YakTube Mobil Uygulamasını Yükle'); installBtn.style.cssText = 'background: linear-gradient(135deg, #ff8f37, #f97316); color: #000; border: none; padding: 6px 14px; border-radius: 20px; font-weight: 700; font-size: 12px; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; margin-right: 8px; box-shadow: 0 4px 12px rgba(255,143,55,0.4);'; installBtn.innerHTML = '📲 Uygulamayı Yükle'; installBtn.addEventListener('click', function () { if (deferredInstallPrompt) { deferredInstallPrompt.prompt(); deferredInstallPrompt.userChoice.then(function (choiceResult) { if (choiceResult.outcome === 'accepted') { installBtn.remove(); announce('YakTube uygulaması cihazınıza yükleniyor.'); } deferredInstallPrompt = null; }); } }); headerButtons.prepend(installBtn); } function injectApkDownloadButton() { // 1. If inside APK: NEVER show APK button if (isInsideYakTubeApp) { var existBtnApp = document.getElementById('yaktube-apk-download-btn'); if (existBtnApp) { existBtnApp.setAttribute('aria-hidden', 'true'); existBtnApp.remove(); } return; } // 2. If on Desktop: NEVER show in top header (keep header clean) if (!isMobileDevice) { var existBtnDesktop = document.getElementById('yaktube-apk-download-btn'); if (existBtnDesktop) { existBtnDesktop.setAttribute('aria-hidden', 'true'); existBtnDesktop.remove(); } return; } // 3. If on Mobile Web: Show in header if (document.getElementById('yaktube-apk-download-btn')) return; var headerButtons = document.querySelector('my-header .buttons-container') || document.querySelector('my-header .header-right') || document.querySelector('.buttons-container') || document.querySelector('my-header'); if (!headerButtons) return; var apkBtn = document.createElement('a'); apkBtn.id = 'yaktube-apk-download-btn'; apkBtn.href = '/yaktube.apk'; apkBtn.className = 'yaktube-apk-btn'; apkBtn.setAttribute('download', 'YakTube_Mobile_v1.0.apk'); apkBtn.setAttribute('aria-label', 'YakTube Android APK İndir (4.5 MB)'); apkBtn.style.cssText = 'background: #1e293b; color: #ff8f37; border: 1px solid #ff8f37; padding: 6px 12px; border-radius: 20px; font-weight: 700; font-size: 11px; text-decoration: none; display: inline-flex; align-items: center; gap: 4px; margin-right: 6px; transition: all 0.2s; box-shadow: 0 4px 12px rgba(0,0,0,0.3);'; apkBtn.innerHTML = '🤖 APK İndir (4.5 MB)'; headerButtons.prepend(apkBtn); } function injectSidebarApkLink() { // If inside APK: NEVER show in sidebar if (isInsideYakTubeApp) { var existLink = document.getElementById('yaktube-sidebar-apk-link'); if (existLink) { existLink.setAttribute('aria-hidden', 'true'); existLink.remove(); } return; } if (document.getElementById('yaktube-sidebar-apk-link')) return; var menuContainer = document.querySelector('my-menu, .menu, .main-menu, .nav-list'); if (!menuContainer) return; var sidebarLink = document.createElement('a'); sidebarLink.id = 'yaktube-sidebar-apk-link'; sidebarLink.href = '/yaktube.apk'; sidebarLink.className = 'menu-link item-link'; sidebarLink.setAttribute('download', 'YakTube_Mobile_v1.0.apk'); sidebarLink.setAttribute('aria-label', 'YakTube Android Uygulamasını İndir (4.5 MB)'); sidebarLink.style.cssText = 'display: flex; align-items: center; gap: 10px; padding: 10px 16px; margin: 8px 12px; background: rgba(255, 143, 55, 0.1); color: #ff8f37; border: 1px solid rgba(255, 143, 55, 0.3); border-radius: 8px; font-weight: 700; font-size: 13px; text-decoration: none;'; sidebarLink.innerHTML = '🤖 YakTube Android APK (4.5 MB)'; menuContainer.appendChild(sidebarLink); } setInterval(function () { checkAndInjectSearchResults(); injectSearchEnhancements(); checkAndInjectRelatedVideos(false); restoreActiveLiveStream(); checkAndInjectWatchPageComments(); injectPWAInstallButton(); injectApkDownloadButton(); injectSidebarApkLink(); }, 800); restoreActiveLiveStream(); window.addEventListener('popstate', function () { currentActiveVideoKey = null; checkAndInjectSearchResults(); checkAndInjectRelatedVideos(true); checkAndInjectWatchPageComments(); }); checkAndInjectWatchPageComments(); })();