Files
peertube-plugin-yaktube-ond…/client.js
T

4529 lines
190 KiB
JavaScript

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 100% Incompatible Browser Suppression & Error Guardian
// CRITICAL: PeerTube's Angular app (my-app ngOnInit) relies on document.getElementById('incompatible-browser')
// to exist so it can do .className += " browser-ok" before calling this.loadUser()!
// Removing it from DOM causes a null pointer crash that breaks user session initialization!
if (typeof window !== 'undefined') {
try {
window.displayIncompatibleBrowser = function () {};
var neuterIncompatible = function () {
var el = document.getElementById('incompatible-browser');
if (el) {
if (!el.classList.contains('browser-ok')) {
el.classList.add('browser-ok');
}
el.style.setProperty('display', 'none', 'important');
el.style.setProperty('visibility', 'hidden', 'important');
el.style.setProperty('height', '0px', 'important');
el.style.setProperty('width', '0px', 'important');
el.style.setProperty('opacity', '0', 'important');
el.style.setProperty('pointer-events', 'none', 'important');
} else {
try {
var dummy = document.createElement('div');
dummy.id = 'incompatible-browser';
dummy.className = 'browser-ok';
dummy.style.display = 'none';
(document.body || document.documentElement).appendChild(dummy);
} catch (e) {}
}
};
neuterIncompatible();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', neuterIncompatible);
}
var neuterInterval = setInterval(neuterIncompatible, 1000);
setTimeout(function () {
clearInterval(neuterInterval);
}, 5000);
} catch (e) {}
}
// Immediate URL rewrite: redirect legacy /search/videos to /search
if (typeof window !== 'undefined' && window.location) {
if (window.location.pathname === '/search/videos' || window.location.pathname.startsWith('/search/videos/')) {
var targetSearch = window.location.search || '';
window.location.replace('/search' + targetSearch);
}
}
try {
(function () {
if (window.__YAKTUBE_ENGINE_LOADED__) return;
window.__YAKTUBE_ENGINE_LOADED__ = true;
try {
localStorage.setItem('hide-mobile-msg', 'true');
sessionStorage.setItem('hide-mobile-msg', 'true');
} catch (e) {}
console.log('[YakTube] YakNet SSO, Custom Branding, A11y & On-Demand Engine Active');
// Ensure Fediverse Global Search (SepiaSearch) is default in PeertubeServerConfig
try {
if (
window.PeertubeServerConfig &&
window.PeertubeServerConfig.search &&
window.PeertubeServerConfig.search.searchIndex
) {
window.PeertubeServerConfig.search.searchIndex.isDefaultSearch = true;
}
} catch (e) {}
// 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 (W3C WCAG 2.2 AAA Compliant Auto-Clearing Engine)
function applySrOnlyStyles(el) {
el.className = 'yaktube-sr-only';
el.style.position = 'absolute';
el.style.width = '1px';
el.style.height = '1px';
el.style.padding = '0';
el.style.margin = '-1px';
el.style.overflow = 'hidden';
el.style.clip = 'rect(0, 0, 0, 0)';
el.style.whiteSpace = 'nowrap';
el.style.border = '0';
}
function getLiveRegion(assertive) {
var id = assertive ? 'yaktube-a11y-live-assertive' : 'yaktube-a11y-live-region';
var region = document.getElementById(id);
if (!region || !document.body.contains(region)) {
region = document.createElement('div');
region.id = id;
region.setAttribute('aria-live', assertive ? 'assertive' : 'polite');
region.setAttribute('aria-atomic', 'true');
region.setAttribute('aria-relevant', 'additions text');
applySrOnlyStyles(region);
document.body.appendChild(region);
}
return region;
}
function announce(text, assertive) {
if (!text || !String(text).trim()) return;
var strText = String(text).trim();
var region = getLiveRegion(assertive);
if (region._setTimer) {
clearTimeout(region._setTimer);
region._setTimer = null;
}
if (region._clearTimer) {
clearTimeout(region._clearTimer);
region._clearTimer = null;
}
// Screen reader mutation trigger: clear first so repeat announcements are re-announced
region.textContent = '';
region._setTimer = setTimeout(function () {
region.textContent = strText;
// Seslendirme yapıldıktan veya ekran okuyucu kuyruğuna alındıktan sonra
// sayfanın en altında ya da sanal imleç gezintisinde eski metin kalmaması için
// DOM'daki duyuru metnini temizle (Metin uzunluğuna göre 3 ile 7 saniye arası)
var clearDelay = Math.min(7000, Math.max(3000, strText.length * 50));
region._clearTimer = setTimeout(function () {
if (region && document.body.contains(region)) {
region.textContent = '';
}
region._clearTimer = null;
}, clearDelay);
region._setTimer = null;
}, 50);
}
// Load official YakNet Web Component definition
if (
typeof customElements !== 'undefined' &&
!customElements.get('yaknet-account') &&
!document.getElementById('yaknet-account-script')
) {
var s = document.createElement('script');
s.id = 'yaknet-account-script';
s.src = 'https://auth.yakhub.com.tr/js/yaknet-account.js';
s.async = true;
document.head.appendChild(s);
}
// Intercept and sanitize YakNet Account Web Component popup menu
function hookYakNetAccountComponent() {
if (typeof customElements === 'undefined') return;
customElements
.whenDefined('yaknet-account')
.then(function () {
var YakNetAccount = customElements.get('yaknet-account');
if (!YakNetAccount || !YakNetAccount.prototype || YakNetAccount.prototype._yaktubeSanitizerHooked) return;
YakNetAccount.prototype._yaktubeSanitizerHooked = true;
var originalRender = YakNetAccount.prototype.render;
if (typeof originalRender === 'function') {
YakNetAccount.prototype.render = function () {
originalRender.call(this);
try {
if (this.shadowRoot) {
var isOfficial = isOfficialYakTubeServer();
// 1. In YakTube: Suppress redundant/recursive link to YakTube inside services list
var selfLinks = this.shadowRoot.querySelectorAll('a[href*="yaktube.yakhub.com.tr"]');
selfLinks.forEach(function (a) {
a.style.display = 'none';
});
// 2. In third-party PeerTube instances: Suppress entire external services grid & header to avoid community backlash
if (!isOfficial) {
var appsGrid = this.shadowRoot.querySelector('.yn-apps-grid');
if (appsGrid) appsGrid.style.display = 'none';
var sectionTitle = this.shadowRoot.querySelector('#yn-services-label, .yn-section-title');
if (sectionTitle) sectionTitle.style.display = 'none';
}
}
} catch (e) {}
};
}
})
.catch(function () {});
}
hookYakNetAccountComponent();
// Silent background SSO Probe Engine
function initSilentSSOProbe() {
var token = localStorage.getItem('access_token');
var isManualLogout = sessionStorage.getItem('yaknet_manual_logout');
if (
token ||
isManualLogout ||
window.location.search.includes('local=true') ||
window.location.search.includes('externalAuthError') ||
window.location.pathname.includes('/login')
) {
return;
}
var iframeId = 'yaknet-sso-iframe-instance';
if (!document.getElementById(iframeId)) {
var iframe = document.createElement('iframe');
iframe.id = iframeId;
iframe.src = 'https://auth.yakhub.com.tr/sdk/sso-frame';
iframe.title = 'YakNet Oturum Kontrolü';
iframe.setAttribute('aria-hidden', 'true');
iframe.tabIndex = -1;
iframe.style.cssText =
'display:none!important;width:0!important;height:0!important;border:0!important;position:absolute!important;pointer-events:none!important;';
document.body.appendChild(iframe);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initSilentSSOProbe);
} else {
initSilentSSOProbe();
}
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...');
clearYakTubeAuthSession();
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) {
clearYakTubeAuthSession();
window.location.reload();
}
}
});
// Clear YakTube auth session and cloud search cache safely
function clearYakTubeAuthSession() {
try {
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
localStorage.removeItem('token_type');
localStorage.removeItem('id');
localStorage.removeItem('username');
localStorage.removeItem('displayName');
localStorage.removeItem('email');
localStorage.removeItem('role');
localStorage.removeItem('yaktube_recent_searches');
sessionStorage.clear();
} catch (e) {}
}
// Unified single sign-out for YakTube and YakNet
function performUnifiedLogout() {
clearYakTubeAuthSession();
try {
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 =
'<div class="yaktube-modal-card" style="max-width: 420px; background:#181824; border:1px solid rgba(255,143,55,0.4); border-radius:18px; padding:26px; color:#fff; box-shadow:0 16px 40px rgba(0,0,0,0.8); text-align:left;">' +
'<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:18px;">' +
'<h3 id="yaktube-admin-modal-title" style="margin:0; font-size:18px; color:#ff8f37;">⚙️ Sunucu Yöneticisi Girişi</h3>' +
'<button id="yaktube-admin-modal-close" class="yaktube-voice-modal-close-btn" aria-label="Kapat">✕</button>' +
'</div>' +
'<p style="font-size:13px; color:#aaa; margin-bottom:16px;">Yerel yönetici (admin) kullanıcı adı ve şifrenizle giriş yaparak PeerTube yönetim paneline erişebilirsiniz.</p>' +
'<form id="yaktube-admin-direct-form" style="display:flex; flex-direction:column; gap:12px;">' +
'<div style="display:flex; flex-direction:column; gap:4px;">' +
'<label for="yaktube-admin-username" style="font-size:13px; font-weight:600; color:#ddd;">Kullanıcı Adı veya E-posta:</label>' +
'<input type="text" id="yaktube-admin-username" required style="background:#22222e; border:1px solid #444; border-radius:8px; padding:10px 12px; color:#fff; font-size:14px; outline:none;" />' +
'</div>' +
'<div style="display:flex; flex-direction:column; gap:4px;">' +
'<label for="yaktube-admin-password" style="font-size:13px; font-weight:600; color:#ddd;">Şifre:</label>' +
'<input type="password" id="yaktube-admin-password" required style="background:#22222e; border:1px solid #444; border-radius:8px; padding:10px 12px; color:#fff; font-size:14px; outline:none;" />' +
'</div>' +
'<div id="yaktube-admin-login-error" style="color:#ef4444; font-size:13px; display:none; margin-top:4px;"></div>' +
'<button type="submit" id="yaktube-admin-login-submit" style="background:linear-gradient(135deg, #ff8f37 0%, #ff5e3a 100%); color:#fff; border:none; border-radius:8px; padding:12px; font-weight:700; font-size:14px; cursor:pointer; margin-top:6px;">Yönetici Olarak Giriş Yap</button>' +
'</form>' +
'</div>';
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...';
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 liveWidget = headerRight.querySelector('#yaknet-header-account-widget');
if (isAuth) {
// 1. AUTHENTICATED: Add class to body & Hide ALL native login links completely
document.body.classList.add('yaktube-user-logged-in');
document
.querySelectorAll('my-login-link, a[href*="/login"], .login-button, [class*="login-link"]')
.forEach(function (btn) {
btn.style.setProperty('display', 'none', 'important');
btn.setAttribute('aria-hidden', 'true');
btn.setAttribute('tabindex', '-1');
});
if (!liveWidget && isOfficialYakTubeServer()) {
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 =
'<yaknet-account ' +
'client-id="01a03c41-f758-721e-b927-619bffde5c23" ' +
'redirect-uri="https://yaktube.yakhub.com.tr/plugins/peertube-plugin-auth-yaknet/router/auth-callback" ' +
'base-url="https://auth.yakhub.com.tr" ' +
'login-url="/login" ' +
'authenticated="true" ' +
'user-name="' +
displayName.replace(/"/g, '&quot;') +
'" ' +
'user-email="' +
emailStr.replace(/"/g, '&quot;') +
'" ' +
'theme="dark">' +
'</yaknet-account>';
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 if (liveWidget) {
if (!isOfficialYakTubeServer()) {
liveWidget.remove();
} else {
var el = liveWidget.querySelector('yaknet-account');
if (el) {
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();
}
}
if (el.shadowRoot) {
// Direct DOM safety: remove self-link on polling cycle too
var selfLinks = el.shadowRoot.querySelectorAll('a[href*="yaktube.yakhub.com.tr"]');
selfLinks.forEach(function (a) {
a.style.display = 'none';
});
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
);
}
}
}
}
}
} else {
// 2. GUEST / NOT LOGGED IN:
document.body.classList.remove('yaktube-user-logged-in');
if (liveWidget) {
liveWidget.remove();
}
document
.querySelectorAll('my-login-link, a[href*="/login"], .login-button, [class*="login-link"]')
.forEach(function (btn) {
btn.style.display = '';
btn.removeAttribute('aria-hidden');
btn.removeAttribute('tabindex');
var loginAnchor = btn.tagName.toLowerCase() === 'a' ? btn : btn.querySelector('a');
if (loginAnchor) {
loginAnchor.setAttribute('aria-label', 'YakNet ile Giriş Yap');
loginAnchor.setAttribute('title', 'YakNet ile Giriş Yap');
var spanText = loginAnchor.querySelector('span');
if (spanText) {
spanText.textContent = 'YakNet ile Giriş Yap';
} else {
loginAnchor.textContent = 'YakNet ile Giriş Yap';
}
}
});
}
}
// 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 =
'<div style="font-size: 19px; font-weight: 700; color: #ff8f37; margin-bottom: 6px;">YakNet ile YakTube\'a Katılın</div>' +
'<p style="font-size: 13px; color: #94a3b8; margin-bottom: 16px; line-height: 1.45;">' +
'YouTube gibi tek bir YakNet hesabıyla güvenle giriş yapabilir, kendi video ve müzik kanallarınızı oluşturabilirsiniz.' +
'</p>' +
'<a href="https://auth.yakhub.com.tr/oauth/authorize?client_id=01a03c41-f758-721e-b927-619bffde5c23&redirect_uri=https%3A%2F%2Fyaktube.yakhub.com.tr%2Fplugins%2Fpeertube-plugin-auth-yaknet%2Frouter%2Fauth-callback&response_type=code&scope=" class="yaknet-sso-login-btn" aria-label="YakNet Hesabınız ile Tek Tıkla Giriş Yapın">' +
'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/><polyline points="10 17 15 12 10 7"/><line x1="15" y1="12" x2="3" y2="12"/></svg>' +
'<span>YakNet ile Giriş Yap</span>' +
'</a>' +
'<div style="margin-top: 14px; font-size: 13px; color: #94a3b8;">' +
'YakNet hesabınız yok mu? <a href="https://auth.yakhub.com.tr/register" target="_blank" rel="noopener noreferrer" style="color: #ff8f37; font-weight: 600; text-decoration: underline;">YakNet Hesabı Oluştur</a>' +
'</div>' +
'<div style="margin-top: 14px; padding-top: 12px; border-top: 1px solid rgba(255,255,255,0.08); font-size: 11.5px; color: #64748b; line-height: 1.45;">' +
'Giriş yaparak <a href="/about/instance" target="_blank" style="color: #94a3b8; text-decoration: underline;">Kullanım Koşulları</a>\'nı ve <a href="/about/instance" target="_blank" style="color: #94a3b8; text-decoration: underline;">Gizlilik Politikası</a>\'nı kabul etmiş sayılırsınız.' +
'</div>' +
'<div class="yaktube-admin-login-link" style="margin-top: 14px;">' +
(isLocalLogin
? '<span>⚙️ Yönetici (Local Admin) Giriş Modu Aktif</span>'
: '<a href="#" id="yaktube-show-admin-login">⚙️ Sunucu Yöneticisi Girişi</a>') +
'</div>';
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 =
'<button style="background:linear-gradient(135deg, #ff8f37 0%, #ff5e3a 100%); color:#fff; border:none; padding:12px 24px; border-radius:10px; font-weight:700; cursor:pointer; font-size:15px;">🔑 Sunucu Yöneticisi Olarak Giriş Yap (Alt+A)</button>';
btn403.querySelector('button').addEventListener('click', openLocalAdminLoginModal);
notAuthorizedEl.appendChild(btn403);
}
injectYakNetSSO();
// Check auth status for login button rendering
var tokenVal = localStorage.getItem('access_token');
var userVal = localStorage.getItem('username');
var isAlreadyAuth = !!tokenVal && !!userVal;
if (!isAlreadyAuth) {
// Replace all login buttons labels and titles ONLY for guests
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
document
.querySelectorAll(
'.mobile-msg, [class*="mobile-msg"], button[title*="Bu mesajı kapat"], button[aria-label*="Bu mesajı kapat"]'
)
.forEach(function (m) {
m.remove();
});
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');
}
}
});
}
// 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 =
'<div class="yaktube-modal-card" style="max-width: 600px; background:#1e1e24; border-radius:18px; padding:24px; color:#fff; box-shadow:0 16px 40px rgba(0,0,0,0.8);">' +
'<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;">' +
'<h3 id="yaktube-shortcuts-title" style="margin:0; font-size:18px; color:#ff8f37;">⌨️ Klavye Kısayolları (Ekran Okuyucu Uyumlu)</h3>' +
'<button id="yaktube-shortcuts-close" class="yaktube-voice-modal-close-btn" aria-label="Kapat">✕</button>' +
'</div>' +
'<div style="font-size:12px; color:#aaa; margin-bottom:14px;">💡 <b>Ekran Okuyucu Notu:</b> Sanal imleç (Browse mode) açıkken kısayolları <b>Alt</b> tuşuyla birlikte kullanabilir veya video oynatıcı butonlarından faydalanabilirsiniz.</div>' +
'<table class="yaktube-shortcuts-table" style="width:100%; border-collapse:collapse; font-size:13px;">' +
'<thead><tr style="border-bottom:1px solid rgba(255,255,255,0.1); text-align:left; color:#aaa;"><th style="padding:6px 0;">Kısayol (Evrensel / Tek Tuş)</th><th style="padding:6px 0;">İşlev</th></tr></thead>' +
'<tbody>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + K</span> veya <span class="yaktube-kbd">Space</span></td><td>Oynat / Duraklat</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + J</span> veya <span class="yaktube-kbd">J</span></td><td>10 saniye geri sar</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + L</span> veya <span class="yaktube-kbd">L</span></td><td>10 saniye ileri sar</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + Yukarı / Aşağı</span></td><td>Sesi %5 artır / azalt</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + M</span> veya <span class="yaktube-kbd">M</span></td><td>Sesi Kapat / Aç (Mute)</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + T</span> veya <span class="yaktube-kbd">T</span></td><td>Sinema / Tiyatro Modu</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + I</span> veya <span class="yaktube-kbd">I</span></td><td>Hızlı Video Bilgisi & İstatistikler</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + C</span> veya <span class="yaktube-kbd">C</span></td><td>Altyazıları Aç / Kapat</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + U</span></td><td>⏱️ Uyku Zamanlayıcısı (Sleep Timer)</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + Q</span></td><td>📱 Telefonda Devam Et (QR Kod)</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + .</span> / <span class="yaktube-kbd">Alt + ,</span></td><td>Oynatma hızını artır / azalt</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + S</span> veya <span class="yaktube-kbd">/</span> (Shift+7)</td><td>Arama penceresini aç</td></tr>' +
'<tr style="border-bottom:1px solid rgba(255,255,255,0.05);"><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + V</span> veya <span class="yaktube-kbd">Shift + V</span></td><td>Sesli aramayı başlat</td></tr>' +
'<tr><td style="padding:8px 0;"><span class="yaktube-kbd">Alt + H</span> veya <span class="yaktube-kbd">F1</span> veya <span class="yaktube-kbd">?</span></td><td>Bu kılavuzu aç / kapat</td></tr>' +
'</tbody>' +
'</table>' +
'</div>';
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() {
// 1. Direct video tag in DOM
var v = document.querySelector('video');
if (v) return v;
// 2. Video inside iframes (e.g. embed player)
var iframes = document.querySelectorAll('iframe');
for (var i = 0; i < iframes.length; i++) {
try {
var doc = iframes[i].contentDocument || (iframes[i].contentWindow && iframes[i].contentWindow.document);
if (doc) {
var iv = doc.querySelector('video');
if (iv) return iv;
}
} catch (e) {}
}
return null;
}
window.getVideoElement = getVideoElement;
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 =
'<div class="yaktube-info-toast-title">📊 Video Bilgisi & Durum</div>' +
'<div class="yaktube-info-toast-row"><span>Başlık:</span> <b>' +
(title.length > 28 ? title.substring(0, 28) + '...' : title) +
'</b></div>' +
'<div class="yaktube-info-toast-row"><span>Konum / Süre:</span> <b>' +
curTime +
' / ' +
totalTime +
'</b></div>' +
'<div class="yaktube-info-toast-row"><span>Kalan Süre:</span> <b>' +
remaining +
'</b></div>' +
'<div class="yaktube-info-toast-row"><span>Çözünürlük:</span> <b>' +
quality +
'</b></div>' +
'<div class="yaktube-info-toast-row"><span>Hız & Ses:</span> <b>' +
speed +
' | ' +
volume +
'</b></div>';
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ü.');
}
}
// ==========================================================================
// YAKTUBE QUICK TOAST HELPER (v1.5.0)
// ==========================================================================
function showQuickToast(message, icon) {
var existing = document.getElementById('yaktube-quick-toast');
if (existing) existing.remove();
var toast = document.createElement('div');
toast.id = 'yaktube-quick-toast';
toast.className = 'yaktube-quick-toast';
toast.style.cssText =
'position:fixed!important;top:80px!important;left:50%!important;transform:translateX(-50%)!important;' +
'background:rgba(24,24,31,0.92)!important;border:1px solid #ff8f37!important;color:#fff!important;' +
'padding:10px 22px!important;border-radius:30px!important;font-size:14px!important;font-weight:700!important;' +
'box-shadow:0 10px 30px rgba(0,0,0,0.8)!important;z-index:999999!important;pointer-events:none!important;' +
'backdrop-filter:blur(8px)!important;display:flex!important;align-items:center!important;gap:8px!important;';
toast.innerHTML = (icon ? '<span>' + icon + '</span> ' : '') + '<span>' + message + '</span>';
document.body.appendChild(toast);
setTimeout(function () {
if (toast && toast.parentNode) {
toast.style.opacity = '0';
toast.style.transition = 'opacity 0.3s ease-out';
setTimeout(function () {
if (toast.parentNode) toast.parentNode.removeChild(toast);
}, 300);
}
}, 2200);
}
// ==========================================================================
// YAKTUBE SLEEP TIMER ENGINE (v1.5.0)
// ==========================================================================
var sleepTimer = {
countdownInterval: null,
targetTime: 0,
mode: null,
originalVolume: 1
};
function clearSleepTimer() {
if (sleepTimer.countdownInterval) clearInterval(sleepTimer.countdownInterval);
sleepTimer.countdownInterval = null;
sleepTimer.targetTime = 0;
sleepTimer.mode = null;
var badge = document.getElementById('yaktube-sleep-timer-badge');
if (badge) badge.remove();
var endListenerVideo = getVideoElement();
if (endListenerVideo && endListenerVideo._sleepEndListener) {
endListenerVideo.removeEventListener('ended', endListenerVideo._sleepEndListener);
endListenerVideo._sleepEndListener = null;
}
}
function setSleepTimer(modeMinutes) {
clearSleepTimer();
var video = getVideoElement();
if (!video) {
announce('Aktif video bulunamadı.');
return;
}
sleepTimer.mode = modeMinutes;
sleepTimer.originalVolume = video.volume || 1;
if (modeMinutes === 'end') {
announce('Uyku zamanlayıcısı ayarlandı: Bu video bitince durdurulacak.', true);
showQuickToast('⏱️ Uyku: Video Bitince');
video._sleepEndListener = function () {
video.pause();
announce('Uyku zamanlayıcısı: Video bitti, oynatma durduruldu.', true);
clearSleepTimer();
};
video.addEventListener('ended', video._sleepEndListener, { once: true });
updateSleepTimerBadge('Video Bitince');
return;
}
var mins = parseInt(modeMinutes, 10);
if (isNaN(mins) || mins <= 0) return;
sleepTimer.targetTime = Date.now() + mins * 60 * 1000;
announce('Uyku zamanlayıcısı ' + mins + ' dakikaya ayarlandı.', true);
showQuickToast('⏱️ Uyku: ' + mins + ' Dakika');
function updateTick() {
var remainingMs = Math.max(0, sleepTimer.targetTime - Date.now());
var remainingSec = Math.floor(remainingMs / 1000);
var m = Math.floor(remainingSec / 60);
var s = remainingSec % 60;
var display = (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
updateSleepTimerBadge(display);
// Gentle volume fadeout in last 15 seconds
if (remainingSec <= 15 && remainingSec > 0 && video && !video.paused) {
var stepRatio = remainingSec / 15;
video.volume = Math.max(0, sleepTimer.originalVolume * stepRatio);
}
if (remainingMs <= 0) {
clearSleepTimer();
if (video) {
video.pause();
video.volume = sleepTimer.originalVolume;
}
announce('Uyku zamanlayıcısı süresi doldu, video durduruldu.', true);
showQuickToast('⏱️ Uyku: Oynatma Durduruldu');
}
}
updateTick();
sleepTimer.countdownInterval = setInterval(updateTick, 1000);
}
function updateSleepTimerBadge(text) {
var bar = document.getElementById('yaktube-player-action-bar');
if (!bar) return;
var badge = document.getElementById('yaktube-sleep-timer-badge');
if (!badge) {
badge = document.createElement('span');
badge.id = 'yaktube-sleep-timer-badge';
badge.className = 'yaktube-timer-badge';
var sleepBtn = document.getElementById('yaktube-bar-sleep-btn');
if (sleepBtn && sleepBtn.nextSibling) {
bar.insertBefore(badge, sleepBtn.nextSibling);
} else {
bar.appendChild(badge);
}
}
badge.textContent = '⏱️ ' + text;
}
function openSleepTimerModal() {
var existing = document.getElementById('yaktube-sleep-modal');
if (existing) existing.remove();
var backdrop = document.createElement('div');
backdrop.id = 'yaktube-sleep-modal';
backdrop.className = 'yaktube-feature-modal-backdrop';
backdrop.setAttribute('role', 'dialog');
backdrop.setAttribute('aria-modal', 'true');
backdrop.setAttribute('aria-label', 'Uyku Zamanlayıcısı');
var card = document.createElement('div');
card.className = 'yaktube-feature-modal-card';
var header = document.createElement('div');
header.className = 'yaktube-feature-modal-header';
header.innerHTML =
'<h3 class="yaktube-feature-modal-title"><span>⏱️</span> Uyku Zamanlayıcısı</h3>' +
'<button class="yaktube-feature-modal-close" aria-label="Kapat (Escape)">&times;</button>';
var body = document.createElement('div');
body.className = 'yaktube-feature-modal-body';
body.innerHTML =
'<p class="yaktube-qr-desc">Videonun ne zaman durdurulacağını seçin. Süre bittiğinde ses yavaşça kısılarak oynatma durdurulur.</p>' +
'<div class="yaktube-timer-options">' +
'<button class="yaktube-timer-btn' +
(sleepTimer.mode === '15' ? ' active' : '') +
'" data-mins="15">15 Dakika</button>' +
'<button class="yaktube-timer-btn' +
(sleepTimer.mode === '30' ? ' active' : '') +
'" data-mins="30">30 Dakika</button>' +
'<button class="yaktube-timer-btn' +
(sleepTimer.mode === '45' ? ' active' : '') +
'" data-mins="45">45 Dakika</button>' +
'<button class="yaktube-timer-btn' +
(sleepTimer.mode === '60' ? ' active' : '') +
'" data-mins="60">60 Dakika</button>' +
'</div>' +
'<button class="yaktube-timer-btn' +
(sleepTimer.mode === 'end' ? ' active' : '') +
'" data-mins="end" style="width:100%;">🎬 Bu Video Bitince</button>' +
(sleepTimer.targetTime || sleepTimer.mode
? '<button id="yaktube-timer-cancel-btn" class="yaktube-timer-btn" style="background:#ef4444!important;color:#fff!important;border-color:#ef4444!important;">❌ Zamanlayıcıyı İptal Et</button>'
: '');
card.appendChild(header);
card.appendChild(body);
backdrop.appendChild(card);
document.body.appendChild(backdrop);
function closeModal() {
backdrop.remove();
announce('Uyku zamanlayıcısı penceresi kapatıldı.');
}
header.querySelector('.yaktube-feature-modal-close').addEventListener('click', closeModal);
backdrop.addEventListener('click', function (e) {
if (e.target === backdrop) closeModal();
});
body.querySelectorAll('.yaktube-timer-options .yaktube-timer-btn, button[data-mins="end"]').forEach(function (b) {
b.addEventListener('click', function () {
var mins = this.getAttribute('data-mins');
setSleepTimer(mins);
closeModal();
});
});
var cancelBtn = body.querySelector('#yaktube-timer-cancel-btn');
if (cancelBtn) {
cancelBtn.addEventListener('click', function () {
clearSleepTimer();
announce('Uyku zamanlayıcısı iptal edildi.', true);
showQuickToast('⏱️ Zamanlayıcı İptal Edildi');
closeModal();
});
}
var firstBtn = body.querySelector('.yaktube-timer-btn');
if (firstBtn) firstBtn.focus();
announce('Uyku zamanlayıcısı penceresi açıldı. Seçeneklerden birini seçin.');
}
// ==========================================================================
// YAKTUBE PHONE TRANSFER / QR CODE ENGINE (v1.5.0)
// ==========================================================================
function openPhoneTransferModal() {
var video = getVideoElement();
var currentSec = video ? Math.floor(video.currentTime || 0) : 0;
var currentUrl = new URL(window.location.href);
if (currentSec > 0) {
currentUrl.searchParams.set('start', currentSec + 's');
}
var targetUrl = currentUrl.toString();
var existing = document.getElementById('yaktube-qr-modal');
if (existing) existing.remove();
var backdrop = document.createElement('div');
backdrop.id = 'yaktube-qr-modal';
backdrop.className = 'yaktube-feature-modal-backdrop';
backdrop.setAttribute('role', 'dialog');
backdrop.setAttribute('aria-modal', 'true');
backdrop.setAttribute('aria-label', 'Telefonda Devam Et');
var card = document.createElement('div');
card.className = 'yaktube-feature-modal-card';
var header = document.createElement('div');
header.className = 'yaktube-feature-modal-header';
header.innerHTML =
'<h3 class="yaktube-feature-modal-title"><span>📱</span> Telefonda Devam Et</h3>' +
'<button class="yaktube-feature-modal-close" aria-label="Kapat (Escape)">&times;</button>';
var formattedTime = formatClockTime(currentSec);
var qrImageUrl =
'https://api.qrserver.com/v1/create-qr-code/?size=220x220&margin=8&data=' + encodeURIComponent(targetUrl);
var body = document.createElement('div');
body.className = 'yaktube-feature-modal-body';
body.innerHTML =
'<p class="yaktube-qr-desc">Telefonunuzun kamerasıyla QR kodu okutarak videoyu kaldığınız yerden (<strong>' +
formattedTime +
'</strong>) izlemeye devam edebilirsiniz.</p>' +
'<div class="yaktube-qr-wrapper">' +
'<img src="' +
qrImageUrl +
'" alt="Video Bağlantısı QR Kodu" width="190" height="190" style="display:block;border-radius:8px;" />' +
'</div>' +
'<button id="yaktube-qr-copy-action-btn" class="yaktube-qr-copy-btn">📋 Bağlantıyı Kopyala</button>';
card.appendChild(header);
card.appendChild(body);
backdrop.appendChild(card);
document.body.appendChild(backdrop);
function closeModal() {
backdrop.remove();
announce('Telefonda devam et penceresi kapatıldı.');
}
header.querySelector('.yaktube-feature-modal-close').addEventListener('click', closeModal);
backdrop.addEventListener('click', function (e) {
if (e.target === backdrop) closeModal();
});
var copyBtn = body.querySelector('#yaktube-qr-copy-action-btn');
copyBtn.addEventListener('click', function () {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard
.writeText(targetUrl)
.then(function () {
copyBtn.textContent = '✅ Bağlantı Kopyalandı!';
announce('Bağlantı panoya kopyalandı: ' + targetUrl);
setTimeout(function () {
copyBtn.textContent = '📋 Bağlantıyı Kopyala';
}, 2500);
})
.catch(function () {
fallbackCopy(targetUrl, copyBtn);
});
} else {
fallbackCopy(targetUrl, copyBtn);
}
});
function fallbackCopy(text, btn) {
var tempInput = document.createElement('input');
tempInput.value = text;
document.body.appendChild(tempInput);
tempInput.select();
try {
document.execCommand('copy');
btn.textContent = '✅ Bağlantı Kopyalandı!';
announce('Bağlantı panoya kopyalandı.');
setTimeout(function () {
btn.textContent = '📋 Bağlantıyı Kopyala';
}, 2500);
} catch (e) {
btn.textContent = '❌ Kopyalanamadı';
}
document.body.removeChild(tempInput);
}
copyBtn.focus();
announce('Telefonda devam et penceresi açıldı. QR kodu okutabilir veya bağlantıyı kopyalayabilirsiniz.');
}
// ==========================================================================
// YAKTUBE DOUBLE-TAP TOUCH SEEK ENGINE (v1.5.0)
// ==========================================================================
function showDoubleTapRipple(container, isLeft) {
var className = isLeft ? 'yaktube-doubletap-left' : 'yaktube-doubletap-right';
var existing = container.querySelector('.yaktube-doubletap-overlay.' + className);
if (!existing) {
existing = document.createElement('div');
existing.className = 'yaktube-doubletap-overlay ' + className;
existing.innerHTML =
'<div class="yaktube-ripple-content">' + (isLeft ? '⏪ 10sn Geri' : '10sn İleri ⏩') + '</div>';
container.appendChild(existing);
}
existing.classList.add('yaktube-ripple-active');
clearTimeout(existing._rippleTimer);
existing._rippleTimer = setTimeout(function () {
existing.classList.remove('yaktube-ripple-active');
}, 650);
}
function initTouchSeekGesture(video) {
if (!video) return;
var playerContainer =
video.closest('.video-js, .player-container, .player, my-video-watch') || video.parentElement;
if (!playerContainer || playerContainer.hasAttribute('data-yaktube-touch-seek-hooked')) return;
playerContainer.setAttribute('data-yaktube-touch-seek-hooked', 'true');
var lastTapTime = 0;
var lastTapX = 0;
var tapTimeout = null;
playerContainer.addEventListener(
'touchend',
function (e) {
if (e.changedTouches && e.changedTouches.length === 1) {
var touch = e.changedTouches[0];
var rect = playerContainer.getBoundingClientRect();
var tapX = touch.clientX - rect.left;
var tapY = touch.clientY - rect.top;
var now = Date.now();
if (tapY > rect.height - 50) return;
if (now - lastTapTime < 320 && Math.abs(tapX - lastTapX) < 60) {
e.preventDefault();
clearTimeout(tapTimeout);
lastTapTime = 0;
var isLeft = tapX < rect.width * 0.45;
var isRight = tapX > rect.width * 0.55;
if (isLeft) {
video.currentTime = Math.max(0, video.currentTime - 10);
showDoubleTapRipple(playerContainer, true);
announce('10 saniye geri sarıldı. Konum: ' + formatTime(video.currentTime));
} else if (isRight) {
video.currentTime = Math.min(video.duration || 999999, video.currentTime + 10);
showDoubleTapRipple(playerContainer, false);
announce('10 saniye ileri sarıldı. Konum: ' + formatTime(video.currentTime));
}
} else {
lastTapTime = now;
lastTapX = tapX;
tapTimeout = setTimeout(function () {
lastTapTime = 0;
}, 330);
}
}
},
{ passive: false }
);
}
// ==========================================================================
// YAKTUBE VOICE ASSISTANT COMMANDS HANDLER (v1.5.0)
// ==========================================================================
function handleVoiceAssistantCommand(rawTranscript) {
if (!rawTranscript) return false;
var text = rawTranscript.toLowerCase().trim();
var video = getVideoElement();
if (text === 'durdur' || text === 'duraklat' || text === 'videoyu durdur' || text === 'pause') {
if (video) video.pause();
announce('Video duraklatıldı.', true);
showQuickToast('⏸️ Video Duraklatıldı');
stopVoiceInsideModal();
closeFullSearchModal();
return true;
}
if (text === 'oynat' || text === 'başlat' || text === 'devam et' || text === 'videoyu oynat' || text === 'play') {
if (video) video.play();
announce('Video oynatılıyor.', true);
showQuickToast('▶️ Video Oynatılıyor');
stopVoiceInsideModal();
closeFullSearchModal();
return true;
}
if (text === 'tam ekran' || text === 'büyük ekran' || text === 'tam ekran yap') {
var playerWrap =
document.querySelector('.player-container, .video-player-container, my-video-watch .player') || video;
if (playerWrap) {
if (!document.fullscreenElement) {
if (playerWrap.requestFullscreen) playerWrap.requestFullscreen();
announce('Tam ekran moduna geçildi.', true);
showQuickToast('⛶ Tam Ekran');
} else {
if (document.exitFullscreen) document.exitFullscreen();
announce('Tam ekrandan çıkıldı.', true);
showQuickToast('⛶ Normal Ekran');
}
}
stopVoiceInsideModal();
closeFullSearchModal();
return true;
}
if (text === 'sesi aç' || text === 'sesi yükselt' || text === 'sesi arttır' || text === 'ses aç') {
if (video) {
video.volume = Math.min(1, video.volume + 0.2);
var pct = Math.round(video.volume * 100);
announce('Ses seviyesi artırıldı: %' + pct, true);
showQuickToast('🔊 Ses: %' + pct);
}
stopVoiceInsideModal();
closeFullSearchModal();
return true;
}
if (text === 'sesi kıs' || text === 'sesi azalt' || text === 'ses kıs') {
if (video) {
video.volume = Math.max(0, video.volume - 0.2);
var pct = Math.round(video.volume * 100);
announce('Ses seviyesi azaltıldı: %' + pct, true);
showQuickToast('🔉 Ses: %' + pct);
}
stopVoiceInsideModal();
closeFullSearchModal();
return true;
}
if (text === 'sessiz' || text === 'sustur' || text === 'sesi kapat' || text === 'mute') {
if (video) {
video.muted = !video.muted;
announce(video.muted ? 'Ses kapatıldı.' : 'Ses açıldı.', true);
showQuickToast(video.muted ? '🔇 Sessiz' : '🔊 Ses Açık');
}
stopVoiceInsideModal();
closeFullSearchModal();
return true;
}
if (text === 'ileri sar' || text === '10 saniye ileri' || text === 'ileri al' || text === 'atla') {
if (video) {
video.currentTime = Math.min(video.duration || 999999, video.currentTime + 10);
announce('10 saniye ileri sarıldı.', true);
showQuickToast('⏩ +10sn İleri');
}
stopVoiceInsideModal();
closeFullSearchModal();
return true;
}
if (text === 'geri sar' || text === '10 saniye geri' || text === 'geri al') {
if (video) {
video.currentTime = Math.max(0, video.currentTime - 10);
announce('10 saniye geri sarıldı.', true);
showQuickToast('⏪ -10sn Geri');
}
stopVoiceInsideModal();
closeFullSearchModal();
return true;
}
if (text.includes('uyku') || text.includes('zamanlayıcı')) {
stopVoiceInsideModal();
closeFullSearchModal();
openSleepTimerModal();
return true;
}
if (text === 'kapat' || text === 'çık' || text === 'iptal') {
stopVoiceInsideModal();
closeFullSearchModal();
announce('Arama penceresi kapatıldı.');
return true;
}
return false;
}
// 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 =
'<span class="yaktube-player-bar-label">⚡ Oynatıcı Kontrolleri:</span>' +
'<button id="yaktube-bar-rewind-btn" class="yaktube-player-action-btn" aria-label="10 Saniye Geri Sar (Alt+J)">⏪ 10sn Geri</button>' +
'<button id="yaktube-bar-play-btn" class="yaktube-player-action-btn" aria-label="Oynat veya Duraklat (Alt+K)">⏯️ Oynat/Duraklat</button>' +
'<button id="yaktube-bar-forward-btn" class="yaktube-player-action-btn" aria-label="10 Saniye İleri Sar (Alt+L)">⏩ 10sn İleri</button>' +
'<button id="yaktube-bar-speed-btn" class="yaktube-player-action-btn" aria-label="Oynatma Hızını Değiştir (Alt+.)">⚡ Hız</button>' +
'<button id="yaktube-bar-sleep-btn" class="yaktube-player-action-btn" aria-label="Uyku Zamanlayıcısı (Alt+U)">⏱️ Uyku</button>' +
'<button id="yaktube-bar-qr-btn" class="yaktube-player-action-btn" aria-label="Telefonda Devam Et / QR Kod (Alt+Q)">📱 Telefonda Aç</button>' +
'<button id="yaktube-bar-theater-btn" class="yaktube-player-action-btn" aria-label="Sinema Modunu Aç/Kapat (Alt+T)">🎭 Sinema</button>' +
'<button id="yaktube-bar-info-btn" class="yaktube-player-action-btn" aria-label="Video Bilgisi ve İstatistikleri (Alt+I)">📊 Bilgi</button>' +
'<button id="yaktube-bar-help-btn" class="yaktube-player-action-btn" aria-label="Kısayol Kılavuzu (Alt+H veya F1)">⌨️ Kısayollar</button>';
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);
});
var sleepBtn = bar.querySelector('#yaktube-bar-sleep-btn');
if (sleepBtn) {
sleepBtn.addEventListener('click', function () {
openSleepTimerModal();
});
}
var qrBtn = bar.querySelector('#yaktube-bar-qr-btn');
if (qrBtn) {
qrBtn.addEventListener('click', function () {
openPhoneTransferModal();
});
}
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 sleepModal = document.getElementById('yaktube-sleep-modal');
if (sleepModal) {
sleepModal.remove();
announce('Uyku zamanlayıcısı penceresi kapatıldı.');
return;
}
var qrModal = document.getElementById('yaktube-qr-modal');
if (qrModal) {
qrModal.remove();
announce('Telefonda devam et penceresi kapatıldı.');
return;
}
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;
}
// Sleep Timer: Alt+U
if (e.altKey && (e.key === 'u' || e.key === 'U') && !isInputFocused()) {
e.preventDefault();
openSleepTimerModal();
return;
}
// Phone QR Transfer: Alt+Q
if (e.altKey && (e.key === 'q' || e.key === 'Q') && !isInputFocused()) {
e.preventDefault();
openPhoneTransferModal();
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.');
} 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 lastSearchTarget = null;
var isSearching = false;
function escapeHtml(str) {
if (!str) return '';
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function formatClockTime(seconds) {
if (isNaN(seconds) || seconds < 0) return '00:00';
var hrs = Math.floor(seconds / 3600);
var mins = Math.floor((seconds % 3600) / 60);
var secs = Math.floor(seconds % 60);
var sSecs = (secs < 10 ? '0' : '') + secs;
if (hrs > 0) {
var sMins = (mins < 10 ? '0' : '') + mins;
return hrs + ':' + sMins + ':' + sSecs;
}
return (mins < 10 ? '0' : '') + mins + ':' + sSecs;
}
function switchSearchTarget(target) {
var radio = document.getElementById(target === 'local' ? 'searchTargetLocal' : 'searchTargetSearchIndex');
if (radio) {
radio.checked = true;
radio.dispatchEvent(new Event('change', { bubbles: true }));
radio.dispatchEvent(new Event('input', { bubbles: true }));
var form = radio.closest('form');
var submitBtn = form ? form.querySelector('.submit-button button') : null;
if (submitBtn) {
submitBtn.click();
return;
}
}
var url = new URL(window.location.href);
url.searchParams.set('searchTarget', target);
window.location.href = url.toString();
}
function ensureFediverseBridge(query, currentSearchTarget) {
var bridge = document.getElementById('yaktube-fediverse-bridge');
if (!bridge) {
bridge = document.createElement('div');
bridge.id = 'yaktube-fediverse-bridge';
bridge.className = 'yaktube-fediverse-bridge';
bridge.setAttribute('role', 'region');
bridge.setAttribute('aria-label', 'Fediverse Video Arama Ağı ve Kapsam Seçimi');
var searchResult = document.querySelector('.search-result, my-search .search-result');
var ondemandContainer = document.getElementById('yaktube-ondemand-container');
if (searchResult && searchResult.parentNode) {
searchResult.parentNode.insertBefore(bridge, searchResult);
} else if (ondemandContainer && ondemandContainer.parentNode) {
ondemandContainer.parentNode.insertBefore(bridge, ondemandContainer.nextSibling);
} else {
var mainCol = document.querySelector('my-search, main, .main-col') || document.body;
mainCol.appendChild(bridge);
}
}
var isFediverse = currentSearchTarget === 'search-index';
bridge.innerHTML =
'<div class="yaktube-fediverse-bridge-content">' +
'<div class="yaktube-fediverse-bridge-left">' +
'<div class="yaktube-fediverse-bridge-badge-wrap">' +
'<span class="yaktube-fediverse-badge">🌐 Fediverse Ağı</span>' +
'<span class="yaktube-fediverse-status">' +
(isFediverse
? 'SepiaSearch Küresel Fediverse Arama Aktif'
: escapeHtml(getInstanceName()) + ' Yerel Arama Aktif') +
'</span>' +
'</div>' +
'<h2 class="yaktube-fediverse-title">🌐 Fediverse & Küresel Ağ Arama Sonuçları</h2>' +
'<p class="yaktube-fediverse-sub">Merkeziyetsiz federe ağdaki (SepiaSearch ve bağımsız PeerTube sunucuları) video sonuçları aşağıda listelenmektedir.</p>' +
'</div>' +
'<div class="yaktube-scope-selector" role="tablist" aria-label="Arama Kapsamı Seçimi">' +
'<button type="button" id="yaktube-scope-global-btn" class="yaktube-scope-btn ' +
(isFediverse ? 'active' : '') +
'" role="tab" aria-selected="' +
(isFediverse ? 'true' : 'false') +
'" aria-label="Tüm Fediverse ve SepiaSearch ağındaki videoları ara" title="Tüm Fediverse genelinde ara">' +
'🌐 Tüm Fediverse (SepiaSearch)' +
'</button>' +
'<button type="button" id="yaktube-scope-local-btn" class="yaktube-scope-btn ' +
(!isFediverse ? 'active' : '') +
'" role="tab" aria-selected="' +
(!isFediverse ? 'true' : 'false') +
'" aria-label="Sadece ' +
escapeHtml(getInstanceName()) +
' yerel sunucusundaki videoları ara" title="Sadece ' +
escapeHtml(getInstanceName()) +
' yerel kütüphanesinde ara">' +
'🏠 Sadece ' +
escapeHtml(getInstanceName()) +
' (Yerel)' +
'</button>' +
'</div>' +
'</div>';
var globalBtn = document.getElementById('yaktube-scope-global-btn');
if (globalBtn) {
globalBtn.addEventListener('click', function () {
if (currentSearchTarget !== 'search-index') {
switchSearchTarget('search-index');
}
});
}
var localBtn = document.getElementById('yaktube-scope-local-btn');
if (localBtn) {
localBtn.addEventListener('click', function () {
if (currentSearchTarget !== 'local') {
switchSearchTarget('local');
}
});
}
}
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 =
'<div class="yaktube-modal-card">' +
'<div class="yaktube-spinner" aria-hidden="true"></div>' +
'<h3 class="yaktube-modal-title" id="yaktube-modal-title" style="margin-top:0;">' +
(title || "YakTube'a Aktarılıyor...") +
'</h3>' +
'<p class="yaktube-modal-desc" id="yaktube-modal-desc">' +
(desc || 'Seçtiğiniz video D: diskinize aktarılıyor...') +
'</p>' +
'</div>';
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 =
'<div style="background: #0f172a; border-radius: 12px; padding: 20px; margin-bottom: 24px; box-shadow: 0 10px 30px rgba(0,0,0,0.6); border: 1px solid rgba(255,255,255,0.08); outline: none;">' +
'<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; flex-wrap: wrap; gap: 10px;">' +
'<div style="display: flex; align-items: center; gap: 10px;">' +
'<span style="background: #dc2626; color: #fff; font-size: 12px; padding: 4px 10px; border-radius: 6px; font-weight: 700; display: inline-flex; align-items: center; gap: 6px;">\ud83d\udd34 CANLI YAYIN</span>' +
'<h1 id="yaktube-live-title" tabindex="0" style="margin: 0; font-size: 1.25rem; font-weight: 700; color: #fff; line-height: 1.3; outline: none;">' +
videoTitle +
'</h1>' +
'</div>' +
'<button id="yaktube-close-live-theater" style="background: #334155; color: #fff; border: none; padding: 8px 16px; border-radius: 8px; font-weight: 600; cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 6px;" aria-label="Canl\u0131 Yay\u0131n \u0130zleme Alan\u0131n\u0131 Kapat (Escape)">' +
'\u2715 Yay\u0131n\u0131 Kapat (Esc)' +
'</button>' +
'</div>' +
'<div style="position: relative; padding-top: 56.25%; width: 100%; background: #000; border-radius: 10px; overflow: hidden; box-shadow: 0 8px 24px rgba(0,0,0,0.7);">' +
'<iframe id="yaktube-live-iframe" src="https://www.youtube-nocookie.com/embed/' +
videoId +
'?autoplay=1&rel=0&modestbranding=1&enablejsapi=1" title="' +
videoTitle.replace(/"/g, '&quot;') +
'" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>' +
'</div>' +
'<div style="margin-top: 14px; display: flex; justify-content: space-between; align-items: center; color: #94a3b8; font-size: 13px;">' +
'<div>\ud83d\udce1 YakTube Canl\u0131 Yay\u0131n Ak\u0131\u015f\u0131 \u2022 Reklams\u0131z ve Kesintisiz</div>' +
'<div style="color: #64748b;">ESC tu\u015funa basarak yay\u0131n\u0131 kapatabilirsiniz.</div>' +
'</div>' +
'<div id="yaktube-live-comments-panel" style="margin-top: 18px; background: #0b1120; border-radius: 10px; padding: 16px; border: 1px solid rgba(255,255,255,0.08);">' +
'<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px;">' +
'<h2 style="margin: 0; font-size: 15px; font-weight: 700; color: #fff; display: flex; align-items: center; gap: 8px;">' +
'\ud83d\udcac YouTube Topluluk Yorumlar\u0131' +
'</h2>' +
'<button id="yaktube-toggle-comments-btn" style="background: #ff8f37; color: #000; border: none; padding: 7px 16px; border-radius: 6px; font-weight: 700; cursor: pointer; font-size: 12px; display: inline-flex; align-items: center; gap: 6px;" aria-label="YouTube yorumlar\u0131n\u0131 y\u00fckle ve g\u00f6ster">' +
'\ud83d\udcac Yorumlar\u0131 G\u00f6ster (20 Yorum)' +
'</button>' +
'</div>' +
'<div id="yaktube-comments-list" style="display: none; margin-top: 14px; flex-direction: column; gap: 12px;"></div>' +
'<div id="yaktube-more-comments-wrap" style="text-align: center; margin-top: 16px; display: none;">' +
'<button id="yaktube-load-more-comments-btn" style="background: #1e293b; color: #ff8f37; border: 1px solid #ff8f37; padding: 8px 20px; border-radius: 8px; font-weight: 700; cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 6px;" aria-label="Daha fazla yorum y\u00fckle">' +
'\u2795 Daha Fazla Yorum G\u00f6ster (+20 Yorum)' +
'</button>' +
'</div>' +
'</div>' +
'</div>';
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 =
'<div style="width: 36px; height: 36px; border-radius: 50%; background: #334155; color: #ff8f37; font-weight: 700; display: flex; align-items: center; justify-content: center; font-size: 14px; flex-shrink: 0;">' +
avatarLetter +
'</div>';
var safeContent = c.content
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br>');
card.innerHTML =
avatarHtml +
'<div style="flex-grow: 1; min-width: 0;">' +
'<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px; flex-wrap: wrap;">' +
'<h3 class="yaktube-comment-author-heading" style="margin: 0; font-size: 13px; font-weight: 700; color: #f1f5f9; display: inline-block;">' +
c.author +
'</h3>' +
'<span style="color: #64748b; font-size: 11px;">' +
c.published +
'</span>' +
(c.likeCount && c.likeCount !== '0'
? '<span style="background: rgba(255,255,255,0.08); color: #94a3b8; font-size: 11px; padding: 2px 6px; border-radius: 4px; display: inline-flex; align-items: center; gap: 4px;">👍 ' +
c.likeCount +
'</span>'
: '') +
'</div>' +
'<div style="color: #cbd5e1; font-size: 13px; line-height: 1.45; word-break: break-word;">' +
safeContent +
'</div>' +
'</div>';
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 =
'<div style="color: #94a3b8; font-size: 13px; text-align: center; padding: 12px;">Bu videoda/yay\u0131nda hen\u00fcz yorum bulunmuyor.</div>';
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 = '📥 ' + escapeHtml(getInstanceName()) + "'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 = '📥 ' + escapeHtml(getInstanceName()) + "'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 = '📥 ' + escapeHtml(getInstanceName()) + "'a Aktar & İzle";
alert('İndirme hatası: ' + err.message);
announce('İndirme hatası: ' + err.message, true);
});
}
function checkAndInjectSearchResults() {
// Clean up stray 404 error mascot pages if on search
try {
var errPages = document.querySelectorAll('my-error-page');
errPages.forEach(function (ep) {
ep.remove();
});
} catch (e) {}
// Instant URL fixer: redirect legacy /search/videos to native /search
if (window.location.pathname === '/search/videos' || window.location.pathname.startsWith('/search/videos/')) {
window.location.replace('/search' + window.location.search);
return;
}
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();
var existingBridge = document.getElementById('yaktube-fediverse-bridge');
if (existingBridge) existingBridge.remove();
lastQuery = null;
lastSearchTarget = null;
return;
}
var query = searchParam.trim();
// Ensure Fediverse Global Search (SepiaSearch) is default target if not specified in URL
if (!url.searchParams.has('searchTarget')) {
url.searchParams.set('searchTarget', 'search-index');
window.location.replace(url.toString());
return;
}
var currentSearchTarget = url.searchParams.get('searchTarget') || 'search-index';
var queryKey = query + '___' + currentSearchTarget;
var lastQueryKey = lastQuery + '___' + lastSearchTarget;
if (queryKey === lastQueryKey) {
var existingBridge = document.getElementById('yaktube-fediverse-bridge');
if (!existingBridge && document.querySelector('my-search')) {
ensureFediverseBridge(query, currentSearchTarget);
}
return;
}
lastQuery = query;
lastSearchTarget = currentSearchTarget;
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ı YouTube ve ' + getInstanceName() + ' Arama Sonuçları');
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 =
'<div class="yaktube-header">' +
'<div class="yaktube-header-left">' +
'<span class="yaktube-badge">⚡ Canlı YouTube & Web Arama</span>' +
'<h2 class="yaktube-title-h3" style="margin:0; font-size:1.15rem;">"' +
escapeHtml(query) +
'" için Sonuçlar Taranıyor...</h2>' +
'</div>' +
'<button class="yaktube-shortcuts-btn" id="yaktube-open-shortcuts" aria-label="Klavye Kısayolları Kılavuzunu Aç">' +
'⌨️ Kısayollar (?)' +
'</button>' +
'</div>' +
'<div class="yaktube-loading">' +
'<div class="yaktube-spinner" aria-hidden="true"></div>' +
'<div style="margin-top: 10px;">YouTube ve ' +
escapeHtml(getInstanceName()) +
' yerel kütüphanesindeki eşleşmeler taranıyor...</div>' +
'</div>';
var shortcutsBtn = document.getElementById('yaktube-open-shortcuts');
if (shortcutsBtn) {
shortcutsBtn.addEventListener('click', toggleShortcutsModal);
}
ensureFediverseBridge(query, currentSearchTarget);
announce(query + ' için arama başlatıldı.');
isSearching = true;
if (window.__yaktube_search_abort_controller__) {
try {
window.__yaktube_search_abort_controller__.abort();
} catch (e) {}
}
var abortCtrl = typeof AbortController !== 'undefined' ? new AbortController() : null;
window.__yaktube_search_abort_controller__ = abortCtrl;
var fetchOpts = abortCtrl ? { signal: abortCtrl.signal } : {};
Promise.all([
fetch('/api-custom/youtube-search?q=' + encodeURIComponent(query), fetchOpts)
.then(function (res) {
return res.json();
})
.catch(function (err) {
if (err && err.name === 'AbortError') return { results: [] };
console.warn('[YakTube] YouTube search fetch error:', err);
return { results: [] };
}),
fetch('/api/v1/search/videos?search=' + encodeURIComponent(query) + '&isLocal=true&count=12', fetchOpts)
.then(function (res) {
return res.json();
})
.catch(function (err) {
if (err && err.name === 'AbortError') return { total: 0, data: [] };
console.warn('[YakTube] Local videos search fetch error:', err);
return { total: 0, data: [] };
})
])
.then(function (responses) {
var ytData = responses[0] || {};
var localData = responses[1] || {};
var rawYtResults = ytData.results || [];
var uniqueYtResults = [];
var seenIds = {};
rawYtResults.forEach(function (item) {
if (item && item.id && !seenIds[item.id] && item.title) {
seenIds[item.id] = true;
uniqueYtResults.push(item);
}
});
var ytCount = uniqueYtResults.length;
var localResults = localData.data || [];
var localTotal = typeof localData.total === 'number' ? localData.total : localResults.length;
if (ytCount === 0 && localResults.length === 0) {
container.innerHTML =
'<div class="yaktube-header">' +
'<div class="yaktube-header-left">' +
'<span class="yaktube-badge">⚡ Canlı YouTube & Web Arama</span>' +
'<h2 class="yaktube-title-h3" style="margin:0; font-size:1.15rem;">"' +
escapeHtml(query) +
'" için Sonuç Bulunamadı</h2>' +
'</div>' +
'<button class="yaktube-shortcuts-btn" id="yaktube-open-shortcuts" aria-label="Klavye Kısayolları Kılavuzunu Aç">' +
'⌨️ Kısayollar (?)' +
'</button>' +
'</div>';
announce(query + ' için YouTube veya yerel sunucuda eşleşen video bulunamadı.');
} else {
var visibleYtCount = Math.min(12, ytCount);
function renderAll() {
// 1. YouTube Section
var ytHeaderHtml = '';
var ytCardsHtml = '';
var ytLoadMoreHtml = '';
if (ytCount > 0) {
var countText = ytCount === 1 ? '1 Video Bulundu' : ytCount + ' Video Bulundu';
ytHeaderHtml =
'<div class="yaktube-header">' +
'<div class="yaktube-header-left">' +
'<span class="yaktube-badge">⚡ Canlı YouTube & Web Arama</span>' +
'<h2 class="yaktube-title-h3" style="margin:0; font-size:1.15rem;">"' +
escapeHtml(query) +
'" için ' +
countText +
'</h2>' +
'</div>' +
'<button class="yaktube-shortcuts-btn" id="yaktube-open-shortcuts" aria-label="Klavye Kısayolları Kılavuzunu Aç">' +
'⌨️ Kısayollar (?)' +
'</button>' +
'</div>';
var visibleResults = uniqueYtResults.slice(0, visibleYtCount);
visibleResults.forEach(function (item) {
var isLocal = !!item.isDownloaded;
var isLive = !!item.isLive;
var statusBadge = '';
if (isLive) {
statusBadge =
'<span class="yaktube-badge" style="background: #dc2626; color: #fff; margin-bottom: 6px; display: inline-block; font-weight: 700;">🔴 CANLI YAYIN</span>';
} else if (isLocal) {
statusBadge =
'<span class="yaktube-badge" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); margin-bottom: 6px; display: inline-block;">✅ ' +
escapeHtml(getInstanceName()) +
"'da Mevcut</span>";
}
var actionButton = '';
if (isLive) {
actionButton =
'<button class="yaktube-live-btn" data-url="' +
escapeHtml(item.url) +
'" data-title="' +
escapeHtml(item.title) +
'" style="background: linear-gradient(135deg, #ef4444 0%, #b91c1c 100%); color: #fff; border: none; padding: 8px 14px; border-radius: 6px; font-weight: 700; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;" aria-label="' +
escapeHtml(item.title) +
' canlı yayınını hemen izle">🔴 Canlı Yayını İzle</button>';
} else if (isLocal && item.localVideo && item.localVideo.url) {
actionButton =
'<a href="' +
escapeHtml(item.localVideo.url) +
'" class="yaktube-play-btn" aria-label="' +
escapeHtml(item.title) +
' videosunu ' +
escapeHtml(getInstanceName()) +
'\'dan hemen izle">▶️ ' +
escapeHtml(getInstanceName()) +
"'dan İzle</a>";
} else {
actionButton =
'<button class="yaktube-import-btn" data-url="' +
escapeHtml(item.url) +
'" data-title="' +
escapeHtml(item.title) +
'" aria-label="' +
escapeHtml(item.title) +
' videosunu ' +
escapeHtml(getInstanceName()) +
'\'a aktar ve izle">📥 ' +
escapeHtml(getInstanceName()) +
"'a Aktar & İzle</button>";
}
var durationText = isLive ? 'CANLI' : item.durationFormatted || formatClockTime(item.duration);
var cardLabel =
item.title +
(isLive ? ' (Canlı Yayın)' : isLocal ? ' (' + getInstanceName() + ' kütüphanenizde mevcut)' : '') +
', ' +
item.channel +
' kanalı, Süre: ' +
durationText;
ytCardsHtml +=
'<div class="yaktube-card" role="article" tabindex="0" aria-label="' +
escapeHtml(cardLabel) +
'">' +
'<div class="yaktube-thumb-wrap">' +
'<img class="yaktube-thumb-img" src="' +
escapeHtml(item.thumbnail) +
'" alt="' +
escapeHtml(item.title) +
'" loading="lazy" />' +
'<span class="yaktube-duration-badge" style="' +
(isLive ? 'background:#dc2626;font-weight:700;' : '') +
'" aria-label="Süre">' +
escapeHtml(durationText) +
'</span>' +
'</div>' +
'<div class="yaktube-info">' +
'<div>' +
statusBadge +
'<h3 class="yaktube-video-name" title="' +
escapeHtml(item.title) +
'" style="margin:0; font-size:13px; font-weight:600; line-height:1.35;">' +
escapeHtml(item.title) +
'</h3>' +
'<div class="yaktube-channel-name">' +
escapeHtml(item.channel) +
'</div>' +
'</div>' +
actionButton +
'</div>' +
'</div>';
});
if (visibleYtCount < ytCount) {
ytLoadMoreHtml =
'<div style="text-align: center; margin: 20px 0 10px 0;">' +
'<button id="yaktube-load-more-btn" class="yaktube-load-more-btn" style="background: #1e293b; color: #ff8f37; border: 1px solid #ff8f37; padding: 10px 24px; border-radius: 8px; font-weight: 700; font-size: 14px; cursor: pointer; display: inline-flex; align-items: center; gap: 8px; transition: all 0.2s ease; box-shadow: 0 4px 12px rgba(0,0,0,0.3);" aria-label="Daha fazla video yükle">' +
'➕ Daha Fazla Video Göster (' +
visibleYtCount +
' / ' +
ytCount +
')' +
'</button>' +
'</div>';
}
}
// 2. Local Videos Shelf
var localShelfHtml = '';
if (localResults.length > 0) {
var localCardsHtml = '';
localResults.forEach(function (lItem) {
var lThumb =
lItem.thumbnailPath ||
(lItem.thumbnails && lItem.thumbnails[0]
? lItem.thumbnails[0].fileUrl
: '/client/assets/images/default-thumbnail.jpg');
var lDuration = formatClockTime(lItem.duration);
var lChannel =
(lItem.channel && (lItem.channel.displayName || lItem.channel.name)) ||
(lItem.account && (lItem.account.displayName || lItem.account.name)) ||
getInstanceName();
var lWatchUrl = lItem.url || '/videos/watch/' + (lItem.shortUUID || lItem.uuid);
var lViews = (lItem.views || 0) + ' görüntüleme';
var lLabel =
lItem.name +
', ' +
lChannel +
', ' +
getInstanceName() +
' yerel sunucusunda mevcut, Süre: ' +
lDuration;
localCardsHtml +=
'<div class="yaktube-card yaktube-local-card" role="article" tabindex="0" aria-label="' +
escapeHtml(lLabel) +
'">' +
'<a href="' +
escapeHtml(lWatchUrl) +
'" style="text-decoration:none; color:inherit; display:flex; flex-direction:column; height:100%;">' +
'<div class="yaktube-thumb-wrap">' +
'<img class="yaktube-thumb-img" src="' +
escapeHtml(lThumb) +
'" alt="' +
escapeHtml(lItem.name) +
'" loading="lazy" onerror="this.src=\'/client/assets/images/default-thumbnail.jpg\'" />' +
'<span class="yaktube-duration-badge" aria-label="Süre">' +
escapeHtml(lDuration) +
'</span>' +
'</div>' +
'<div class="yaktube-info">' +
'<div>' +
'<span class="yaktube-badge" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); margin-bottom: 6px; display: inline-block;">✅ ' +
escapeHtml(getInstanceName()) +
"'da Mevcut</span>" +
'<h3 class="yaktube-video-name" title="' +
escapeHtml(lItem.name) +
'" style="margin:0; font-size:13px; font-weight:600; line-height:1.35;">' +
escapeHtml(lItem.name) +
'</h3>' +
'<div class="yaktube-channel-name">' +
escapeHtml(lChannel) +
' • ' +
escapeHtml(lViews) +
'</div>' +
'</div>' +
'<div class="yaktube-play-btn" style="margin-top:8px;">▶️ ' +
escapeHtml(getInstanceName()) +
"'dan Hemen İzle</div>" +
'</div>' +
'</a>' +
'</div>';
});
localShelfHtml =
'<div id="yaktube-local-shelf" class="yaktube-local-shelf" role="region" aria-label="' +
escapeHtml(getInstanceName()) +
' Yerel Kütüphanesi">' +
'<div class="yaktube-header" style="margin-top: 24px; border-bottom-color: rgba(16, 185, 129, 0.3);">' +
'<div class="yaktube-header-left">' +
'<span class="yaktube-badge" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%);">✅ Yerel Kütüphane</span>' +
'<h2 class="yaktube-title-h3" style="margin:0; font-size:1.15rem;">"' +
escapeHtml(query) +
'" için ' +
escapeHtml(getInstanceName()) +
' Sunucusunda ' +
localTotal +
' Video Bulundu</h2>' +
'</div>' +
'<span class="yaktube-local-shelf-sub">Doğrudan sunucumuzda barındırılan, yüksek hızlı ve anında izlenebilen videolar</span>' +
'</div>' +
'<div class="yaktube-grid">' +
localCardsHtml +
'</div>' +
'</div>';
}
container.innerHTML =
ytHeaderHtml +
(ytCount > 0 ? '<div class="yaktube-grid">' + ytCardsHtml + '</div>' : '') +
ytLoadMoreHtml +
localShelfHtml;
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 () {
visibleYtCount = Math.min(visibleYtCount + 6, ytCount);
renderAll();
announce(visibleYtCount + ' 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);
});
});
}
renderAll();
var announceMsg = '"' + query + '" için arama sonuçları listelendi: ';
if (localTotal > 0) {
announceMsg += localTotal + ' adet yerel ' + getInstanceName() + ' videosu, ';
}
if (ytCount > 0) {
announceMsg += ytCount + ' adet YouTube sonucu ve ';
}
announceMsg += 'Fediverse sonuçları hazır.';
announce(announceMsg);
}
})
.catch(function (err) {
console.error('[YakTube] Search fetch error:', err);
container.innerHTML =
'<div class="yaktube-header">' +
'<div class="yaktube-header-left">' +
'<span class="yaktube-badge">⚡ Canlı YouTube & Web Arama</span>' +
'<h2 class="yaktube-title-h3" style="margin:0; font-size:1.15rem;">Arama sırasında bir bağlantı sorunu oluştu.</h2>' +
'</div>' +
'</div>';
announce('Arama sırasında bir bağlantı sorunu oluştu.');
})
.finally(function () {
isSearching = false;
ensureFediverseBridge(query, currentSearchTarget);
});
}
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// 8. FULL YOUTUBE SEARCH & VOICE MODAL ENGINE (v1.3.3 - Guaranteed Close Fix)
// --------------------------------------------------------------------------
var activeVoiceRec = null;
var isVoiceListening = false;
// Dynamic Search History & Trending Helpers with YakNet Cloud Sync
function isUserLoggedIn() {
try {
var token = localStorage.getItem('access_token');
var username = localStorage.getItem('username');
return Boolean(token || username);
} catch (e) {
return false;
}
}
function isAccountSyncEnabled() {
try {
var userPref = localStorage.getItem('yaktube_sync_search_history');
if (userPref === 'false') return false;
return isUserLoggedIn();
} catch (e) {
return false;
}
}
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'];
if (!isUserLoggedIn()) {
try {
localStorage.setItem('yaktube_recent_searches', JSON.stringify(defaults));
} catch (e) {}
}
return defaults;
}
function callCloudSearchHistory(method, queryParams, body, onSuccess, onError) {
var token = localStorage.getItem('access_token');
var email = localStorage.getItem('email');
var username = localStorage.getItem('username');
var headers = {};
if (body) headers['Content-Type'] = 'application/json';
if (token) headers['Authorization'] = 'Bearer ' + token;
if (email) headers['X-User-Email'] = email;
if (username) headers['X-User-Username'] = username;
var authUrl = 'https://auth.yakhub.com.tr/api/yaktube/search-history' + (queryParams || '');
var bridgeUrl = '/api-custom/search-history' + (queryParams || '');
var options = {
method: method,
headers: headers,
credentials: 'include'
};
if (body) options.body = JSON.stringify(body);
fetch(authUrl, options)
.then(function (res) {
if (!res.ok) throw new Error('Auth server status ' + res.status);
return res.json();
})
.then(function (data) {
if (typeof onSuccess === 'function') onSuccess(data);
})
.catch(function () {
// Fallback to local PeerTube bridge proxy
fetch(bridgeUrl, options)
.then(function (res) {
return res.json();
})
.then(function (data) {
if (typeof onSuccess === 'function') onSuccess(data);
})
.catch(function (e) {
if (typeof onError === 'function') onError(e);
});
});
}
var isSyncingHistory = false;
function syncCloudSearchHistory(callback) {
if (!isAccountSyncEnabled() || isSyncingHistory) return;
isSyncingHistory = true;
callCloudSearchHistory(
'GET',
'',
null,
function (data) {
isSyncingHistory = false;
if (data && data.status === 'success' && Array.isArray(data.history)) {
if (data.history.length > 0) {
localStorage.setItem('yaktube_recent_searches', JSON.stringify(data.history));
}
if (typeof callback === 'function') callback();
}
},
function () {
isSyncingHistory = 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 > 12) list = list.slice(0, 12);
localStorage.setItem('yaktube_recent_searches', JSON.stringify(list));
} catch (e) {}
// If user is logged in and sync is active, sync with YakNet account
if (isAccountSyncEnabled()) {
callCloudSearchHistory('POST', '', { query: q, device_name: 'YakTube Web' });
}
}
function clearRecentSearches() {
try {
localStorage.removeItem('yaktube_recent_searches');
} catch (e) {}
if (isAccountSyncEnabled()) {
callCloudSearchHistory('DELETE', '', null);
}
renderDynamicSearchChips();
announce('Arama geçmişi temizlendi.');
}
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' }
];
// 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);
function renderDynamicSearchChips() {
var modal = document.getElementById('yaktube-full-search-modal');
if (!modal) return;
var container = modal.querySelector('#yaktube-modal-dynamic-chips-section');
if (!container) return;
var recent = getRecentSearches();
var loggedIn = isUserLoggedIn();
var syncEnabled = isAccountSyncEnabled();
var html = '';
// 1. Recent searches section
html +=
'<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; flex-wrap: wrap; gap: 8px;">' +
'<div class="yaktube-modal-section-title" style="margin-bottom: 0;">🕒 Son Aramalarınız</div>' +
'<div style="display: flex; align-items: center; gap: 8px;">';
if (loggedIn) {
var syncTitle = syncEnabled
? 'YakNet Bulut Eşitlemesi Aktif (Tüm cihazlarınızda senkronize edilir. Kapatmak için tıklayın.)'
: 'Eşitleme Kapalı (Aramalar sadece bu cihazda saklanır. Açmak için tıklayın.)';
var syncText = syncEnabled ? '☁️ Eşitleme: Açık' : '☁️ Eşitleme: Kapalı';
var syncBg = syncEnabled ? '#0f766e' : '#334155';
var syncColor = syncEnabled ? '#5eead4' : '#94a3b8';
html +=
'<button id="yaktube-sync-toggle-btn" style="background: ' +
syncBg +
'; border: 1px solid rgba(255,255,255,0.15); border-radius: 6px; padding: 2px 8px; color: ' +
syncColor +
'; font-size: 11px; font-weight: 600; cursor: pointer; transition: all 0.2s ease;" title="' +
syncTitle +
'" aria-label="' +
syncTitle +
'">' +
syncText +
'</button>';
} else {
html +=
'<span style="font-size: 11px; color: #94a3b8; background: #1e293b; border-radius: 6px; padding: 2px 8px;" title="Misafir modundasınız, aramalar sadece bu cihazda saklanır">🔒 Bu Cihazda</span>';
}
if (recent.length > 0) {
html +=
'<button id="yaktube-clear-recent-btn" style="background: transparent; border: none; color: #ff8f37; font-size: 12px; font-weight: 600; cursor: pointer; text-decoration: underline;" aria-label="Arama geçmişini temizle">Geçmişi Temizle</button>';
}
html += '</div></div>';
if (recent.length > 0) {
html += '<div class="yaktube-modal-chips-grid" style="margin-bottom: 18px;">';
recent.forEach(function (term) {
var safeTerm = term.replace(/"/g, '&quot;');
html +=
'<button class="yaktube-modal-chip yaktube-recent-chip" data-query="' +
safeTerm +
'" aria-label="Son arama: ' +
safeTerm +
'">🕒 ' +
safeTerm +
'</button>';
});
html += '</div>';
}
// 2. Popular & Trending searches section
html +=
'<div class="yaktube-modal-section-title">🔥 Hızlı ve Popüler Aramalar</div>' +
'<div class="yaktube-modal-chips-grid">';
popularTrendingQueries.forEach(function (item) {
var safeQ = item.q.replace(/"/g, '&quot;');
html +=
'<button class="yaktube-modal-chip" data-query="' +
safeQ +
'" aria-label="Popüler arama: ' +
safeQ +
'">' +
item.title +
'</button>';
});
html += '</div>';
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();
});
}
var syncToggleBtn = container.querySelector('#yaktube-sync-toggle-btn');
if (syncToggleBtn) {
syncToggleBtn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
var current = localStorage.getItem('yaktube_sync_search_history') !== 'false';
var next = !current;
localStorage.setItem('yaktube_sync_search_history', next ? 'true' : 'false');
if (next) {
announce('YakNet arama geçmişi eşitlemesi açıldı.');
syncCloudSearchHistory(renderDynamicSearchChips);
} else {
announce('Arama geçmişi eşitlemesi kapatıldı. Aramalar sadece bu cihazda saklanacak.');
}
renderDynamicSearchChips();
});
}
}
function createFullSearchModal() {
var existingModal = document.getElementById('yaktube-full-search-modal');
if (existingModal) return existingModal;
var modal = document.createElement('div');
modal.id = 'yaktube-full-search-modal';
modal.className = 'yaktube-full-search-modal';
modal.setAttribute('role', 'dialog');
modal.setAttribute('aria-modal', 'true');
modal.setAttribute('aria-labelledby', 'yaktube-modal-search-title');
modal.innerHTML =
'<div class="yaktube-modal-search-card">' +
'<div class="yaktube-modal-search-bar">' +
'<button id="yaktube-modal-back-btn" class="yaktube-modal-back-btn" aria-label="Aramadan Çık (Escape)" title="Aramadan Çık (Escape)">←</button>' +
'<div class="yaktube-modal-input-wrap">' +
'<input id="yaktube-modal-input" type="search" placeholder="Video veya kanal ara..." aria-label="Arama Metni">' +
'<button id="yaktube-modal-clear-btn" class="yaktube-modal-clear-btn" aria-label="Metni Temizle">✕</button>' +
'</div>' +
'<button id="yaktube-modal-submit-btn" class="yaktube-modal-search-action-btn" aria-label="Ara">🔍 Ara</button>' +
'<button id="yaktube-modal-voice-btn" class="yaktube-modal-voice-btn" aria-label="Sesle Ara (Shift+V)" title="Sesle Ara">🎙️</button>' +
'</div>' +
'<div class="yaktube-modal-body">' +
'<div id="yaktube-modal-voice-section" class="yaktube-modal-voice-section">' +
'<div id="yaktube-modal-voice-status" class="yaktube-modal-voice-status">🎙️ Dinleniyor...</div>' +
'<div id="yaktube-modal-voice-transcript" class="yaktube-modal-voice-transcript">Lütfen aramak istediğiniz videoyu söyleyin...</div>' +
'<button id="yaktube-modal-mic-pulse" class="yaktube-modal-mic-pulse-btn" aria-label="Mikrofonu durdur">🎙️</button>' +
'</div>' +
'<div id="yaktube-modal-dynamic-chips-section"></div>' +
'<div class="yaktube-modal-hint-footer">' +
'<span>💡 <b>Enter</b> ile ara, <b>Shift+V</b> ile sesli ara</span>' +
'<span><b>Escape</b> ile kapat</span>' +
'</div>' +
'</div>' +
'</div>';
document.body.appendChild(modal);
var inp = modal.querySelector('#yaktube-modal-input');
var clearBtn = modal.querySelector('#yaktube-modal-clear-btn');
var backBtn = modal.querySelector('#yaktube-modal-back-btn');
var submitBtn = modal.querySelector('#yaktube-modal-submit-btn');
var voiceBtn = modal.querySelector('#yaktube-modal-voice-btn');
var micPulseBtn = modal.querySelector('#yaktube-modal-mic-pulse');
// Input typing & clear
inp.addEventListener('input', function () {
if (inp.value && inp.value.trim().length > 0) {
clearBtn.style.display = 'inline-flex';
} else {
clearBtn.style.display = 'none';
}
});
clearBtn.addEventListener('click', function () {
inp.value = '';
clearBtn.style.display = 'none';
inp.focus();
announce('Arama metni temizlendi.');
});
// Close on Back button or Escape
backBtn.addEventListener('click', function (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?search=' + encodeURIComponent(q) + '&searchTarget=search-index';
}
}
window.__yaktube_execute_search__ = executeSearch;
renderDynamicSearchChips();
submitBtn.addEventListener('click', function () {
executeSearch();
});
inp.addEventListener('keydown', function (e) {
if (e.key === 'Enter') {
e.preventDefault();
executeSearch();
} else if (e.key === 'Escape') {
e.preventDefault();
closeFullSearchModal();
}
});
// Chips click
modal.querySelectorAll('.yaktube-modal-chip').forEach(function (chip) {
chip.addEventListener('click', function () {
var query = chip.getAttribute('data-query');
executeSearch(query);
});
});
// Voice button click
voiceBtn.addEventListener('click', function () {
toggleVoiceInsideModal();
});
micPulseBtn.addEventListener('click', function () {
toggleVoiceInsideModal();
});
return modal;
}
function openFullSearchModal(initialQuery, autoStartVoice) {
var modal = createFullSearchModal();
modal.classList.add('yaktube-modal-open');
modal.style.display = 'flex';
renderDynamicSearchChips();
syncCloudSearchHistory(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) {
if (handleVoiceAssistantCommand(transcript)) {
return;
}
announce('"' + transcript + '" için arama başlatılıyor...', true);
setTimeout(function () {
closeFullSearchModal();
window.location.href =
'/search?search=' + encodeURIComponent(transcript) + '&searchTarget=search-index';
}, 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 =
'<span class="yaktube-search-trigger-icon">🔍</span>' +
'<span class="yaktube-search-trigger-text">Ara...</span>' +
'<span class="yaktube-search-trigger-shortcut">/</span>';
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);
}
});
// Automatically trigger cloud search history pull on startup if logged in
try {
if (isAccountSyncEnabled()) {
syncCloudSearchHistory();
}
} catch (e) {}
// 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
? '<span class="yaktube-badge" style="background: linear-gradient(135deg, #10b981 0%, #059669 100%); margin-bottom: 4px; display: inline-block; font-size:10px; padding:2px 8px;">✅ ' +
getInstanceName() +
"'da İzle</span>"
: '<span class="yaktube-badge" style="background: linear-gradient(135deg, #ff8f37 0%, #ff5e3a 100%); margin-bottom: 4px; display: inline-block; font-size:10px; padding:2px 8px;">⚡ Canlı Keşif</span>';
var actionButton = isLocal
? '<a href="' +
item.localVideo.url +
'" class="yaktube-play-btn yaktube-related-play-link" style="font-size:11px; padding:6px 10px;" aria-label="' +
item.title.replace(/"/g, '&quot;') +
' videosunu ' +
escapeHtml(getInstanceName()) +
'\'dan hemen izle">▶️ Hemen İzle</a>'
: '<button class="yaktube-import-btn yaktube-related-import" data-url="' +
item.url +
'" data-title="' +
item.title.replace(/"/g, '&quot;') +
'" style="font-size:11px; padding:6px 10px;" aria-label="' +
item.title.replace(/"/g, '&quot;') +
' videosunu ' +
getInstanceName() +
'\'a indir ve izle">📥 İndir ve İzle</button>';
var cardLabel =
item.title +
(isLocal ? ' (' + getInstanceName() + ' kütüphanenizde mevcut)' : ' (Canlı YouTube keşfi)') +
', ' +
item.channel +
' kanalı, Süre: ' +
item.durationFormatted;
cardsHtml +=
'<div class="yaktube-card" role="article" tabindex="0" style="margin-bottom:10px;" aria-label="' +
cardLabel.replace(/"/g, '&quot;') +
'">' +
'<div class="yaktube-thumb-wrap">' +
'<img class="yaktube-thumb-img" src="' +
item.thumbnail +
'" alt="' +
item.title.replace(/"/g, '&quot;') +
'" loading="lazy" />' +
'<span class="yaktube-duration-badge" aria-label="Süre">' +
item.durationFormatted +
'</span>' +
'</div>' +
'<div class="yaktube-info">' +
'<div>' +
statusBadge +
'<h4 class="yaktube-video-name" title="' +
item.title +
'" style="margin:0; font-size:12px; font-weight:600; line-height:1.35;">' +
item.title +
'</h4>' +
'<div class="yaktube-channel-name" style="font-size:10px;">' +
item.channel +
'</div>' +
'</div>' +
actionButton +
'</div>' +
'</div>';
});
var loadMoreBtn = '';
if (count > 12) {
if (!relatedShowAll) {
loadMoreBtn =
'<button id="yaktube-related-toggle-more" class="yaktube-shortcuts-btn" style="width:100%; margin-top:12px; padding:10px; justify-content:center; font-weight:700; background:rgba(255,143,55,0.15); border-color:#ff8f37; color:#fff;" aria-label="Daha fazla önerilen video göster">' +
'➕ Daha Fazla Öneri Göster (' +
(count - 12) +
' video daha)' +
'</button>';
} else {
loadMoreBtn =
'<button id="yaktube-related-toggle-more" class="yaktube-shortcuts-btn" style="width:100%; margin-top:12px; padding:8px; justify-content:center; font-size:11px;" aria-label="Daha az öneri göster">' +
'🔼 Daha Az Göster' +
'</button>';
}
}
var headerCountText = count > 12 && !relatedShowAll ? displayed.length + ' / ' + count : count;
container.innerHTML =
'<div class="yaktube-header">' +
'<div class="yaktube-header-left">' +
'<span class="yaktube-badge">🔥 ' +
getInstanceName() +
' Akıllı Önerilenler</span>' +
'<h3 class="yaktube-title-h3" style="margin:0; font-size:1.05rem;">"' +
query +
'" ile İlgili Öneriler (' +
headerCountText +
')</h3>' +
'</div>' +
'<button id="yaktube-related-refresh-btn" class="yaktube-shortcuts-btn" aria-label="Önerileri Yenile">' +
'🔄 Yenile' +
'</button>' +
'</div>' +
'<div class="yaktube-grid" style="grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap:12px;">' +
cardsHtml +
'</div>' +
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 =
'<div class="yaktube-header">' +
'<div class="yaktube-header-left">' +
'<span class="yaktube-badge">🔥 ' +
getInstanceName() +
' Akıllı Önerilenler</span>' +
'<h3 class="yaktube-title-h3" style="margin:0; font-size:1.05rem;">"' +
searchQuery +
'" için Öneriler Taranıyor...</h3>' +
'</div>' +
'</div>' +
'<div style="padding: 18px; text-align: center; color: #ff8f37;">' +
'<div class="yaktube-spinner" style="width: 30px; height: 30px; border-width: 3px;" aria-hidden="true"></div>' +
'<div style="margin-top: 10px; font-size: 13px;">YouTube ve Fediverse\'deki en alakalı videolar getiriliyor...</div>' +
'</div>';
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 =
'<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px;">' +
'<div>' +
'<h2 style="margin: 0; font-size: 16px; font-weight: 700; color: #fff; display: flex; align-items: center; gap: 8px;">' +
"\ud83d\udcac YouTube'daki Orijinal Yorumlar" +
'</h2>' +
'<div style="color: #94a3b8; font-size: 12px; margin-top: 4px;">Bu video YouTube\'dan aktar\u0131lm\u0131\u015ft\u0131r. Orijinal topluluk yorumlar\u0131n\u0131 a\u015fa\u011f\u0131dan anl\u0131k olarak y\u00fckleyebilirsiniz.</div>' +
'</div>' +
'<button id="yaktube-watch-toggle-comments-btn" style="background: #ff8f37; color: #000; border: none; padding: 8px 18px; border-radius: 8px; font-weight: 700; cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 6px; box-shadow: 0 4px 12px rgba(255,143,55,0.3);" aria-label="YouTube yorumlar\u0131n\u0131 y\u00fckle ve g\u00f6ster">' +
'\ud83d\udcac Yorumlar\u0131 G\u00f6ster (20 Yorum)' +
'</button>' +
'</div>' +
'<div id="yaktube-watch-comments-list" style="display: none; margin-top: 16px; flex-direction: column; gap: 12px;"></div>' +
'<div id="yaktube-watch-more-comments-wrap" style="text-align: center; margin-top: 18px; display: none;">' +
'<button id="yaktube-watch-more-comments-btn" style="background: #1e293b; color: #ff8f37; border: 1px solid #ff8f37; padding: 10px 24px; border-radius: 8px; font-weight: 700; cursor: pointer; font-size: 13px; display: inline-flex; align-items: center; gap: 6px; transition: all 0.2s ease;" aria-label="Daha fazla YouTube yorumu y\u00fckle">' +
'\u2795 Daha Fazla Yorum G\u00f6ster (+20 Yorum)' +
'</button>' +
'</div>';
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 =
'<div style="color: #94a3b8; font-size: 13px; text-align: center; padding: 12px;">Bu videoda hen\u00fcz YouTube yorumu bulunmuyor.</div>';
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 =
'<div style="width: 38px; height: 38px; border-radius: 50%; background: #334155; color: #ff8f37; font-weight: 700; display: flex; align-items: center; justify-content: center; font-size: 14px; flex-shrink: 0;">' +
avatarLetter +
'</div>';
var safeContent = c.content
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\n/g, '<br>');
card.innerHTML =
avatarHtml +
'<div style="flex-grow: 1; min-width: 0;">' +
'<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px; flex-wrap: wrap;">' +
'<h3 class="yaktube-comment-author-heading" style="margin: 0; font-size: 13px; font-weight: 700; color: #f1f5f9; display: inline-block;">' +
c.author +
'</h3>' +
'<span style="color: #64748b; font-size: 11px;">' +
c.published +
'</span>' +
(c.likeCount && c.likeCount !== '0'
? '<span style="background: rgba(255,255,255,0.08); color: #94a3b8; font-size: 11px; padding: 2px 6px; border-radius: 4px; display: inline-flex; align-items: center; gap: 4px;">👍 ' +
c.likeCount +
'</span>'
: '') +
'</div>' +
'<div style="color: #cbd5e1; font-size: 13px; line-height: 1.5; word-break: break-word;">' +
safeContent +
'</div>' +
'</div>';
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();
});
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', getInstanceName() + ' 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(getInstanceName() + ' uygulaması cihazınıza yükleniyor.');
}
deferredInstallPrompt = null;
});
}
});
headerButtons.prepend(installBtn);
}
function isOfficialYakTubeServer() {
var host = (window.location.hostname || '').toLowerCase();
// Sadece ve sadece resmi YakTube alan adında veya yerel test ortamında çalışsın
// Diğer PeerTube sunucularında yaktube.apk dosyası olmayacağı için ASLA gösterilmez
return host === 'yaktube.yakhub.com.tr' || host === '127.0.0.1' || host === 'localhost';
}
function injectApkDownloadButton() {
if (!isOfficialYakTubeServer()) {
var existBtnOther = document.getElementById('yaktube-apk-download-btn');
if (existBtnOther) existBtnOther.remove();
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 (!isOfficialYakTubeServer()) {
var existLinkOther = document.getElementById('yaktube-sidebar-apk-link');
if (existLinkOther) existLinkOther.remove();
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 = '<span>🤖</span> <span>YakTube Android APK (4.5 MB)</span>';
menuContainer.appendChild(sidebarLink);
}
// 9. Background Play (Screen-Off / Minimize) & MediaSession Lock-Screen Controls
var videoState = {
hasPlayed: false,
explicitlyPausedByClick: false,
lastPlayTime: 0
};
function attachVideoListeners(video) {
if (!video || video.hasAttribute('data-yaktube-bgplay-hooked')) return;
video.setAttribute('data-yaktube-bgplay-hooked', 'true');
try {
initTouchSeekGesture(video);
} catch (e) {}
video.addEventListener('play', function () {
videoState.hasPlayed = true;
videoState.explicitlyPausedByClick = false;
videoState.lastPlayTime = Date.now();
updateMediaSessionMetadata(video);
});
video.addEventListener('ended', function () {
videoState.hasPlayed = false;
videoState.explicitlyPausedByClick = true;
});
video.addEventListener('timeupdate', function () {
updateMediaSessionPosition(video);
});
}
// Hook manual play/pause buttons
document.addEventListener(
'click',
function (e) {
var target = e.target;
if (!target) return;
var btn = target.closest(
'.vjs-play-control, button[title*="Duraklat"], button[aria-label*="Duraklat"], #yaktube-bar-play-btn'
);
if (btn) {
var video = getVideoElement();
if (video && !video.paused) {
videoState.explicitlyPausedByClick = true;
}
}
},
true
);
function handleVisibilityChange() {
var video = getVideoElement();
if (!video) return;
// When screen turns off / tab is hidden:
if (document.hidden || document.webkitHidden) {
if (videoState.hasPlayed && !videoState.explicitlyPausedByClick) {
// Unconditionally keep audio stream active
setTimeout(function () {
if (video.paused && !videoState.explicitlyPausedByClick) {
video.play().catch(function () {});
}
}, 100);
setTimeout(function () {
if (video.paused && !videoState.explicitlyPausedByClick) {
video.play().catch(function () {});
}
}, 300);
}
}
}
document.addEventListener('visibilitychange', handleVisibilityChange, false);
document.addEventListener('webkitvisibilitychange', handleVisibilityChange, false);
window.addEventListener('pagehide', 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) {}
}
// 10. Unified Debounced MutationObserver & Reactive SPA Engine (v1.5.0)
var unifiedTimer = null;
function runUnifiedCycle(isNav) {
try {
autoFixDOM();
} catch (e) {}
try {
var video = getVideoElement();
if (video) attachVideoListeners(video);
} catch (e) {}
try {
checkAndInjectSearchResults();
} catch (e) {}
try {
injectSearchEnhancements();
} catch (e) {}
try {
checkAndInjectRelatedVideos(Boolean(isNav));
} catch (e) {}
try {
restoreActiveLiveStream();
} catch (e) {}
try {
checkAndInjectWatchPageComments();
} catch (e) {}
try {
injectPWAInstallButton();
} catch (e) {}
try {
injectApkDownloadButton();
} catch (e) {}
try {
injectSidebarApkLink();
} catch (e) {}
}
function scheduleUnifiedCycle(immediate, debounceDelay) {
if (unifiedTimer) {
clearTimeout(unifiedTimer);
unifiedTimer = null;
}
if (immediate) {
runUnifiedCycle(true);
return;
}
var delay = typeof debounceDelay === 'number' ? debounceDelay : 150;
unifiedTimer = setTimeout(function () {
unifiedTimer = null;
runUnifiedCycle(false);
}, delay);
}
// SPA Navigation Handler
function onLocationChange() {
currentActiveVideoKey = null;
scheduleUnifiedCycle(true);
setTimeout(function () {
scheduleUnifiedCycle(false, 0);
}, 350);
}
window.addEventListener('popstate', onLocationChange);
// History pushState / replaceState interception for seamless Angular routing
(function hookHistory() {
try {
var origPush = history.pushState;
var origReplace = history.replaceState;
if (origPush) {
history.pushState = function () {
var ret = origPush.apply(this, arguments);
try {
onLocationChange();
} catch (e) {}
return ret;
};
}
if (origReplace) {
history.replaceState = function () {
var ret = origReplace.apply(this, arguments);
try {
onLocationChange();
} catch (e) {}
return ret;
};
}
} catch (e) {}
})();
// MutationObserver: react immediately to DOM changes instead of CPU polling
if (typeof MutationObserver !== 'undefined') {
try {
var observer = new MutationObserver(function () {
scheduleUnifiedCycle(false, 150);
});
if (document.body) {
observer.observe(document.body, { childList: true, subtree: true });
} else {
document.addEventListener('DOMContentLoaded', function () {
observer.observe(document.body, { childList: true, subtree: true });
});
}
} catch (e) {}
}
// Gentle 3.5s heartbeat fallback (replaces aggressive 800ms polling, reduces idle CPU by 80%)
setInterval(function () {
scheduleUnifiedCycle(false, 0);
}, 3500);
// Initial trigger
runUnifiedCycle(true);
})();
} catch (err) {
console.error('[YakTube] Safe catch error:', err);
}