From dac313733025bb3127283faa7fb5b42765855b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Yak=C4=B1=C5=9Ft=C4=B1r?= Date: Mon, 31 Aug 2026 18:48:19 +0300 Subject: [PATCH] fix(compatibility): safe matchMedia check and wrap client in try-catch to prevent incompatible browser screen v1.4.10 --- client.js | 5952 +++++++++++++++++++++++++------------------------- package.json | 2 +- 2 files changed, 2987 insertions(+), 2967 deletions(-) diff --git a/client.js b/client.js index 8cc3988..40a4d9b 100644 --- a/client.js +++ b/client.js @@ -1,67 +1,95 @@ -(function () { - if (window.__YAKTUBE_ENGINE_LOADED__) return; - window.__YAKTUBE_ENGINE_LOADED__ = true; - console.log('[YakTube] YakNet SSO, Custom Branding, A11y & On-Demand Engine Active'); +try { + (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); + // 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'; } - return liveRegion; - } - function announce(text, assertive) { - var region = getLiveRegion(); - if (assertive) { - region.setAttribute('aria-live', 'assertive'); - } else { - region.setAttribute('aria-live', 'polite'); + // 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; } - 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='; - } + 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); + } - // 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) { + 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 && @@ -69,901 +97,986 @@ !window.location.search.includes('externalAuthError') ) { sessionStorage.removeItem('yaknet_manual_logout'); - console.log('[YakTube] Live YakNet session detected via SSO frame! Connecting...', ev.data.user); + console.log('[YakTube] Initiating automatic single-sign-on handshake for:', e.detail.user); triggerYakNetOAuth(); } - } else if (ev.data.authenticated === false) { + } else if (e.detail && e.detail.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) {} + } catch (err) {} 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.3 Dedicated Local Admin Login Modal (Alt+A) - function openLocalAdminLoginModal() { - var existingModal = document.getElementById('yaktube-admin-login-modal'); - if (existingModal) { - existingModal.style.display = 'flex'; - var uInput = existingModal.querySelector('#yaktube-admin-username'); - if (uInput) uInput.focus(); - return; - } - - var modal = document.createElement('div'); - modal.id = 'yaktube-admin-login-modal'; - modal.className = 'yaktube-modal-overlay'; - modal.setAttribute('role', 'dialog'); - modal.setAttribute('aria-modal', 'true'); - modal.setAttribute('aria-labelledby', 'yaktube-admin-modal-title'); - - modal.innerHTML = - '
' + - '
' + - '

⚙️ Sunucu Yöneticisi Girişi

' + - '' + - '
' + - '

Yerel yönetici (admin) kullanıcı adı ve şifrenizle giriş yaparak PeerTube yönetim paneline erişebilirsiniz.

' + - '
' + - '
' + - '' + - '' + - '
' + - '
' + - '' + - '' + - '
' + - '' + - '' + - '
' + - '
'; - - document.body.appendChild(modal); - - modal.querySelector('#yaktube-admin-modal-close').addEventListener('click', function () { - modal.style.display = 'none'; - }); - modal.addEventListener('click', function (e) { - if (e.target === modal) modal.style.display = 'none'; }); - var form = modal.querySelector('#yaktube-admin-direct-form'); - var errEl = modal.querySelector('#yaktube-admin-login-error'); - var uInput = modal.querySelector('#yaktube-admin-username'); - var pInput = modal.querySelector('#yaktube-admin-password'); - var sBtn = modal.querySelector('#yaktube-admin-login-submit'); - - uInput.focus(); - - form.addEventListener('submit', async function (ev) { - ev.preventDefault(); - errEl.style.display = 'none'; - sBtn.disabled = true; - sBtn.innerText = 'Giriş Yapılıyor...'; - + // Unified single sign-out for YakTube and YakNet + function performUnifiedLogout() { try { - var clientRes = await fetch('/api/v1/oauth-clients/local'); - var clientData = await clientRes.json(); - - var bodyParams = new URLSearchParams({ - client_id: clientData.client_id, - client_secret: clientData.client_secret, - grant_type: 'password', - username: uInput.value.trim(), - password: pInput.value - }); - - var tokenRes = await fetch('/api/v1/users/token', { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: bodyParams.toString() - }); - - var tokenData = await tokenRes.json(); - if (!tokenRes.ok || !tokenData.access_token) { - throw new Error(tokenData.error_description || tokenData.error || 'Kullanıcı adı veya şifre hatalı.'); - } - - // Store tokens - localStorage.setItem('access_token', tokenData.access_token); - localStorage.setItem('refresh_token', tokenData.refresh_token); - localStorage.setItem('token_type', tokenData.token_type || 'Bearer'); - localStorage.setItem('username', uInput.value.trim()); - - // Get user details - var meRes = await fetch('/api/v1/users/me', { - headers: { Authorization: 'Bearer ' + tokenData.access_token } - }); - if (meRes.ok) { - var meData = await meRes.json(); - localStorage.setItem('email', meData.email || ''); - localStorage.setItem('displayName', meData.displayName || meData.username || ''); - } - - announce('Yönetici girişi başarılı! Yönetim paneline yönlendiriliyorsunuz.', true); - window.location.href = '/admin'; - } catch (err) { - errEl.innerText = err.message; - errEl.style.display = 'block'; - sBtn.disabled = false; - sBtn.innerText = 'Yönetici Olarak Giriş Yap'; - } - }); - } - - // 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 defaultLoginLink = headerRight.querySelector('my-login-link, a[href*="/login"], .login-button'); - var liveWidget = headerRight.querySelector('#yaknet-header-account-widget'); - - if (isAuth) { - // Logged In: Show YakNet Profile Menu, Hide default login link - if (defaultLoginLink) { - defaultLoginLink.style.display = 'none'; - defaultLoginLink.setAttribute('aria-hidden', 'true'); - } - - 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('.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); - } - } else { - 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 - ); - } - } - - if (el.getAttribute('authenticated') !== 'true' || el.getAttribute('user-name') !== displayName) { - el.setAttribute('authenticated', 'true'); - el.setAttribute('user-name', displayName); - el.setAttribute('user-email', emailStr); - if (typeof el.syncStateFromAttributes === 'function') { - el.syncStateFromAttributes(); - el.render(); - } - } - } - } - } else { - // Not Logged In: Remove YakNet widget to eliminate duplicate button, keep PeerTube native login button clean - if (liveWidget) { - liveWidget.remove(); - } - if (defaultLoginLink) { - defaultLoginLink.style.display = ''; - defaultLoginLink.removeAttribute('aria-hidden'); - - var loginAnchor = - defaultLoginLink.tagName.toLowerCase() === 'a' ? defaultLoginLink : defaultLoginLink.querySelector('a'); - if (loginAnchor) { - loginAnchor.setAttribute('aria-label', 'YakNet ile Giriş Yap'); - loginAnchor.setAttribute('title', 'YakNet Hesabınız ile Giriş Yapın'); - var spanText = loginAnchor.querySelector('span'); - if (spanText) { - spanText.textContent = 'YakNet ile Giriş Yap'; - } else { - loginAnchor.textContent = 'YakNet ile Giriş Yap'; - } - } - } - } + 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') + ); } - // 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') + 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"]' ); - }); - - subElements.forEach(function (el) { - el.style.display = 'none'; - }); - - var showAdminBtn = document.getElementById('yaktube-show-admin-login'); - if (showAdminBtn) { - showAdminBtn.addEventListener('click', function (e) { + if (isLogout) { e.preventDefault(); - openLocalAdminLoginModal(); - }); - } - } - } - } - - // 3. Intelligent Branding & A11y DOM Auto-Fixer - function autoFixDOM() { - injectAccessiblePlayerBar(); - - // Check if on 403 / unauthorized page and provide admin login button - var notAuthorizedEl = document.querySelector('my-not-found, .not-found, .forbidden, my-forbidden, .error-page'); - if (notAuthorizedEl && !document.getElementById('yaktube-403-admin-btn')) { - var btn403 = document.createElement('div'); - btn403.id = 'yaktube-403-admin-btn'; - btn403.style.margin = '20px auto'; - btn403.style.textAlign = 'center'; - btn403.innerHTML = - ''; - btn403.querySelector('button').addEventListener('click', openLocalAdminLoginModal); - notAuthorizedEl.appendChild(btn403); - } - - injectYakNetSSO(); - - // Replace all login buttons labels and titles - document - .querySelectorAll( - 'my-login-link, a[href*="/login"], a[title*="Giriş sayfasına git"], [aria-label*="Giriş sayfasına git"]' - ) - .forEach(function (el) { - if (el.getAttribute('title') && el.getAttribute('title').includes('Giriş sayfasına git')) { - el.setAttribute('title', 'YakNet ile Giriş Yap'); - } - if (el.getAttribute('aria-label') && el.getAttribute('aria-label').includes('Giriş sayfasına git')) { - el.setAttribute('aria-label', 'YakNet ile Giriş Yap'); - } - var anchor = el.tagName.toLowerCase() === 'a' ? el : el.querySelector('a'); - if (anchor) { - anchor.setAttribute('title', 'YakNet ile Giriş Yap'); - anchor.setAttribute('aria-label', 'YakNet ile Giriş Yap'); - var spans = anchor.querySelectorAll('span'); - spans.forEach(function (s) { - if ( - s.textContent.includes('Giriş sayfasına git') || - s.textContent.trim() === 'Giriş' || - s.textContent.trim() === 'Giriş yap' - ) { - s.textContent = 'YakNet ile Giriş Yap'; - } - }); - } - }); - - // 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') - .replace(/Giriş sayfasına git/gi, 'YakNet ile Giriş Yap'); - } - } - } - - // 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'); + e.stopPropagation(); + performUnifiedLogout(); + return; } - } - }); - - 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ğı'); + 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 + ); - 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'); - } + // 2.3 Dedicated Local Admin Login Modal (Alt+A) + function openLocalAdminLoginModal() { + var existingModal = document.getElementById('yaktube-admin-login-modal'); + if (existingModal) { + existingModal.style.display = 'flex'; + var uInput = existingModal.querySelector('#yaktube-admin-username'); + if (uInput) uInput.focus(); + return; } - }); - // 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. Screen-Reader & Turkish Q Optimized Shortcuts & Player Bar (v1.4.1) - 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'; + var modal = document.createElement('div'); + modal.id = 'yaktube-admin-login-modal'; modal.className = 'yaktube-modal-overlay'; modal.setAttribute('role', 'dialog'); modal.setAttribute('aria-modal', 'true'); - modal.setAttribute('aria-labelledby', 'yaktube-shortcuts-title'); + modal.setAttribute('aria-labelledby', 'yaktube-admin-modal-title'); + modal.innerHTML = - '
' + - '
' + - '

⌨️ Klavye Kısayolları (Ekran Okuyucu Uyumlu)

' + - '' + + '
' + + '
' + + '

⚙️ Sunucu Yöneticisi Girişi

' + + '' + '
' + - '
💡 Ekran Okuyucu Notu: Sanal imleç (Browse mode) açıkken kısayolları Alt tuşuyla birlikte kullanabilir veya video oynatıcı butonlarından faydalanabilirsiniz.
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
Kısayol (Evrensel / Tek Tuş)İşlev
Alt + K veya SpaceOynat / Duraklat
Alt + J veya J10 saniye geri sar
Alt + L veya L10 saniye ileri sar
Alt + Yukarı / AşağıSesi %5 artır / azalt
Alt + M veya MSesi Kapat / Aç (Mute)
Alt + T veya TSinema / Tiyatro Modu
Alt + I veya IHızlı Video Bilgisi & İstatistikler
Alt + C veya CAltyazıları Aç / Kapat
Alt + . / Alt + ,Oynatma hızını artır / azalt
Alt + S veya / (Shift+7)Arama penceresini aç
Alt + V veya Shift + VSesli aramayı başlat
Alt + H veya F1 veya ?Bu kılavuzu aç / kapat
' + + '

Yerel yönetici (admin) kullanıcı adı ve şifrenizle giriş yaparak PeerTube yönetim paneline erişebilirsiniz.

' + + '
' + + '
' + + '' + + '' + + '
' + + '
' + + '' + + '' + + '
' + + '' + + '' + + '
' + '
'; + document.body.appendChild(modal); - var closeBtn = modal.querySelector('#yaktube-shortcuts-close'); - if (closeBtn) { - closeBtn.addEventListener('click', function () { - modal.style.display = 'none'; - }); - } + modal.querySelector('#yaktube-admin-modal-close').addEventListener('click', function () { + modal.style.display = 'none'; + }); modal.addEventListener('click', function (e) { if (e.target === modal) modal.style.display = 'none'; }); - announce('Klavye kısayolları yardım penceresi açıldı. Kapatmak için Escape tuşuna basın.'); - } - } + var form = modal.querySelector('#yaktube-admin-direct-form'); + var errEl = modal.querySelector('#yaktube-admin-login-error'); + var uInput = modal.querySelector('#yaktube-admin-username'); + var pInput = modal.querySelector('#yaktube-admin-password'); + var sBtn = modal.querySelector('#yaktube-admin-login-submit'); - 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; - } + uInput.focus(); - function getVideoElement() { - return document.querySelector('video'); - } + form.addEventListener('submit', async function (ev) { + ev.preventDefault(); + errEl.style.display = 'none'; + sBtn.disabled = true; + sBtn.innerText = 'Giriş Yapılıyor...'; - 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'; - } + try { + var clientRes = await fetch('/api/v1/oauth-clients/local'); + var clientData = await clientRes.json(); - function showQuickVideoInfo() { - var video = getVideoElement(); - if (!video) { - announce('Şu anda aktif oynatılan bir video bulunmuyor.'); - return; - } + var bodyParams = new URLSearchParams({ + client_id: clientData.client_id, + client_secret: clientData.client_secret, + grant_type: 'password', + username: uInput.value.trim(), + password: pInput.value + }); - var titleEl = document.querySelector('h1.video-title, .video-name, my-video-watch h1, .title'); - var title = titleEl ? titleEl.textContent.trim() : 'Video'; - var curTime = formatTime(video.currentTime); - var totalTime = formatTime(video.duration || 0); - var remaining = formatTime((video.duration || 0) - video.currentTime); - var speed = (video.playbackRate || 1) + 'x'; - var volume = Math.round((video.volume || 1) * 100) + '%'; - var quality = video.videoHeight ? video.videoHeight + 'p' : 'Otomatik'; + var tokenRes = await fetch('/api/v1/users/token', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: bodyParams.toString() + }); - // Visual Toast - var existingToast = document.querySelector('.yaktube-video-info-toast'); - if (existingToast) existingToast.remove(); + var tokenData = await tokenRes.json(); + if (!tokenRes.ok || !tokenData.access_token) { + throw new Error(tokenData.error_description || tokenData.error || 'Kullanıcı adı veya şifre hatalı.'); + } - var toast = document.createElement('div'); - toast.className = 'yaktube-video-info-toast'; - toast.innerHTML = - '
📊 Video Bilgisi & Durum
' + - '
Başlık: ' + - (title.length > 28 ? title.substring(0, 28) + '...' : title) + - '
' + - '
Konum / Süre: ' + - curTime + - ' / ' + - totalTime + - '
' + - '
Kalan Süre: ' + - remaining + - '
' + - '
Çözünürlük: ' + - quality + - '
' + - '
Hız & Ses: ' + - speed + - ' | ' + - volume + - '
'; + // Store tokens + localStorage.setItem('access_token', tokenData.access_token); + localStorage.setItem('refresh_token', tokenData.refresh_token); + localStorage.setItem('token_type', tokenData.token_type || 'Bearer'); + localStorage.setItem('username', uInput.value.trim()); - document.body.appendChild(toast); - setTimeout(function () { - if (toast.parentElement) toast.remove(); - }, 4000); + // Get user details + var meRes = await fetch('/api/v1/users/me', { + headers: { Authorization: 'Bearer ' + tokenData.access_token } + }); + if (meRes.ok) { + var meData = await meRes.json(); + localStorage.setItem('email', meData.email || ''); + localStorage.setItem('displayName', meData.displayName || meData.username || ''); + } - announce( - 'Video Bilgisi: ' + - title + - '. Konum: ' + - curTime + - ', Toplam Süre: ' + - totalTime + - ', Kalan: ' + - remaining + - ', Oynatma Hızı: ' + - speed + - ', Ses: yüzde ' + - Math.round(video.volume * 100) + - ', Çözünürlük: ' + - quality, - true - ); - } - - var speedSteps = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]; - - function adjustPlaybackSpeed(increase) { - var video = getVideoElement(); - if (!video) return; - var curSpeed = video.playbackRate || 1; - if (increase) { - var nextIdx = speedSteps.findIndex(function (s) { - return s > curSpeed; + announce('Yönetici girişi başarılı! Yönetim paneline yönlendiriliyorsunuz.', true); + window.location.href = '/admin'; + } catch (err) { + errEl.innerText = err.message; + errEl.style.display = 'block'; + sBtn.disabled = false; + sBtn.innerText = 'Yönetici Olarak Giriş Yap'; + } }); - video.playbackRate = nextIdx !== -1 ? speedSteps[nextIdx] : 2; - announce('Oynatma hızı ' + video.playbackRate + ' katına çıkarıldı.'); - } else { - var prevSteps = speedSteps.filter(function (s) { - return s < curSpeed; - }); - video.playbackRate = prevSteps.length > 0 ? prevSteps[prevSteps.length - 1] : 0.25; - announce('Oynatma hızı ' + video.playbackRate + ' katına düşürüldü.'); - } - } - - // 3.1. Injects Accessible Player Action Bar under the Watch Video - function injectAccessiblePlayerBar() { - if (!window.location.pathname.includes('/videos/watch/')) return; - var video = getVideoElement(); - if (!video) return; - - var targetContainer = document.querySelector( - 'my-video-watch, .video-watch, .watch-container, #yaktube-ondemand-container' - ); - if (!targetContainer) return; - - var existingBar = document.getElementById('yaktube-player-action-bar'); - if (existingBar) return; - - var bar = document.createElement('div'); - bar.id = 'yaktube-player-action-bar'; - bar.className = 'yaktube-player-action-bar'; - bar.setAttribute('role', 'toolbar'); - bar.setAttribute('aria-label', 'Erişilebilir Video Kontrolleri'); - - bar.innerHTML = - '⚡ Oynatıcı Kontrolleri:' + - '' + - '' + - '' + - '' + - '' + - '' + - ''; - - var playerWrap = - document.querySelector('.player-container, .video-player-container, .player-wrapper, my-video-watch .player') || - targetContainer; - if (playerWrap && playerWrap.nextSibling) { - playerWrap.parentNode.insertBefore(bar, playerWrap.nextSibling); - } else { - targetContainer.prepend(bar); } - // Attach button actions - bar.querySelector('#yaktube-bar-rewind-btn').addEventListener('click', function () { - if (video) { - video.currentTime = Math.max(0, video.currentTime - 10); - announce('10 saniye geri sarıldı. Konum: ' + formatTime(video.currentTime)); + // 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 defaultLoginLink = headerRight.querySelector('my-login-link, a[href*="/login"], .login-button'); + var liveWidget = headerRight.querySelector('#yaknet-header-account-widget'); + + if (isAuth) { + // Logged In: Show YakNet Profile Menu, Hide default login link + if (defaultLoginLink) { + defaultLoginLink.style.display = 'none'; + defaultLoginLink.setAttribute('aria-hidden', 'true'); + } + + 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('.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); + } + } else { + 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 + ); + } + } + + if (el.getAttribute('authenticated') !== 'true' || el.getAttribute('user-name') !== displayName) { + el.setAttribute('authenticated', 'true'); + el.setAttribute('user-name', displayName); + el.setAttribute('user-email', emailStr); + if (typeof el.syncStateFromAttributes === 'function') { + el.syncStateFromAttributes(); + el.render(); + } + } + } + } + } else { + // Not Logged In: Remove YakNet widget to eliminate duplicate button, keep PeerTube native login button clean + if (liveWidget) { + liveWidget.remove(); + } + if (defaultLoginLink) { + defaultLoginLink.style.display = ''; + defaultLoginLink.removeAttribute('aria-hidden'); + + var loginAnchor = + defaultLoginLink.tagName.toLowerCase() === 'a' ? defaultLoginLink : defaultLoginLink.querySelector('a'); + if (loginAnchor) { + loginAnchor.setAttribute('aria-label', 'YakNet ile Giriş Yap'); + loginAnchor.setAttribute('title', 'YakNet Hesabınız ile Giriş Yapın'); + var spanText = loginAnchor.querySelector('span'); + if (spanText) { + spanText.textContent = 'YakNet ile Giriş Yap'; + } else { + loginAnchor.textContent = 'YakNet ile Giriş Yap'; + } + } + } + } } - }); - bar.querySelector('#yaktube-bar-play-btn').addEventListener('click', function () { - if (video) { + // 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 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(); + openLocalAdminLoginModal(); + }); + } + } + } + } + + // 3. Intelligent Branding & A11y DOM Auto-Fixer + function autoFixDOM() { + injectAccessiblePlayerBar(); + + // Check if on 403 / unauthorized page and provide admin login button + var notAuthorizedEl = document.querySelector('my-not-found, .not-found, .forbidden, my-forbidden, .error-page'); + if (notAuthorizedEl && !document.getElementById('yaktube-403-admin-btn')) { + var btn403 = document.createElement('div'); + btn403.id = 'yaktube-403-admin-btn'; + btn403.style.margin = '20px auto'; + btn403.style.textAlign = 'center'; + btn403.innerHTML = + ''; + btn403.querySelector('button').addEventListener('click', openLocalAdminLoginModal); + notAuthorizedEl.appendChild(btn403); + } + + injectYakNetSSO(); + + // Replace all login buttons labels and titles + document + .querySelectorAll( + 'my-login-link, a[href*="/login"], a[title*="Giriş sayfasına git"], [aria-label*="Giriş sayfasına git"]' + ) + .forEach(function (el) { + if (el.getAttribute('title') && el.getAttribute('title').includes('Giriş sayfasına git')) { + el.setAttribute('title', 'YakNet ile Giriş Yap'); + } + if (el.getAttribute('aria-label') && el.getAttribute('aria-label').includes('Giriş sayfasına git')) { + el.setAttribute('aria-label', 'YakNet ile Giriş Yap'); + } + var anchor = el.tagName.toLowerCase() === 'a' ? el : el.querySelector('a'); + if (anchor) { + anchor.setAttribute('title', 'YakNet ile Giriş Yap'); + anchor.setAttribute('aria-label', 'YakNet ile Giriş Yap'); + var spans = anchor.querySelectorAll('span'); + spans.forEach(function (s) { + if ( + s.textContent.includes('Giriş sayfasına git') || + s.textContent.trim() === 'Giriş' || + s.textContent.trim() === 'Giriş yap' + ) { + s.textContent = 'YakNet ile Giriş Yap'; + } + }); + } + }); + + // 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') + .replace(/Giriş sayfasına git/gi, 'YakNet ile Giriş Yap'); + } + } + } + + // 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. Screen-Reader & Turkish Q Optimized Shortcuts & Player Bar (v1.4.1) + 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 = + '
' + + '
' + + '

⌨️ Klavye Kısayolları (Ekran Okuyucu Uyumlu)

' + + '' + + '
' + + '
💡 Ekran Okuyucu Notu: Sanal imleç (Browse mode) açıkken kısayolları Alt tuşuyla birlikte kullanabilir veya video oynatıcı butonlarından faydalanabilirsiniz.
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
Kısayol (Evrensel / Tek Tuş)İşlev
Alt + K veya SpaceOynat / Duraklat
Alt + J veya J10 saniye geri sar
Alt + L veya L10 saniye ileri sar
Alt + Yukarı / AşağıSesi %5 artır / azalt
Alt + M veya MSesi Kapat / Aç (Mute)
Alt + T veya TSinema / Tiyatro Modu
Alt + I veya IHızlı Video Bilgisi & İstatistikler
Alt + C veya CAltyazıları Aç / Kapat
Alt + . / Alt + ,Oynatma hızını artır / azalt
Alt + S veya / (Shift+7)Arama penceresini aç
Alt + V veya Shift + VSesli aramayı başlat
Alt + H veya F1 veya ?Bu kılavuzu aç / kapat
' + + '
'; + document.body.appendChild(modal); + + var closeBtn = modal.querySelector('#yaktube-shortcuts-close'); + if (closeBtn) { + closeBtn.addEventListener('click', function () { + modal.style.display = 'none'; + }); + } + modal.addEventListener('click', function (e) { + if (e.target === modal) modal.style.display = 'none'; + }); + + 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'; + } + + function showQuickVideoInfo() { + var video = getVideoElement(); + if (!video) { + announce('Şu anda aktif oynatılan bir video bulunmuyor.'); + return; + } + + var titleEl = document.querySelector('h1.video-title, .video-name, my-video-watch h1, .title'); + var title = titleEl ? titleEl.textContent.trim() : 'Video'; + var curTime = formatTime(video.currentTime); + var totalTime = formatTime(video.duration || 0); + var remaining = formatTime((video.duration || 0) - video.currentTime); + var speed = (video.playbackRate || 1) + 'x'; + var volume = Math.round((video.volume || 1) * 100) + '%'; + var quality = video.videoHeight ? video.videoHeight + 'p' : 'Otomatik'; + + // Visual Toast + var existingToast = document.querySelector('.yaktube-video-info-toast'); + if (existingToast) existingToast.remove(); + + var toast = document.createElement('div'); + toast.className = 'yaktube-video-info-toast'; + toast.innerHTML = + '
📊 Video Bilgisi & Durum
' + + '
Başlık: ' + + (title.length > 28 ? title.substring(0, 28) + '...' : title) + + '
' + + '
Konum / Süre: ' + + curTime + + ' / ' + + totalTime + + '
' + + '
Kalan Süre: ' + + remaining + + '
' + + '
Çözünürlük: ' + + quality + + '
' + + '
Hız & Ses: ' + + speed + + ' | ' + + volume + + '
'; + + document.body.appendChild(toast); + setTimeout(function () { + if (toast.parentElement) toast.remove(); + }, 4000); + + announce( + 'Video Bilgisi: ' + + title + + '. Konum: ' + + curTime + + ', Toplam Süre: ' + + totalTime + + ', Kalan: ' + + remaining + + ', Oynatma Hızı: ' + + speed + + ', Ses: yüzde ' + + Math.round(video.volume * 100) + + ', Çözünürlük: ' + + quality, + true + ); + } + + var speedSteps = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2]; + + function adjustPlaybackSpeed(increase) { + var video = getVideoElement(); + if (!video) return; + var curSpeed = video.playbackRate || 1; + if (increase) { + var nextIdx = speedSteps.findIndex(function (s) { + return s > curSpeed; + }); + video.playbackRate = nextIdx !== -1 ? speedSteps[nextIdx] : 2; + announce('Oynatma hızı ' + video.playbackRate + ' katına çıkarıldı.'); + } else { + var prevSteps = speedSteps.filter(function (s) { + return s < curSpeed; + }); + video.playbackRate = prevSteps.length > 0 ? prevSteps[prevSteps.length - 1] : 0.25; + announce('Oynatma hızı ' + video.playbackRate + ' katına düşürüldü.'); + } + } + + // 3.1. Injects Accessible Player Action Bar under the Watch Video + function injectAccessiblePlayerBar() { + if (!window.location.pathname.includes('/videos/watch/')) return; + var video = getVideoElement(); + if (!video) return; + + var targetContainer = document.querySelector( + 'my-video-watch, .video-watch, .watch-container, #yaktube-ondemand-container' + ); + if (!targetContainer) return; + + var existingBar = document.getElementById('yaktube-player-action-bar'); + if (existingBar) return; + + var bar = document.createElement('div'); + bar.id = 'yaktube-player-action-bar'; + bar.className = 'yaktube-player-action-bar'; + bar.setAttribute('role', 'toolbar'); + bar.setAttribute('aria-label', 'Erişilebilir Video Kontrolleri'); + + bar.innerHTML = + '⚡ Oynatıcı Kontrolleri:' + + '' + + '' + + '' + + '' + + '' + + '' + + ''; + + var playerWrap = + document.querySelector('.player-container, .video-player-container, .player-wrapper, my-video-watch .player') || + targetContainer; + if (playerWrap && playerWrap.nextSibling) { + playerWrap.parentNode.insertBefore(bar, playerWrap.nextSibling); + } else { + targetContainer.prepend(bar); + } + + // Attach button actions + bar.querySelector('#yaktube-bar-rewind-btn').addEventListener('click', function () { + if (video) { + video.currentTime = Math.max(0, video.currentTime - 10); + announce('10 saniye geri sarıldı. Konum: ' + formatTime(video.currentTime)); + } + }); + + bar.querySelector('#yaktube-bar-play-btn').addEventListener('click', function () { + if (video) { + if (video.paused) { + video.play(); + announce('Video oynatılıyor.'); + } else { + video.pause(); + announce('Video duraklatıldı.'); + } + } + }); + + bar.querySelector('#yaktube-bar-forward-btn').addEventListener('click', function () { + if (video) { + video.currentTime = Math.min(video.duration || 99999, video.currentTime + 10); + announce('10 saniye ileri sarıldı. Konum: ' + formatTime(video.currentTime)); + } + }); + + bar.querySelector('#yaktube-bar-speed-btn').addEventListener('click', function () { + adjustPlaybackSpeed(true); + }); + + bar.querySelector('#yaktube-bar-theater-btn').addEventListener('click', function () { + document.body.classList.toggle('yaktube-theater-mode'); + var isT = document.body.classList.contains('yaktube-theater-mode'); + announce(isT ? 'Sinema modu açıldı.' : 'Sinema modundan çıkıldı.'); + }); + + bar.querySelector('#yaktube-bar-info-btn').addEventListener('click', function () { + showQuickVideoInfo(); + }); + + bar.querySelector('#yaktube-bar-help-btn').addEventListener('click', function () { + toggleShortcutsModal(); + }); + } + + // 4. Global Keydown Listener (Screen-Reader & Turkish Q Optimized) + 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; + } + + // Admin Login: Alt+A + if (e.altKey && (e.key === 'a' || e.key === 'A') && !isInputFocused()) { + e.preventDefault(); + openLocalAdminLoginModal(); + return; + } + + // Help Dialog: Alt+H, F1 or '?' key + if ((e.altKey && (e.key === 'h' || e.key === 'H')) || e.key === 'F1' || e.key === '?') { + if (!isInputFocused()) { + e.preventDefault(); + toggleShortcutsModal(); + return; + } + } + + // Search Open: Alt+S, Ctrl+K or '/' key (Turkish Q Shift+7) + if ( + ((e.altKey && (e.key === 's' || e.key === 'S')) || + (e.ctrlKey && (e.key === 'k' || e.key === 'K')) || + e.key === '/') && + !isInputFocused() + ) { + e.preventDefault(); + openFullSearchModal('', false); + return; + } + + // Voice Search Open: Alt+V or Shift+V + if ( + ((e.altKey && (e.key === 'v' || e.key === 'V')) || (e.shiftKey && (e.key === 'v' || e.key === 'V'))) && + !isInputFocused() + ) { + e.preventDefault(); + openFullSearchModal('', true); + return; + } + + if (isInputFocused()) { + return; + } + + var video = getVideoElement(); + if (!video) return; + + var key = e.key.toLowerCase(); + var alt = e.altKey; + + // Play / Pause (Alt+K, Space or K) + if (key === ' ' || key === 'k' || (alt && key === 'k')) { + e.preventDefault(); if (video.paused) { video.play(); announce('Video oynatılıyor.'); @@ -972,2157 +1085,2064 @@ announce('Video duraklatıldı.'); } } - }); - - bar.querySelector('#yaktube-bar-forward-btn').addEventListener('click', function () { - if (video) { + // Seek 10s Back / Forward (Alt+J / Alt+L or J / L) + else if (key === 'j' || (alt && key === 'j')) { + e.preventDefault(); + video.currentTime = Math.max(0, video.currentTime - 10); + announce('10 saniye geri sarıldı. Konum: ' + formatTime(video.currentTime)); + } else if (key === 'l' || (alt && key === 'l')) { + e.preventDefault(); video.currentTime = Math.min(video.duration || 99999, video.currentTime + 10); announce('10 saniye ileri sarıldı. Konum: ' + formatTime(video.currentTime)); } - }); - - bar.querySelector('#yaktube-bar-speed-btn').addEventListener('click', function () { - adjustPlaybackSpeed(true); - }); - - bar.querySelector('#yaktube-bar-theater-btn').addEventListener('click', function () { - document.body.classList.toggle('yaktube-theater-mode'); - var isT = document.body.classList.contains('yaktube-theater-mode'); - announce(isT ? 'Sinema modu açıldı.' : 'Sinema modundan çıkıldı.'); - }); - - bar.querySelector('#yaktube-bar-info-btn').addEventListener('click', function () { - showQuickVideoInfo(); - }); - - bar.querySelector('#yaktube-bar-help-btn').addEventListener('click', function () { - toggleShortcutsModal(); - }); - } - - // 4. Global Keydown Listener (Screen-Reader & Turkish Q Optimized) - 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; - } - - // Admin Login: Alt+A - if (e.altKey && (e.key === 'a' || e.key === 'A') && !isInputFocused()) { - e.preventDefault(); - openLocalAdminLoginModal(); - return; - } - - // Help Dialog: Alt+H, F1 or '?' key - if ((e.altKey && (e.key === 'h' || e.key === 'H')) || e.key === 'F1' || e.key === '?') { - if (!isInputFocused()) { + // Seek 5s Back / Forward (ArrowLeft / ArrowRight or Alt+Left/Right) + else if (e.key === 'ArrowLeft' || (alt && e.key === 'ArrowLeft')) { e.preventDefault(); - toggleShortcutsModal(); + video.currentTime = Math.max(0, video.currentTime - 5); + announce('5 saniye geri sarıldı. Konum: ' + formatTime(video.currentTime)); + } else if (e.key === 'ArrowRight' || (alt && e.key === 'ArrowRight')) { + e.preventDefault(); + video.currentTime = Math.min(video.duration || 99999, video.currentTime + 5); + announce('5 saniye ileri sarıldı. Konum: ' + formatTime(video.currentTime)); + } + // Volume Up / Down (ArrowUp / ArrowDown or Alt+Up/Down) + else if (e.key === 'ArrowUp' || (alt && e.key === 'ArrowUp')) { + e.preventDefault(); + video.volume = Math.min(1, (video.volume || 1) + 0.05); + announce('Ses seviyesi yüzde ' + Math.round(video.volume * 100)); + } else if (e.key === 'ArrowDown' || (alt && e.key === 'ArrowDown')) { + e.preventDefault(); + video.volume = Math.max(0, (video.volume || 1) - 0.05); + announce('Ses seviyesi yüzde ' + Math.round(video.volume * 100)); + } + // Mute (Alt+M or M) + else if (key === 'm' || (alt && key === 'm')) { + e.preventDefault(); + video.muted = !video.muted; + announce(video.muted ? 'Ses kapatıldı (Sessiz).' : 'Ses açıldı.'); + } + // Fullscreen (Alt+F or F) + else if (key === 'f' || (alt && 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ı.'); + } + } + // Theater Mode (Alt+T or T) + else if (key === 't' || (alt && key === 't')) { + e.preventDefault(); + document.body.classList.toggle('yaktube-theater-mode'); + var isTheater = document.body.classList.contains('yaktube-theater-mode'); + announce(isTheater ? 'Sinema modu açıldı.' : 'Sinema modundan çıkıldı.'); + } + // Quick Video Info (Alt+I or I) + else if (key === 'i' || (alt && key === 'i')) { + e.preventDefault(); + showQuickVideoInfo(); + } + // Subtitles (Alt+C or C) + else if (key === 'c' || (alt && 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.'); + } + } + // Speed Controls (Alt+. / Alt+, or > / <) + else if ((alt && (e.key === '.' || e.key === '>')) || e.key === '>') { + e.preventDefault(); + adjustPlaybackSpeed(true); + } else if ((alt && (e.key === ',' || e.key === '<')) || e.key === '<') { + e.preventDefault(); + adjustPlaybackSpeed(false); + } + // Percentage Jumps (0 - 9) + 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ı. Konum: ' + 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; } - } - // Search Open: Alt+S, Ctrl+K or '/' key (Turkish Q Shift+7) - if ( - ((e.altKey && (e.key === 's' || e.key === 'S')) || - (e.ctrlKey && (e.key === 'k' || e.key === 'K')) || - e.key === '/') && - !isInputFocused() - ) { - e.preventDefault(); - openFullSearchModal('', false); - return; - } - - // Voice Search Open: Alt+V or Shift+V - if ( - ((e.altKey && (e.key === 'v' || e.key === 'V')) || (e.shiftKey && (e.key === 'v' || e.key === 'V'))) && - !isInputFocused() - ) { - e.preventDefault(); - openFullSearchModal('', true); - return; - } - - if (isInputFocused()) { - return; - } - - var video = getVideoElement(); - if (!video) return; - - var key = e.key.toLowerCase(); - var alt = e.altKey; - - // Play / Pause (Alt+K, Space or K) - if (key === ' ' || key === 'k' || (alt && key === 'k')) { - e.preventDefault(); - if (video.paused) { - video.play(); - announce('Video oynatılıyor.'); - } else { - video.pause(); - announce('Video duraklatıldı.'); - } - } - // Seek 10s Back / Forward (Alt+J / Alt+L or J / L) - else if (key === 'j' || (alt && key === 'j')) { - e.preventDefault(); - video.currentTime = Math.max(0, video.currentTime - 10); - announce('10 saniye geri sarıldı. Konum: ' + formatTime(video.currentTime)); - } else if (key === 'l' || (alt && key === 'l')) { - e.preventDefault(); - video.currentTime = Math.min(video.duration || 99999, video.currentTime + 10); - announce('10 saniye ileri sarıldı. Konum: ' + formatTime(video.currentTime)); - } - // Seek 5s Back / Forward (ArrowLeft / ArrowRight or Alt+Left/Right) - else if (e.key === 'ArrowLeft' || (alt && e.key === 'ArrowLeft')) { - e.preventDefault(); - video.currentTime = Math.max(0, video.currentTime - 5); - announce('5 saniye geri sarıldı. Konum: ' + formatTime(video.currentTime)); - } else if (e.key === 'ArrowRight' || (alt && e.key === 'ArrowRight')) { - e.preventDefault(); - video.currentTime = Math.min(video.duration || 99999, video.currentTime + 5); - announce('5 saniye ileri sarıldı. Konum: ' + formatTime(video.currentTime)); - } - // Volume Up / Down (ArrowUp / ArrowDown or Alt+Up/Down) - else if (e.key === 'ArrowUp' || (alt && e.key === 'ArrowUp')) { - e.preventDefault(); - video.volume = Math.min(1, (video.volume || 1) + 0.05); - announce('Ses seviyesi yüzde ' + Math.round(video.volume * 100)); - } else if (e.key === 'ArrowDown' || (alt && e.key === 'ArrowDown')) { - e.preventDefault(); - video.volume = Math.max(0, (video.volume || 1) - 0.05); - announce('Ses seviyesi yüzde ' + Math.round(video.volume * 100)); - } - // Mute (Alt+M or M) - else if (key === 'm' || (alt && key === 'm')) { - e.preventDefault(); - video.muted = !video.muted; - announce(video.muted ? 'Ses kapatıldı (Sessiz).' : 'Ses açıldı.'); - } - // Fullscreen (Alt+F or F) - else if (key === 'f' || (alt && 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ı.'); - } - } - // Theater Mode (Alt+T or T) - else if (key === 't' || (alt && key === 't')) { - e.preventDefault(); - document.body.classList.toggle('yaktube-theater-mode'); - var isTheater = document.body.classList.contains('yaktube-theater-mode'); - announce(isTheater ? 'Sinema modu açıldı.' : 'Sinema modundan çıkıldı.'); - } - // Quick Video Info (Alt+I or I) - else if (key === 'i' || (alt && key === 'i')) { - e.preventDefault(); - showQuickVideoInfo(); - } - // Subtitles (Alt+C or C) - else if (key === 'c' || (alt && 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.'); - } - } - // Speed Controls (Alt+. / Alt+, or > / <) - else if ((alt && (e.key === '.' || e.key === '>')) || e.key === '>') { - e.preventDefault(); - adjustPlaybackSpeed(true); - } else if ((alt && (e.key === ',' || e.key === '<')) || e.key === '<') { - e.preventDefault(); - adjustPlaybackSpeed(false); - } - // Percentage Jumps (0 - 9) - 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ı. Konum: ' + 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() { + // Persist active live stream state in sessionStorage try { - sessionStorage.removeItem('yaktube_active_live'); + sessionStorage.setItem( + 'yaktube_active_live', + JSON.stringify({ + url: url, + title: videoTitle, + videoId: videoId + }) + ); } catch (e) {} - if (liveContainer && liveContainer.parentNode) { - var ifr = liveContainer.querySelector('iframe'); - if (ifr) ifr.src = ''; - liveContainer.remove(); + + 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); } - 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(); + 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 closeBtn = document.getElementById('yaktube-close-live-theater'); - if (closeBtn) closeBtn.addEventListener('click', closeLiveTheater); - document.addEventListener('keydown', handleEsc); - } + 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 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); + 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; } - } 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ı.'); + function fetchComments(token) { + if (!token) { + toggleCommentsBtn.disabled = true; + toggleCommentsBtn.textContent = '⏳ Yorumlar Y\u00fckleniyor...'; + } else { + moreCommentsBtn.disabled = true; + moreCommentsBtn.textContent = '⏳ Y\u00fckleniyor...'; } - 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 = - '
' + - '' + - '
'; + 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 ' + - countText + - '

' + + '" için YouTube\'da Sonuç Bulunamadı' + '
' + - '' + - '
' + - '
' + - cardsHtml + - '
' + - loadMoreHtml; + '
'; + 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); - var sBtn = document.getElementById('yaktube-open-shortcuts'); - if (sBtn) { - sBtn.addEventListener('click', toggleShortcutsModal); - } + function render() { + var visibleResults = uniqueResults.slice(0, visibleCount); + var cardsHtml = ''; + visibleResults.forEach(function (item) { + var isLocal = !!item.isDownloaded; + var isLive = !!item.isLive; - 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.'); + 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); + }); }); } - 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; + }); + } - render(); + // -------------------------------------------------------------------------- + // -------------------------------------------------------------------------- + // -------------------------------------------------------------------------- + // -------------------------------------------------------------------------- + // -------------------------------------------------------------------------- + // -------------------------------------------------------------------------- + // -------------------------------------------------------------------------- + // -------------------------------------------------------------------------- + // -------------------------------------------------------------------------- + // -------------------------------------------------------------------------- + // 8. FULL YOUTUBE SEARCH & VOICE MODAL ENGINE (v1.3.3 - Guaranteed Close Fix) + // -------------------------------------------------------------------------- + var activeVoiceRec = null; + var isVoiceListening = false; + + // Dynamic Search History & Trending Helpers + function getRecentSearches() { + try { + var data = localStorage.getItem('yaktube_recent_searches'); + if (data) { + var parsed = JSON.parse(data); + if (Array.isArray(parsed) && parsed.length > 0) return parsed; } - }) - .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; - }); - } + } catch (e) {} + // Default initial recent searches for first-time visitors + var defaults = ['Tarkan', 'Barış Manço', 'Yapay Zeka', 'Canlı Haber']; + try { + localStorage.setItem('yaktube_recent_searches', JSON.stringify(defaults)); + } catch (e) {} + return defaults; + } - // -------------------------------------------------------------------------- - // -------------------------------------------------------------------------- - // -------------------------------------------------------------------------- - // -------------------------------------------------------------------------- - // -------------------------------------------------------------------------- - // -------------------------------------------------------------------------- - // -------------------------------------------------------------------------- - // -------------------------------------------------------------------------- - // -------------------------------------------------------------------------- - // -------------------------------------------------------------------------- - // 8. FULL YOUTUBE SEARCH & VOICE MODAL ENGINE (v1.3.3 - Guaranteed Close Fix) - // -------------------------------------------------------------------------- - var activeVoiceRec = null; - var isVoiceListening = false; + function saveRecentSearch(query) { + if (!query || query.trim().length < 2) return; + var q = query.trim(); + try { + var list = getRecentSearches(); + list = list.filter(function (item) { + return item.toLowerCase() !== q.toLowerCase(); + }); + list.unshift(q); + if (list.length > 8) list = list.slice(0, 8); + localStorage.setItem('yaktube_recent_searches', JSON.stringify(list)); + } catch (e) {} + } - // Dynamic Search History & Trending Helpers - function getRecentSearches() { - try { - var data = localStorage.getItem('yaktube_recent_searches'); - if (data) { - var parsed = JSON.parse(data); - if (Array.isArray(parsed) && parsed.length > 0) return parsed; - } - } catch (e) {} - // Default initial recent searches for first-time visitors - var defaults = ['Tarkan', 'Barış Manço', 'Yapay Zeka', 'Canlı Haber']; - try { - localStorage.setItem('yaktube_recent_searches', JSON.stringify(defaults)); - } catch (e) {} - return defaults; - } + function clearRecentSearches() { + try { + localStorage.removeItem('yaktube_recent_searches'); + } catch (e) {} + renderDynamicSearchChips(); + announce('Arama geçmişi temizlendi.'); + } - function saveRecentSearch(query) { - if (!query || query.trim().length < 2) return; - var q = query.trim(); - try { - var list = getRecentSearches(); - list = list.filter(function (item) { - return item.toLowerCase() !== q.toLowerCase(); - }); - list.unshift(q); - if (list.length > 8) list = list.slice(0, 8); - localStorage.setItem('yaktube_recent_searches', JSON.stringify(list)); - } catch (e) {} - } + var popularTrendingQueries = [ + { title: '🎵 Türkçe Pop', q: 'Türkçe Pop' }, + { title: '🎸 Anadolu Rock', q: 'Anadolu Rock' }, + { title: '🤖 Yapay Zeka', q: 'Yapay Zeka' }, + { title: '💻 Yazılım Dersleri', q: 'Yazılım Dersleri' }, + { title: '🎧 Sesli Kitap & Radyo Tiyatrosu', q: 'Sesli Kitap' }, + { title: '📺 Canlı Haber', q: 'Canlı Haber' }, + { title: '🌍 Bilim & Belgesel', q: 'Bilim Belgeseli' }, + { title: '🎼 Akustik Canlı Performans', q: 'Akustik Canlı' }, + { title: '🎙️ Podcast & Söyleşi', q: 'Podcast Türkçe' } + ]; - function clearRecentSearches() { - try { - localStorage.removeItem('yaktube_recent_searches'); - } catch (e) {} - renderDynamicSearchChips(); - announce('Arama geçmişi temizlendi.'); - } + // Auto capture search query from URL into recent searches + function captureUrlSearchQuery() { + try { + var params = new URLSearchParams(window.location.search); + var q = params.get('search') || params.get('q'); + if (q && q.trim().length >= 2) { + saveRecentSearch(q.trim()); + } + } catch (e) {} + } + captureUrlSearchQuery(); + window.addEventListener('popstate', captureUrlSearchQuery); - var popularTrendingQueries = [ - { title: '🎵 Türkçe Pop', q: 'Türkçe Pop' }, - { title: '🎸 Anadolu Rock', q: 'Anadolu Rock' }, - { title: '🤖 Yapay Zeka', q: 'Yapay Zeka' }, - { title: '💻 Yazılım Dersleri', q: 'Yazılım Dersleri' }, - { title: '🎧 Sesli Kitap & Radyo Tiyatrosu', q: 'Sesli Kitap' }, - { title: '📺 Canlı Haber', q: 'Canlı Haber' }, - { title: '🌍 Bilim & Belgesel', q: 'Bilim Belgeseli' }, - { title: '🎼 Akustik Canlı Performans', q: 'Akustik Canlı' }, - { title: '🎙️ Podcast & Söyleşi', q: 'Podcast Türkçe' } - ]; + function renderDynamicSearchChips() { + var modal = document.getElementById('yaktube-full-search-modal'); + if (!modal) return; - // Auto capture search query from URL into recent searches - function captureUrlSearchQuery() { - try { - var params = new URLSearchParams(window.location.search); - var q = params.get('search') || params.get('q'); - if (q && q.trim().length >= 2) { - saveRecentSearch(q.trim()); - } - } catch (e) {} - } - captureUrlSearchQuery(); - window.addEventListener('popstate', captureUrlSearchQuery); + var container = modal.querySelector('#yaktube-modal-dynamic-chips-section'); + if (!container) return; - function renderDynamicSearchChips() { - var modal = document.getElementById('yaktube-full-search-modal'); - if (!modal) return; + var recent = getRecentSearches(); + var html = ''; - var container = modal.querySelector('#yaktube-modal-dynamic-chips-section'); - if (!container) return; - - var recent = getRecentSearches(); - var html = ''; - - // 1. Recent searches section (if any) - if (recent.length > 0) { - html += - '
' + - '
🕒 Son Aramalarınız
' + - '' + - '
' + - '
'; - - recent.forEach(function (term) { - var safeTerm = term.replace(/"/g, '"'); + // 1. Recent searches section (if any) + if (recent.length > 0) { html += - '' + + '
' + + '
'; + + recent.forEach(function (term) { + var safeTerm = term.replace(/"/g, '"'); + html += + ''; + }); + html += '
'; + } + + // 2. Popular & Trending searches section + html += + '
🔥 Hızlı ve Popüler Aramalar
' + + '
'; + + popularTrendingQueries.forEach(function (item) { + var safeQ = item.q.replace(/"/g, '"'); + html += + ''; }); html += '
'; + + container.innerHTML = html; + + // Attach click events + container.querySelectorAll('.yaktube-modal-chip').forEach(function (chip) { + chip.addEventListener('click', function () { + var query = chip.getAttribute('data-query'); + var inp = modal.querySelector('#yaktube-modal-input'); + if (inp) inp.value = query; + if (typeof window.__yaktube_execute_search__ === 'function') { + window.__yaktube_execute_search__(query); + } + }); + }); + + var clearBtn = container.querySelector('#yaktube-clear-recent-btn'); + if (clearBtn) { + clearBtn.addEventListener('click', function (e) { + e.preventDefault(); + e.stopPropagation(); + clearRecentSearches(); + }); + } } - // 2. Popular & Trending searches section - html += - '
🔥 Hızlı ve Popüler Aramalar
' + - '
'; + function createFullSearchModal() { + var existingModal = document.getElementById('yaktube-full-search-modal'); + if (existingModal) return existingModal; - popularTrendingQueries.forEach(function (item) { - var safeQ = item.q.replace(/"/g, '"'); - html += - ''; - }); - html += '
'; + 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'); - container.innerHTML = html; + modal.innerHTML = + '
' + + '' + + '
' + + '
' + + '
🎙️ Dinleniyor...
' + + '
Lütfen aramak istediğiniz videoyu söyleyin...
' + + '' + + '
' + + '
' + + '' + + '
' + + '
'; - // Attach click events - container.querySelectorAll('.yaktube-modal-chip').forEach(function (chip) { - chip.addEventListener('click', function () { - var query = chip.getAttribute('data-query'); - var inp = modal.querySelector('#yaktube-modal-input'); - if (inp) inp.value = query; - if (typeof window.__yaktube_execute_search__ === 'function') { - window.__yaktube_execute_search__(query); + 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'; } }); - }); - var clearBtn = container.querySelector('#yaktube-clear-recent-btn'); - if (clearBtn) { - clearBtn.addEventListener('click', function (e) { + 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 (e) { e.preventDefault(); e.stopPropagation(); - clearRecentSearches(); + closeFullSearchModal(); }); - } - } - function createFullSearchModal() { - var existingModal = document.getElementById('yaktube-full-search-modal'); - if (existingModal) return existingModal; + modal.addEventListener('click', function (e) { + if (e.target === modal) { + closeFullSearchModal(); + } + }); - 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.innerHTML = - '
' + - '' + - '
' + - '
' + - '
🎙️ Dinleniyor...
' + - '
Lütfen aramak istediğiniz videoyu söyleyin...
' + - '' + - '
' + - '
' + - '' + - '
' + - '
'; - - 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'; + // Search execute + function executeSearch(query) { + var q = (query || inp.value || '').trim(); + if (q) { + saveRecentSearch(q); + announce('"' + q + '" için arama yapılıyor...', true); + closeFullSearchModal(); + window.location.href = '/search/videos?search=' + encodeURIComponent(q); + } } - }); + window.__yaktube_execute_search__ = executeSearch; + renderDynamicSearchChips(); - 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 (e) { - e.preventDefault(); - e.stopPropagation(); - closeFullSearchModal(); - }); - - modal.addEventListener('click', function (e) { - if (e.target === modal) { - closeFullSearchModal(); - } - }); - - // Search execute - function executeSearch(query) { - var q = (query || inp.value || '').trim(); - if (q) { - saveRecentSearch(q); - announce('"' + q + '" için arama yapılıyor...', true); - closeFullSearchModal(); - window.location.href = '/search/videos?search=' + encodeURIComponent(q); - } - } - window.__yaktube_execute_search__ = executeSearch; - renderDynamicSearchChips(); - - submitBtn.addEventListener('click', function () { - executeSearch(); - }); - - inp.addEventListener('keydown', function (e) { - if (e.key === 'Enter') { - e.preventDefault(); + submitBtn.addEventListener('click', function () { executeSearch(); - } else if (e.key === 'Escape') { + }); + + 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.classList.add('yaktube-modal-open'); + modal.style.display = 'flex'; + renderDynamicSearchChips(); + + 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.classList.remove('yaktube-modal-open'); + modal.style.display = 'none'; + } + stopVoiceInsideModal(); + announce('Arama penceresi kapatıldı.'); + + var trigger = document.querySelector('.yaktube-search-trigger-pill'); + if (trigger) { + try { + trigger.focus(); + } catch (e) {} + } + } + + 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(); - closeFullSearchModal(); + 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.classList.contains('yaktube-modal-open')) { + 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); } }); - // Chips click - modal.querySelectorAll('.yaktube-modal-chip').forEach(function (chip) { - chip.addEventListener('click', function () { - var query = chip.getAttribute('data-query'); - executeSearch(query); + // 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 && navigator.userAgent.indexOf('YakTubeMobileApp') !== -1) || + (typeof window.matchMedia === 'function' && + window.matchMedia('(display-mode: standalone)') && + window.matchMedia('(display-mode: standalone)').matches) || + (window.navigator && 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(); }); - // Voice button click - voiceBtn.addEventListener('click', function () { - toggleVoiceInsideModal(); + 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 isYakTubeInstance() { + return window.location.hostname.includes('yaktube.yakhub.com.tr') || getInstanceName() === 'YakTube'; + } + + function injectApkDownloadButton() { + if (!isYakTubeInstance()) return; + // 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 (!isYakTubeInstance()) return; + // 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(); + })(); - micPulseBtn.addEventListener('click', function () { - toggleVoiceInsideModal(); - }); + // 5. Background Play (Screen-Off / Minimize) & MediaSession Lock-Screen Controls + (function initBackgroundPlaybackEngine() { + var videoState = { + hasPlayed: false, + pausedByUser: false, + pausedAt: null + }; - return modal; - } + function attachVideoListeners(video) { + if (!video || video.hasAttribute('data-yaktube-bgplay-hooked')) return; + video.setAttribute('data-yaktube-bgplay-hooked', 'true'); - function openFullSearchModal(initialQuery, autoStartVoice) { - var modal = createFullSearchModal(); - modal.classList.add('yaktube-modal-open'); - modal.style.display = 'flex'; - renderDynamicSearchChips(); + video.addEventListener('play', function () { + videoState.hasPlayed = true; + videoState.pausedByUser = false; + updateMediaSessionMetadata(video); + }); - 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'; + video.addEventListener('pause', function () { + videoState.pausedAt = Date.now(); + // If document is visible when paused, it was paused manually by the user + if (!document.hidden) { + videoState.pausedByUser = true; + } + }); + + video.addEventListener('ended', function () { + videoState.hasPlayed = false; + videoState.pausedByUser = true; + }); + + video.addEventListener('timeupdate', function () { + updateMediaSessionPosition(video); + }); + } + + function handleVisibilityChange() { + if (!document.hidden) return; // Screen is active + + var video = getVideoElement(); + if (!video) return; + + // If user hasn't played or deliberately paused, do not auto-resume + if (!videoState.hasPlayed || videoState.pausedByUser) return; + + var now = Date.now(); + if (videoState.pausedAt && now - videoState.pausedAt > 1500 && videoState.pausedByUser) { + return; + } + + // Resume playback when screen locks or tab switches setTimeout(function () { - inp.focus(); - inp.select(); + if (video.paused && !videoState.pausedByUser) { + video.play().catch(function () {}); + } }, 50); } - announce('Arama penceresi açıldı. Arayacağınız konuyu yazın veya sesle arayın.', true); + document.addEventListener('visibilitychange', handleVisibilityChange, false); + document.addEventListener('webkitvisibilitychange', handleVisibilityChange, false); - if (autoStartVoice) { - startVoiceInsideModal(); - } - } + // MediaSession API Integration (Lock Screen, Bluetooth, Smart Watches) + function updateMediaSessionMetadata(video) { + if (!('mediaSession' in navigator)) return; - function closeFullSearchModal() { - var modal = document.getElementById('yaktube-full-search-modal'); - if (modal) { - modal.classList.remove('yaktube-modal-open'); - modal.style.display = 'none'; - } - stopVoiceInsideModal(); - announce('Arama penceresi kapatıldı.'); + var titleEl = document.querySelector('h1.video-title, .video-name, my-video-watch h1, .title'); + var title = titleEl ? titleEl.textContent.trim() : 'YakTube Video'; + var authorEl = document.querySelector('.actor-name, .channel-name, .video-channel-name, a.actor-link'); + var artist = authorEl ? authorEl.textContent.trim() : 'YakTube'; + + var artwork = []; + var thumbEl = document.querySelector('meta[property="og:image"]'); + if (thumbEl && thumbEl.content) { + artwork.push({ src: thumbEl.content, sizes: '512x512', type: 'image/jpeg' }); + } - var trigger = document.querySelector('.yaktube-search-trigger-pill'); - if (trigger) { try { - trigger.focus(); - } catch (e) {} - } - } - - 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.classList.contains('yaktube-modal-open')) { - 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); - } - } + navigator.mediaSession.metadata = new MediaMetadata({ + title: title, + artist: artist, + album: 'YakTube', + artwork: artwork }); - 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; + navigator.mediaSession.setActionHandler('play', function () { + video.play(); + announce('Video oynatılıyor.'); }); - } - }); - - headerButtons.prepend(installBtn); - } - - function isYakTubeInstance() { - return window.location.hostname.includes('yaktube.yakhub.com.tr') || getInstanceName() === 'YakTube'; - } - - function injectApkDownloadButton() { - if (!isYakTubeInstance()) return; - // 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; + navigator.mediaSession.setActionHandler('pause', function () { + videoState.pausedByUser = true; + video.pause(); + announce('Video duraklatıldı.'); + }); + navigator.mediaSession.setActionHandler('seekbackward', function (details) { + var skipTime = details.seekOffset || 10; + video.currentTime = Math.max(0, video.currentTime - skipTime); + announce(skipTime + ' saniye geri sarıldı.'); + }); + navigator.mediaSession.setActionHandler('seekforward', function (details) { + var skipTime = details.seekOffset || 10; + video.currentTime = Math.min(video.duration || 99999, video.currentTime + skipTime); + announce(skipTime + ' saniye ileri sarıldı.'); + }); + navigator.mediaSession.setActionHandler('stop', function () { + videoState.pausedByUser = true; + video.pause(); + }); + } catch (e) {} } - // 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; + function updateMediaSessionPosition(video) { + if (!('mediaSession' in navigator) || !navigator.mediaSession.setPositionState) return; + if (isNaN(video.duration) || isNaN(video.currentTime) || video.duration <= 0) return; + try { + navigator.mediaSession.setPositionState({ + duration: video.duration, + playbackRate: video.playbackRate || 1, + position: Math.min(video.currentTime, video.duration) + }); + } catch (e) {} } - // 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 (!isYakTubeInstance()) return; - // 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(); -})(); - -// 5. Background Play (Screen-Off / Minimize) & MediaSession Lock-Screen Controls -(function initBackgroundPlaybackEngine() { - var videoState = { - hasPlayed: false, - pausedByUser: false, - pausedAt: null - }; - - function attachVideoListeners(video) { - if (!video || video.hasAttribute('data-yaktube-bgplay-hooked')) return; - video.setAttribute('data-yaktube-bgplay-hooked', 'true'); - - video.addEventListener('play', function () { - videoState.hasPlayed = true; - videoState.pausedByUser = false; - updateMediaSessionMetadata(video); - }); - - video.addEventListener('pause', function () { - videoState.pausedAt = Date.now(); - // If document is visible when paused, it was paused manually by the user - if (!document.hidden) { - videoState.pausedByUser = true; - } - }); - - video.addEventListener('ended', function () { - videoState.hasPlayed = false; - videoState.pausedByUser = true; - }); - - video.addEventListener('timeupdate', function () { - updateMediaSessionPosition(video); - }); - } - - function handleVisibilityChange() { - if (!document.hidden) return; // Screen is active - - var video = getVideoElement(); - if (!video) return; - - // If user hasn't played or deliberately paused, do not auto-resume - if (!videoState.hasPlayed || videoState.pausedByUser) return; - - var now = Date.now(); - if (videoState.pausedAt && now - videoState.pausedAt > 1500 && videoState.pausedByUser) { - return; - } - - // Resume playback when screen locks or tab switches - setTimeout(function () { - if (video.paused && !videoState.pausedByUser) { - video.play().catch(function () {}); - } - }, 50); - } - - document.addEventListener('visibilitychange', handleVisibilityChange, false); - document.addEventListener('webkitvisibilitychange', handleVisibilityChange, false); - - // MediaSession API Integration (Lock Screen, Bluetooth, Smart Watches) - function updateMediaSessionMetadata(video) { - if (!('mediaSession' in navigator)) return; - - var titleEl = document.querySelector('h1.video-title, .video-name, my-video-watch h1, .title'); - var title = titleEl ? titleEl.textContent.trim() : 'YakTube Video'; - var authorEl = document.querySelector('.actor-name, .channel-name, .video-channel-name, a.actor-link'); - var artist = authorEl ? authorEl.textContent.trim() : 'YakTube'; - - var artwork = []; - var thumbEl = document.querySelector('meta[property="og:image"]'); - if (thumbEl && thumbEl.content) { - artwork.push({ src: thumbEl.content, sizes: '512x512', type: 'image/jpeg' }); - } - - try { - navigator.mediaSession.metadata = new MediaMetadata({ - title: title, - artist: artist, - album: 'YakTube', - artwork: artwork - }); - - navigator.mediaSession.setActionHandler('play', function () { - video.play(); - announce('Video oynatılıyor.'); - }); - navigator.mediaSession.setActionHandler('pause', function () { - videoState.pausedByUser = true; - video.pause(); - announce('Video duraklatıldı.'); - }); - navigator.mediaSession.setActionHandler('seekbackward', function (details) { - var skipTime = details.seekOffset || 10; - video.currentTime = Math.max(0, video.currentTime - skipTime); - announce(skipTime + ' saniye geri sarıldı.'); - }); - navigator.mediaSession.setActionHandler('seekforward', function (details) { - var skipTime = details.seekOffset || 10; - video.currentTime = Math.min(video.duration || 99999, video.currentTime + skipTime); - announce(skipTime + ' saniye ileri sarıldı.'); - }); - navigator.mediaSession.setActionHandler('stop', function () { - videoState.pausedByUser = true; - video.pause(); - }); - } catch (e) {} - } - - function updateMediaSessionPosition(video) { - if (!('mediaSession' in navigator) || !navigator.mediaSession.setPositionState) return; - if (isNaN(video.duration) || isNaN(video.currentTime) || video.duration <= 0) return; - try { - navigator.mediaSession.setPositionState({ - duration: video.duration, - playbackRate: video.playbackRate || 1, - position: Math.min(video.currentTime, video.duration) - }); - } catch (e) {} - } - - // Auto-hook active video elements - setInterval(function () { - var video = getVideoElement(); - if (video) attachVideoListeners(video); - }, 1000); -})(); + // Auto-hook active video elements + setInterval(function () { + var video = getVideoElement(); + if (video) attachVideoListeners(video); + }, 1000); + })(); +} catch (err) { + console.error('[YakTube] Safe catch error:', err); +} diff --git a/package.json b/package.json index cd316a5..05e1879 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "peertube-plugin-yaktube-ondemand", - "version": "1.4.9", + "version": "1.4.10", "description": "Accessible On-Demand YouTube Import, Live Streams, Smart Recommendations and A11y Engine for PeerTube", "main": "main.js", "library": "./main.js",