2026-08-29 12:42:36 +03:00
( function () {
if ( window . __YAKTUBE_ENGINE_LOADED__ ) return ;
window . __YAKTUBE_ENGINE_LOADED__ = true ;
console . log ( '[YakTube] YakNet SSO, Custom Branding, A11y & On-Demand Engine Active' );
// Dynamic Instance Name Helper
function getInstanceName () {
if (
window . PeertubeServerConfig &&
window . PeertubeServerConfig . instance &&
window . PeertubeServerConfig . instance . name
) {
return window . PeertubeServerConfig . instance . name ;
}
var metaSite = document . querySelector ( 'meta[property="og:site_name"]' );
if ( metaSite && metaSite . content ) return metaSite . content ;
var brandEl = document . querySelector ( '.header-left .brand-name, .instance-name, a.brand' );
if ( brandEl && brandEl . textContent ) return brandEl . textContent . trim ();
return 'YakTube' ;
}
// 1. Screen Reader Live Announcer
var liveRegion = null ;
function getLiveRegion () {
if ( ! liveRegion || ! document . body . contains ( liveRegion )) {
liveRegion = document . getElementById ( 'yaktube-a11y-live-region' );
if ( ! liveRegion ) {
liveRegion = document . createElement ( 'div' );
liveRegion . id = 'yaktube-a11y-live-region' ;
liveRegion . setAttribute ( 'aria-live' , 'polite' );
liveRegion . setAttribute ( 'aria-atomic' , 'true' );
liveRegion . className = 'yaktube-sr-only' ;
document . body . appendChild ( liveRegion );
}
}
return liveRegion ;
}
function announce ( text , assertive ) {
var region = getLiveRegion ();
if ( assertive ) {
region . setAttribute ( 'aria-live' , 'assertive' );
} else {
region . setAttribute ( 'aria-live' , 'polite' );
}
region . textContent = '' ;
setTimeout ( function () {
region . textContent = text ;
}, 50 );
}
function triggerYakNetOAuth () {
sessionStorage . removeItem ( 'yaknet_manual_logout' );
var callbackUrl = 'https://yaktube.yakhub.com.tr/plugins/peertube-plugin-auth-yaknet/router/auth-callback' ;
window . location . href =
'https://auth.yakhub.com.tr/oauth/authorize?client_id=01a03c41-f758-721e-b927-619bffde5c23&redirect_uri=' +
encodeURIComponent ( callbackUrl ) +
'&response_type=code&scope=' ;
}
// Handle direct postMessage from YakNet SSO frame
window . addEventListener ( 'message' , function ( ev ) {
if ( ev . data && ev . data . type === 'YAKNET_SSO_STATUS' ) {
if ( ev . data . authenticated && ev . data . user ) {
var token = localStorage . getItem ( 'access_token' );
if (
! token &&
! window . location . search . includes ( 'local=true' ) &&
! window . location . search . includes ( 'externalAuthError' )
) {
sessionStorage . removeItem ( 'yaknet_manual_logout' );
console . log ( '[YakTube] Live YakNet session detected via SSO frame! Connecting...' , ev . data . user );
triggerYakNetOAuth ();
}
} else if ( ev . data . authenticated === false ) {
var hasToken = localStorage . getItem ( 'access_token' ) || localStorage . getItem ( 'username' );
if ( hasToken ) {
console . log ( '[YakTube] YakNet session ended elsewhere. Clearing YakTube...' );
try {
localStorage . clear ();
sessionStorage . clear ();
} catch ( e ) {}
window . location . reload ();
}
}
}
});
// Global listener for YakNet SSO broadcast from auth.yakhub.com.tr
window . addEventListener ( 'yaknet-auth' , function ( e ) {
if ( e . detail && e . detail . authenticated && e . detail . user ) {
var token = localStorage . getItem ( 'access_token' );
if (
! token &&
! window . location . search . includes ( 'local=true' ) &&
! window . location . search . includes ( 'externalAuthError' )
) {
sessionStorage . removeItem ( 'yaknet_manual_logout' );
console . log ( '[YakTube] Initiating automatic single-sign-on handshake for:' , e . detail . user );
triggerYakNetOAuth ();
}
} else if ( e . detail && e . detail . authenticated === false ) {
var hasToken = localStorage . getItem ( 'access_token' ) || localStorage . getItem ( 'username' );
if ( hasToken ) {
try {
localStorage . clear ();
sessionStorage . clear ();
} catch ( err ) {}
window . location . reload ();
}
}
});
// Unified single sign-out for YakTube and YakNet
function performUnifiedLogout () {
try {
localStorage . clear ();
sessionStorage . clear ();
sessionStorage . setItem ( 'yaknet_manual_logout' , 'true' );
} catch ( err ) {}
window . location . replace (
'https://auth.yakhub.com.tr/sdk/logout?redirect_uri=' + encodeURIComponent ( 'https://yaktube.yakhub.com.tr' )
);
}
document . addEventListener (
'click' ,
function ( e ) {
var el = e . target ;
if ( el && el . closest ) {
var isLogout = el . closest (
'a[href*="logout"], button[title*="Log out"], button[title*="Çıkış"], .logout-button, [class*="logout"], [aria-label*="Çıkış"], [aria-label*="Log out"]'
);
if ( isLogout ) {
e . preventDefault ();
e . stopPropagation ();
performUnifiedLogout ();
return ;
}
var isLogin = el . closest (
'my-login-link, .login-button, a[href*="/login"]:not(#yaktube-show-admin-login), a[aria-label*="Giriş"], a[title*="Giriş"], a[title*="login"], a[aria-label*="login"], .yaknet-sso-login-btn'
);
if ( isLogin && ! window . location . search . includes ( 'local=true' )) {
e . preventDefault ();
e . stopPropagation ();
sessionStorage . removeItem ( 'yaknet_manual_logout' );
triggerYakNetOAuth ();
return ;
}
}
},
true
);
// 2. YakNet SSO Header & Login Flow
function injectYakNetSSO () {
// 2.1 Header: Place YakNet Account widget in top-right .buttons-container
var headerRight =
document . querySelector ( 'my-header .buttons-container' ) ||
document . querySelector ( '.buttons-container' ) ||
document . querySelector ( 'my-header .header-right' ) ||
document . querySelector ( 'my-header .user-menu' ) ||
document . querySelector ( 'my-header .action-buttons' ) ||
document . querySelector ( '.header-right' ) ||
document . querySelector ( 'header .right' );
var token = localStorage . getItem ( 'access_token' );
var uName = localStorage . getItem ( 'username' );
var uEmail = localStorage . getItem ( 'email' );
var displayName = localStorage . getItem ( 'displayName' ) || uName || '' ;
var emailStr = uEmail || '' ;
var isAuth = !! token && !! uName ;
if ( headerRight ) {
var liveWidget = headerRight . querySelector ( '#yaknet-header-account-widget' );
if ( ! liveWidget ) {
var widgetWrap = document . createElement ( 'div' );
widgetWrap . id = 'yaknet-header-account-widget' ;
widgetWrap . className = 'yaknet-header-widget' ;
widgetWrap . style . display = 'inline-flex' ;
widgetWrap . style . alignItems = 'center' ;
widgetWrap . style . marginRight = '8px' ;
widgetWrap . innerHTML =
'<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="' +
( isAuth ? 'true' : 'false' ) +
'" ' +
'user-name="' +
( isAuth ? displayName . replace ( /"/g , '"' ) : '' ) +
'" ' +
'user-email="' +
( isAuth ? emailStr . replace ( /"/g , '"' ) : '' ) +
'" ' +
'theme="dark">' +
'</yaknet-account>' ;
var targetSibling =
headerRight . querySelector ( 'my-user-menu' ) ||
headerRight . querySelector ( 'my-login-link' ) ||
headerRight . querySelector ( '.user-menu' ) ||
headerRight . querySelector ( '.settings-button' ) ||
headerRight . querySelector ( 'my-user-notifications' ) ||
headerRight . querySelector ( 'my-notification-bell' );
if ( targetSibling && targetSibling . parentElement === headerRight ) {
headerRight . insertBefore ( widgetWrap , targetSibling );
} else {
headerRight . prepend ( widgetWrap );
}
var defaultLoginLink = headerRight . querySelector ( 'my-login-link' );
if ( defaultLoginLink ) {
defaultLoginLink . setAttribute ( 'aria-hidden' , 'true' );
defaultLoginLink . setAttribute ( 'tabindex' , '-1' );
defaultLoginLink . style . display = 'none' ;
}
} else {
// Sync widget attributes dynamically if auth state changed
var el = liveWidget . querySelector ( 'yaknet-account' );
if ( el ) {
if ( el . shadowRoot ) {
var shadowLogoutBtn =
el . shadowRoot . getElementById ( 'logoutBtn' ) ||
el . shadowRoot . querySelector ( '.yn-logout-btn' ) ||
el . shadowRoot . querySelector ( 'button[title*="Çıkış"], button[aria-label*="Çıkış"]' );
if ( shadowLogoutBtn && ! shadowLogoutBtn . hasAttribute ( 'data-yaktube-hooked' )) {
shadowLogoutBtn . setAttribute ( 'data-yaktube-hooked' , 'true' );
shadowLogoutBtn . addEventListener (
'click' ,
function ( ev ) {
ev . preventDefault ();
ev . stopPropagation ();
performUnifiedLogout ();
},
true
);
}
}
var expectedAuth = isAuth ? 'true' : 'false' ;
var expectedName = isAuth ? displayName : '' ;
var expectedEmail = isAuth ? emailStr : '' ;
if ( el . getAttribute ( 'authenticated' ) !== expectedAuth || el . getAttribute ( 'user-name' ) !== expectedName ) {
el . setAttribute ( 'authenticated' , expectedAuth );
el . setAttribute ( 'user-name' , expectedName );
el . setAttribute ( 'user-email' , expectedEmail );
if ( typeof el . syncStateFromAttributes === 'function' ) {
el . syncStateFromAttributes ();
el . render ();
}
}
}
}
}
// 2.2 Login Modal / Page
var loginCard =
document . querySelector ( 'my-login' ) ||
document . querySelector ( '.login-page' ) ||
document . querySelector ( '.login-container' ) ||
document . querySelector ( 'form.login' ) ||
document . querySelector ( 'form[action*="login"]' ) ||
document . querySelector ( 'my-login-modal' );
if ( loginCard && ! document . getElementById ( 'yaknet-login-section' )) {
var isLocalLogin = window . location . search . includes ( 'local=true' ) || window . location . pathname . includes ( '/admin' );
var ssoBox = document . createElement ( 'div' );
ssoBox . id = 'yaknet-login-section' ;
ssoBox . className = 'yaknet-login-banner' ;
ssoBox . innerHTML =
'<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 ();
subElements . forEach ( function ( el ) {
el . style . display = '' ;
});
showAdminBtn . style . display = 'none' ;
announce ( 'Yönetici kullanı cı adı ve şifre giriş formu açıldı .' );
});
}
}
}
}
// 3. Intelligent Branding & A11y DOM Auto-Fixer
function autoFixDOM () {
injectYakNetSSO ();
// Aggressively remove generic Framasoft Open-in-App / Play Store banners
var genericAppBanners = document . querySelectorAll (
'my-open-app, my-client-app-banner, .open-app-container, .app-banner, [class*="open-app"], [class*="open-in-app"], .mobile-app-banner, my-open-in-app, .app-promotion, a[href*="play.google.com/store/apps/details?id=app.joinpeertube"], a[href*="f-droid.org"]'
);
genericAppBanners . forEach ( function ( banner ) {
banner . setAttribute ( 'aria-hidden' , 'true' );
banner . setAttribute ( 'tabindex' , '-1' );
if ( banner . parentElement ) {
try {
banner . remove ();
} catch ( e ) {
banner . style . display = 'none' ;
}
}
});
// Replace leftover PeerTube branding in visible UI texts
var walker = document . createTreeWalker ( document . body , NodeFilter . SHOW_TEXT , null , false );
var node ;
while (( node = walker . nextNode ())) {
var val = node . nodeValue ;
if ( val && ( val . includes ( 'PeerTube' ) || val . includes ( 'Peertube' ))) {
var parentTag = node . parentElement ? node . parentElement . tagName : '' ;
var parentElem = node . parentElement ;
if (
parentElem &&
( parentElem . closest ( '#yaktube-ondemand-container' ) ||
parentElem . closest ( '#yaktube-live-watch-container' ) ||
parentElem . tagName === 'SCRIPT' ||
parentElem . tagName === 'STYLE' )
) {
continue ;
}
if ( true ) {
node . nodeValue = val
. replace ( /PeerTube hakkı nda/gi , 'YakTube Hakkı nda' )
. replace ( /PeerTube'a katı lı n/gi , "YakTube'a Katı lı n" )
. replace ( /PeerTube örneği/gi , 'YakTube Sunucusu' )
. replace ( /PeerTube/g , 'YakTube' );
}
}
}
// Semantic Headings (H3 for video titles, H2 for sections)
document
. querySelectorAll ( '.video-miniature, my-video-miniature, .video-card, .yaktube-card, [class*="miniature"]' )
. forEach ( function ( card ) {
var titleElement = card . querySelector (
'.video-miniature-name, .video-name, .yaktube-video-name, a[title], .title'
);
if ( titleElement ) {
if ( ! [ 'H1' , 'H2' , 'H3' , 'H4' , 'H5' , 'H6' ]. includes ( titleElement . tagName )) {
titleElement . setAttribute ( 'role' , 'heading' );
titleElement . setAttribute ( 'aria-level' , '3' );
}
}
});
document
. querySelectorAll ( '.section-title, .sub-header, .videos-header, .page-header, .yaktube-title-h3' )
. forEach ( function ( sec ) {
if ( ! [ 'H1' , 'H2' ]. includes ( sec . tagName )) {
sec . setAttribute ( 'role' , 'heading' );
sec . setAttribute ( 'aria-level' , '2' );
}
});
// Make native PeerTube comment authors accessible as H3 headings for screen readers
document
. querySelectorAll ( 'my-video-comment, .video-comment, my-video-comment-thread, .comment, .comment-thread' )
. forEach ( function ( commentEl ) {
var authorEl = commentEl . querySelector (
'.display-name, .actor-name, .comment-author, a.actor-link, .video-comment-header-left a, .video-comment-header-left span'
);
if (
authorEl &&
! [ 'H1' , 'H2' , 'H3' , 'H4' , 'H5' , 'H6' ]. includes ( authorEl . tagName ) &&
! authorEl . getAttribute ( 'role' )
) {
authorEl . setAttribute ( 'role' , 'heading' );
authorEl . setAttribute ( 'aria-level' , '3' );
}
});
// Auto-fix unlabeled buttons
document . querySelectorAll ( 'button:not([data-a11y-fixed])' ). forEach ( function ( btn ) {
btn . setAttribute ( 'data-a11y-fixed' , 'true' );
var text = ( btn . textContent || '' ). trim ();
var hasAria = btn . getAttribute ( 'aria-label' ) || btn . getAttribute ( 'title' );
if ( ! text && ! hasAria ) {
var html = btn . innerHTML . toLowerCase ();
var className = ( btn . className || '' ). toLowerCase ();
var icon = btn . querySelector ( 'my-global-icon, i, svg, [class*="icon-"]' );
var iconName = icon ? icon . className || icon . getAttribute ( 'name' ) || '' : '' ;
if ( html . includes ( 'search' ) || className . includes ( 'search' ) || iconName . includes ( 'search' )) {
btn . setAttribute ( 'aria-label' , 'Ara' );
} else if ( html . includes ( 'bell' ) || className . includes ( 'notification' ) || iconName . includes ( 'bell' )) {
btn . setAttribute ( 'aria-label' , 'Bildirimler' );
} else if ( html . includes ( 'cog' ) || html . includes ( 'setting' ) || className . includes ( 'setting' )) {
btn . setAttribute ( 'aria-label' , 'Ayarlar' );
} else if (
html . includes ( 'menu' ) ||
html . includes ( 'bars' ) ||
className . includes ( 'burger' ) ||
className . includes ( 'menu' )
) {
btn . setAttribute ( 'aria-label' , 'Ana Gezinme Menüsü' );
} else if ( html . includes ( 'play' ) || className . includes ( 'play' )) {
btn . setAttribute ( 'aria-label' , 'Oynat' );
} else if ( html . includes ( 'pause' ) || className . includes ( 'pause' )) {
btn . setAttribute ( 'aria-label' , 'Duraklat' );
} else if ( html . includes ( 'volume' ) || html . includes ( 'mute' ) || className . includes ( 'volume' )) {
btn . setAttribute ( 'aria-label' , 'Ses Kontrolü' );
} else if ( html . includes ( 'fullscreen' ) || className . includes ( 'fullscreen' )) {
btn . setAttribute ( 'aria-label' , 'Tam Ekran' );
} else if (
html . includes ( 'ellipsis' ) ||
html . includes ( 'more' ) ||
className . includes ( 'more' ) ||
className . includes ( 'action' )
) {
btn . setAttribute ( 'aria-label' , 'Diğer İşlemler ve Seçenekler' );
} else if ( html . includes ( 'trash' ) || html . includes ( 'delete' ) || className . includes ( 'delete' )) {
btn . setAttribute ( 'aria-label' , 'Sil' );
} else if ( html . includes ( 'edit' ) || className . includes ( 'edit' )) {
btn . setAttribute ( 'aria-label' , 'Düzenle' );
} else if ( html . includes ( 'share' ) || className . includes ( 'share' )) {
btn . setAttribute ( 'aria-label' , 'Paylaş' );
} else if ( html . includes ( 'thumb-up' ) || html . includes ( 'like' ) || className . includes ( 'like' )) {
btn . setAttribute ( 'aria-label' , 'Beğen' );
} else if ( html . includes ( 'thumb-down' ) || html . includes ( 'dislike' ) || className . includes ( 'dislike' )) {
btn . setAttribute ( 'aria-label' , 'Beğenme' );
} else if ( html . includes ( 'filter' ) || className . includes ( 'filter' )) {
btn . setAttribute ( 'aria-label' , 'Filtreler' );
} else if ( html . includes ( 'close' ) || html . includes ( 'times' ) || className . includes ( 'close' )) {
btn . setAttribute ( 'aria-label' , 'Kapat' );
} else if ( btn . closest ( '.vjs-control-bar' )) {
btn . setAttribute ( 'aria-label' , 'Oynatı cı Düğmesi' );
} else {
var parent = btn . parentElement ;
if ( parent && parent . title ) {
btn . setAttribute ( 'aria-label' , parent . title );
} else {
btn . setAttribute ( 'aria-label' , 'Düğme' );
}
}
}
});
// Auto-fix unlabeled images
document . querySelectorAll ( 'img:not([data-a11y-fixed])' ). forEach ( function ( img ) {
img . setAttribute ( 'data-a11y-fixed' , 'true' );
var alt = img . getAttribute ( 'alt' );
if ( ! alt || alt . trim () === '' ) {
var card = img . closest ( '.video-miniature, .video-card, .yaktube-card, [class*="miniature"]' );
if ( card ) {
var titleEl = card . querySelector ( '.video-miniature-name, .video-name, .yaktube-video-name, h3, h4, a[title]' );
var titleText = titleEl ? ( titleEl . textContent || titleEl . getAttribute ( 'title' ) || '' ). trim () : '' ;
if ( titleText ) {
img . setAttribute ( 'alt' , titleText + ' video kapağı' );
return ;
}
}
var avatarContainer = img . closest ( '.actor-avatar, .channel-avatar, .avatar, [class*="avatar"]' );
if ( avatarContainer ) {
var nameEl = avatarContainer . parentElement
? avatarContainer . parentElement . querySelector ( '.name, .actor-name, .channel-name' )
: null ;
var nameText = nameEl ? ( nameEl . textContent || '' ). trim () : '' ;
img . setAttribute ( 'alt' , ( nameText ? nameText + ' ' : '' ) + 'Profil Resmi' );
return ;
}
if ( img . src && ( img . src . includes ( 'logo' ) || img . className . includes ( 'logo' ))) {
img . setAttribute ( 'alt' , getInstanceName () + ' Logo' );
return ;
}
var parentLink = img . closest ( 'a' );
if ( parentLink && parentLink . textContent && parentLink . textContent . trim (). length > 0 ) {
img . setAttribute ( 'alt' , '' );
} else if ( parentLink && parentLink . getAttribute ( 'title' )) {
img . setAttribute ( 'alt' , parentLink . getAttribute ( 'title' ));
} else {
img . setAttribute ( 'alt' , 'Görsel' );
}
}
});
// Auto-fix unlabeled links
document . querySelectorAll ( 'a:not([data-a11y-fixed])' ). forEach ( function ( a ) {
a . setAttribute ( 'data-a11y-fixed' , 'true' );
var text = ( a . textContent || '' ). trim ();
var hasAria = a . getAttribute ( 'aria-label' ) || a . getAttribute ( 'title' );
if ( ! text && ! hasAria ) {
var img = a . querySelector ( 'img' );
if ( img && img . alt ) {
a . setAttribute ( 'aria-label' , img . alt );
} else if ( a . href ) {
if ( a . href . includes ( '/videos/watch/' )) {
a . setAttribute ( 'aria-label' , 'Videoyu İzle' );
} else if ( a . href . includes ( '/video-channels/' )) {
a . setAttribute ( 'aria-label' , 'Kanal Sayfası na Git' );
} else if ( a . href . includes ( '/accounts/' )) {
a . setAttribute ( 'aria-label' , 'Hesap Profiline Git' );
} else if ( a . href . includes ( '/videos/search' )) {
a . setAttribute ( 'aria-label' , 'Arama Sayfası ' );
} else {
a . setAttribute ( 'aria-label' , 'Bağlantı ' );
}
}
}
});
// Auto-fix search inputs
document . querySelectorAll ( 'input:not([data-a11y-fixed])' ). forEach ( function ( inp ) {
inp . setAttribute ( 'data-a11y-fixed' , 'true' );
if ( ! inp . getAttribute ( 'aria-label' ) && ! inp . getAttribute ( 'placeholder' ) && ! ( inp . labels && inp . labels . length )) {
var type = ( inp . getAttribute ( 'type' ) || 'text' ). toLowerCase ();
if ( type === 'search' ) {
inp . setAttribute ( 'aria-label' , getInstanceName () + ' Video Arama' );
} else if ( type === 'checkbox' ) {
inp . setAttribute ( 'aria-label' , 'Seçim Kutusu' );
} else if ( type === 'password' ) {
inp . setAttribute ( 'aria-label' , 'Åifre' );
}
}
});
}
setInterval ( autoFixDOM , 1000 );
// 3. Keyboard Shortcuts Modal
function toggleShortcutsModal () {
var modal = document . getElementById ( 'yaktube-shortcuts-dialog' );
if ( modal ) {
if ( modal . style . display === 'none' || ! modal . style . display ) {
modal . style . display = 'flex' ;
announce ( 'Klavye kı sayolları yardı m penceresi açıldı . Kapatmak i� in Escape tuşuna bası n.' );
var closeBtn = document . getElementById ( 'yaktube-shortcuts-close' );
if ( closeBtn ) closeBtn . focus ();
} else {
modal . style . display = 'none' ;
announce ( 'Yardı m penceresi kapatı ldı .' );
}
} else {
modal = document . createElement ( 'div' );
modal . id = 'yaktube-shortcuts-dialog' ;
modal . className = 'yaktube-modal-overlay' ;
modal . setAttribute ( 'role' , 'dialog' );
modal . setAttribute ( 'aria-modal' , 'true' );
modal . setAttribute ( 'aria-labelledby' , 'yaktube-shortcuts-title' );
modal . innerHTML =
'<div class="yaktube-modal-card" style="max-width: 520px;">' +
'<h3 id="yaktube-shortcuts-title" class="yaktube-modal-title" style="margin-top:0;">?? YakTube Klavye K� sayollar� </h3>' +
'<table class="yaktube-shortcuts-table">' +
'<thead><tr><th>Kı sayol</th><th>İşlev</th></tr></thead>' +
'<tbody>' +
'<tr><td><span class="yaktube-kbd">Space</span> veya <span class="yaktube-kbd">K</span></td><td>Oynat / Duraklat</td></tr>' +
'<tr><td><span class="yaktube-kbd">Sol Ok</span> veya <span class="yaktube-kbd">J</span></td><td>5 saniye geri sar</td></tr>' +
'<tr><td><span class="yaktube-kbd">Sağ Ok</span> veya <span class="yaktube-kbd">L</span></td><td>5 saniye ileri sar</td></tr>' +
'<tr><td><span class="yaktube-kbd">Yukarı / Aşağı Ok</span></td><td>Sesi %5 artı r / azalt</td></tr>' +
'<tr><td><span class="yaktube-kbd">M</span></td><td>Sesi Kapat / A� (Mute)</td></tr>' +
'<tr><td><span class="yaktube-kbd">F</span></td><td>Tam Ekran</td></tr>' +
'<tr><td><span class="yaktube-kbd">C</span></td><td>Altyazı ları A� / Kapat</td></tr>' +
'<tr><td><span class="yaktube-kbd">/</span> (Bölü)</td><td>Arama kutusuna hı zlı git</td></tr>' +
'<tr><td><span class="yaktube-kbd">0 - 9</span></td><td>Videonun %0 - %90 noktası na atla</td></tr>' +
'<tr><td><span class="yaktube-kbd">?</span> (Shift+/)</td><td>Bu yardı m kı lavuzunu aç / kapat</td></tr>' +
'<tr><td><span class="yaktube-kbd">Esc</span></td><td>Pencereleri kapat</td></tr>' +
'</tbody>' +
'</table>' +
'<button id="yaktube-shortcuts-close" class="yaktube-modal-close-btn" aria-label="Kı lavuzu Kapat">Kı lavuzu Kapat (Esc)</button>' +
'</div>' ;
document . body . appendChild ( modal );
document . getElementById ( 'yaktube-shortcuts-close' ). addEventListener ( 'click' , function () {
modal . style . display = 'none' ;
});
var closeBtn = document . getElementById ( 'yaktube-shortcuts-close' );
if ( closeBtn ) closeBtn . focus ();
announce ( 'Klavye kı sayolları yardı m penceresi açıldı . Kapatmak i� in Escape tuşuna bası n.' );
}
}
function isInputFocused () {
var el = document . activeElement ;
if ( ! el ) return false ;
var tag = el . tagName ? el . tagName . toUpperCase () : '' ;
return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || el . isContentEditable ;
}
function getVideoElement () {
return document . querySelector ( 'video' );
}
function formatTime ( seconds ) {
if ( isNaN ( seconds ) || seconds < 0 ) return '0 saniye' ;
var mins = Math . floor ( seconds / 60 );
var secs = Math . floor ( seconds % 60 );
if ( mins === 0 ) return secs + ' saniye' ;
return mins + ' dakika ' + secs + ' saniye' ;
}
// 4. Global Keydown Listener
window . addEventListener ( 'keydown' , function ( e ) {
if ( e . key === 'Escape' ) {
var shortcutsModal = document . getElementById ( 'yaktube-shortcuts-dialog' );
if ( shortcutsModal && shortcutsModal . style . display !== 'none' ) {
shortcutsModal . style . display = 'none' ;
announce ( 'Pencere kapatı ldı .' );
return ;
}
var importModal = document . getElementById ( 'yaktube-import-modal' );
if ( importModal && importModal . style . display !== 'none' ) {
importModal . style . display = 'none' ;
announce ( 'İndirme penceresi kapatı ldı .' );
return ;
}
if ( isInputFocused ()) {
document . activeElement . blur ();
}
return ;
}
if ( e . key === '?' || ( e . shiftKey && e . key === '/' )) {
if ( ! isInputFocused ()) {
e . preventDefault ();
toggleShortcutsModal ();
return ;
}
}
if ( e . key === '/' && ! isInputFocused ()) {
e . preventDefault ();
var searchInput =
document . querySelector ( 'input[type="search"]' ) ||
document . querySelector ( 'input[name="search"]' ) ||
document . querySelector ( '.search-input' ) ||
document . querySelector ( 'input' );
if ( searchInput ) {
searchInput . focus ();
if ( searchInput . select ) searchInput . select ();
announce ( 'Arama kutusuna odaklanı ldı .' );
}
return ;
}
if ( isInputFocused ()) {
return ;
}
var video = getVideoElement ();
if ( ! video ) return ;
var key = e . key . toLowerCase ();
if ( key === ' ' || key === 'k' ) {
e . preventDefault ();
if ( video . paused ) {
video . play ();
announce ( 'Video oynatı lı yor.' );
} else {
video . pause ();
announce ( 'Video duraklatı ldı .' );
}
} else if ( key === 'j' || e . key === 'ArrowLeft' ) {
e . preventDefault ();
video . currentTime = Math . max ( 0 , video . currentTime - 5 );
announce ( '5 saniye geri sarı ldı . Åu an: ' + formatTime ( video . currentTime ));
} else if ( key === 'l' || e . key === 'ArrowRight' ) {
e . preventDefault ();
video . currentTime = Math . min ( video . duration || 99999 , video . currentTime + 5 );
announce ( '5 saniye ileri sarı ldı . Åu an: ' + formatTime ( video . currentTime ));
} else if ( e . key === 'ArrowUp' ) {
e . preventDefault ();
video . volume = Math . min ( 1 , video . volume + 0.05 );
announce ( 'Ses seviyesi yüzde ' + Math . round ( video . volume * 100 ));
} else if ( e . key === 'ArrowDown' ) {
e . preventDefault ();
video . volume = Math . max ( 0 , video . volume - 0.05 );
announce ( 'Ses seviyesi yüzde ' + Math . round ( video . volume * 100 ));
} else if ( key === 'm' ) {
e . preventDefault ();
video . muted = ! video . muted ;
announce ( video . muted ? 'Ses kapatı ldı (Sessiz).' : 'Ses açıldı .' );
} else if ( key === 'f' ) {
e . preventDefault ();
if ( ! document . fullscreenElement ) {
var container = video . closest ( '.video-js' ) || video . parentElement || video ;
if ( container . requestFullscreen ) container . requestFullscreen ();
announce ( 'Tam ekran moduna geçildi.' );
} else {
if ( document . exitFullscreen ) document . exitFullscreen ();
announce ( 'Tam ekrandan çıkı ldı .' );
}
} else if ( key === 'c' ) {
e . preventDefault ();
var tracks = video . textTracks ;
if ( tracks && tracks . length > 0 ) {
var isShowing = tracks [ 0 ]. mode === 'showing' ;
tracks [ 0 ]. mode = isShowing ? 'disabled' : 'showing' ;
announce ( isShowing ? 'Altyazı lar kapatı ldı .' : 'Altyazı lar açıldı .' );
} else {
announce ( 'Bu videoda altyazı bulunmuyor.' );
}
} else if ( key >= '0' && key <= '9' && ! e . ctrlKey && ! e . altKey && ! e . metaKey ) {
e . preventDefault ();
var percent = parseInt ( key , 10 ) * 10 ;
if ( video . duration ) {
video . currentTime = ( video . duration * percent ) / 100 ;
announce ( 'Videonun yüzde ' + percent + ' noktası na atlandı . Åu an: ' + formatTime ( video . currentTime ));
}
}
});
// 5. On-Demand Search & Import with Duplicate Awareness
var lastQuery = null ;
var isSearching = false ;
function showModal ( title , desc ) {
var overlay = document . getElementById ( 'yaktube-import-modal' );
if ( ! overlay ) {
overlay = document . createElement ( 'div' );
overlay . id = 'yaktube-import-modal' ;
overlay . className = 'yaktube-modal-overlay' ;
overlay . setAttribute ( 'role' , 'dialog' );
overlay . setAttribute ( 'aria-modal' , 'true' );
overlay . setAttribute ( 'aria-labelledby' , 'yaktube-modal-title' );
overlay . innerHTML =
'<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 , '"' ) +
'" 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 , '&' )
. replace ( /</g , '<' )
. replace ( />/g , '>' )
. 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 = "📥 YakTube'a Aktar & İzle" ;
alert ( 'Video indirme sı rası nda bir hata oluştu.' );
announce ( 'Video indirme sı rası nda bir hata oluştu.' , true );
}
})
. catch ( function ( e ) {
console . error ( '[YakTube] Status poll error:' , e );
});
if ( attempts > 35 ) {
clearInterval ( pollInterval );
hideModal ();
btn . disabled = false ;
btn . innerHTML = "📥 YakTube'a Aktar & İzle" ;
alert (
'İndirme arka planda tamamlanı yor. Ana sayfada veya Videoları m sayfası nda kı sa süre içinde hazı r olacaktı r.'
);
announce ( 'İndirme arka planda tamamlanı yor. Ana sayfada kı sa süre içinde hazı r olacaktı r.' );
}
}, 1500 );
})
. catch ( function ( err ) {
hideModal ();
btn . disabled = false ;
btn . innerHTML = "📥 YakTube'a Aktar & İzle" ;
alert ( 'İndirme hatası : ' + err . message );
announce ( 'İndirme hatası : ' + err . message , true );
});
}
function checkAndInjectSearchResults () {
var url = new URL ( window . location . href );
var searchParam = url . searchParams . get ( 'search' );
if ( ! window . location . pathname . includes ( '/search' ) || ! searchParam || searchParam . trim (). length < 2 ) {
var existing = document . getElementById ( 'yaktube-ondemand-container' );
if ( existing ) existing . remove ();
lastQuery = null ;
return ;
}
var query = searchParam . trim ();
if ( query === lastQuery ) return ;
lastQuery = query ;
var container = document . getElementById ( 'yaktube-ondemand-container' );
if ( ! container ) {
container = document . createElement ( 'div' );
container . id = 'yaktube-ondemand-container' ;
container . className = 'yaktube-ondemand-section' ;
container . setAttribute ( 'role' , 'region' );
container . setAttribute ( 'aria-label' , 'Canl\u0131 YouTube Arama Sonu\u00e7lar\u0131' );
var mainContent =
document . querySelector ( 'my-search, my-search-results, router-outlet, main, .main-col' ) || document . body ;
if ( mainContent && mainContent . firstChild ) {
mainContent . insertBefore ( container , mainContent . firstChild );
} else if ( mainContent ) {
mainContent . appendChild ( container );
}
}
container . innerHTML =
'<div class="yaktube-header">' +
'<div class="yaktube-header-left">' +
'<span class="yaktube-badge">\u26a1 Canl\u0131 YouTube & Web Arama</span>' +
'<h2 class="yaktube-title-h3" style="margin:0; font-size:1.15rem;">"' +
query +
'" i\u00e7in YouTube Sonu\u00e7lar\u0131 Taran\u0131yor...</h2>' +
'</div>' +
'<button class="yaktube-shortcuts-btn" id="yaktube-open-shortcuts" aria-label="Klavye K\u0131sayollar\u0131 K\u0131lavuzunu A\u00e7">' +
'\u2328\ufe0f K\u0131sayollar (?)' +
'</button>' +
'</div>' +
'<div class="yaktube-loading">' +
'<div class="yaktube-spinner" aria-hidden="true"></div>' +
'<div style="margin-top: 10px;">YouTube\'daki en pop\u00fcler e\u015fle\u015fmeler taran\u0131yor...</div>' +
'</div>' ;
var shortcutsBtn = document . getElementById ( 'yaktube-open-shortcuts' );
if ( shortcutsBtn ) {
shortcutsBtn . addEventListener ( 'click' , toggleShortcutsModal );
}
announce ( query + ' i\u00e7in canl\u0131 YouTube aramas\u0131 ba\u015flat\u0131ld\u0131.' );
isSearching = true ;
fetch ( '/api-custom/youtube-search?q=' + encodeURIComponent ( query ))
. then ( function ( res ) {
return res . json ();
})
. then ( function ( data ) {
var rawResults = data . results || [];
var uniqueResults = [];
var seenIds = {};
rawResults . forEach ( function ( item ) {
if ( item && item . id && ! seenIds [ item . id ] && item . title ) {
seenIds [ item . id ] = true ;
uniqueResults . push ( item );
}
});
var count = uniqueResults . length ;
if ( count === 0 ) {
container . innerHTML =
'<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;">"' +
query +
'" için YouTube\'da Sonuç Bulunamadı </h2>' +
'</div>' +
'</div>' ;
announce ( query + " için YouTube\'da eşleşen video bulunamadı ." );
} else {
var countText = count === 1 ? '1 Video Bulundu' : count + ' Video Bulundu' ;
announce ( query + ' araması için ' + count + ' video listelendi.' );
var visibleCount = Math . min ( 12 , count );
function render () {
var visibleResults = uniqueResults . slice ( 0 , visibleCount );
var cardsHtml = '' ;
visibleResults . forEach ( function ( item ) {
var isLocal = !! item . isDownloaded ;
var isLive = !! item . isLive ;
var statusBadge = '' ;
if ( isLive ) {
statusBadge =
'<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;">✅ ' +
getInstanceName () +
"'da Mevcut</span>" ;
}
var actionButton = '' ;
if ( isLive ) {
actionButton =
'<button class="yaktube-live-btn" data-url="' +
item . url +
'" data-title="' +
item . title . replace ( /"/g , '"' ) +
'" 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="' +
item . title . replace ( /"/g , '"' ) +
' canlı yayı nı nı hemen izle">🔴 Canlı Yayı nı İzle</button>' ;
} else if ( isLocal ) {
actionButton =
'<a href="' +
item . localVideo . url +
'" class="yaktube-play-btn" aria-label="' +
item . title . replace ( /"/g , '"' ) +
' videosunu YakTube\'dan hemen izle">▶️ ' +
getInstanceName () +
"'dan İzle</a>" ;
} else {
actionButton =
'<button class="yaktube-import-btn" data-url="' +
item . url +
'" data-title="' +
item . title . replace ( /"/g , '"' ) +
'" aria-label="' +
item . title . replace ( /"/g , '"' ) +
' videosunu ' +
getInstanceName () +
'\'a indir ve izle">📥 ' +
getInstanceName () +
"'a Aktar & İzle</button>" ;
}
var durationText = isLive ? 'CANLI' : item . durationFormatted ;
var cardLabel =
item . title +
( isLive ? ' (Canlı Yayı n)' : isLocal ? ' (' + getInstanceName () + ' kütüphanenizde mevcut)' : '' ) +
', ' +
item . channel +
' kanalı , Süre: ' +
durationText ;
cardsHtml +=
'<div class="yaktube-card" role="article" tabindex="0" aria-label="' +
cardLabel . replace ( /"/g , '"' ) +
'">' +
'<div class="yaktube-thumb-wrap">' +
'<img class="yaktube-thumb-img" src="' +
item . thumbnail +
'" alt="' +
item . title . replace ( /"/g , '"' ) +
'" loading="lazy" />' +
'<span class="yaktube-duration-badge" style="' +
( isLive ? 'background:#dc2626;font-weight:700;' : '' ) +
'" aria-label="Süre">' +
durationText +
'</span>' +
'</div>' +
'<div class="yaktube-info">' +
'<div>' +
statusBadge +
'<h3 class="yaktube-video-name" title="' +
item . title +
'" style="margin:0; font-size:13px; font-weight:600; line-height:1.35;">' +
item . title +
'</h3>' +
'<div class="yaktube-channel-name">' +
item . channel +
'</div>' +
'</div>' +
actionButton +
'</div>' +
'</div>' ;
});
var loadMoreHtml = '' ;
if ( visibleCount < count ) {
loadMoreHtml =
'<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 (' +
visibleCount +
' / ' +
count +
')' +
'</button>' +
'</div>' ;
}
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;">"' +
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>' +
'<div class="yaktube-grid">' +
cardsHtml +
'</div>' +
loadMoreHtml ;
var sBtn = document . getElementById ( 'yaktube-open-shortcuts' );
if ( sBtn ) {
sBtn . addEventListener ( 'click' , toggleShortcutsModal );
}
var loadMoreBtn = document . getElementById ( 'yaktube-load-more-btn' );
if ( loadMoreBtn ) {
loadMoreBtn . addEventListener ( 'click' , function () {
visibleCount = Math . min ( visibleCount + 6 , count );
render ();
announce ( visibleCount + ' adet video gösteriliyor.' );
});
}
container . querySelectorAll ( '.yaktube-live-btn' ). forEach ( function ( btn ) {
btn . addEventListener ( 'click' , function ( e ) {
var liveUrl = e . currentTarget . getAttribute ( 'data-url' );
var liveTitle = e . currentTarget . getAttribute ( 'data-title' );
handleLiveStream ( liveUrl , liveTitle );
});
});
container . querySelectorAll ( '.yaktube-import-btn' ). forEach ( function ( btn ) {
btn . addEventListener ( 'click' , function ( e ) {
var videoUrl = e . currentTarget . getAttribute ( 'data-url' );
var videoTitle = e . currentTarget . getAttribute ( 'data-title' );
handleImport ( videoUrl , e . currentTarget , videoTitle );
});
});
}
render ();
}
})
. catch ( function ( err ) {
console . error ( '[YakTube] Search fetch error:' , err );
container . innerHTML =
'<div class="yaktube-header">' +
'<div class="yaktube-header-left">' +
'<span class="yaktube-badge">\u26a1 Canl\u0131 YouTube & Web Arama</span>' +
'<h2 class="yaktube-title-h3" style="margin:0; font-size:1.15rem;">Arama s\u0131ras\u0131nda bir ba\u011flant\u0131 sorunu olu\u015ftu.</h2>' +
'</div>' +
'</div>' ;
announce ( 'Arama s\u0131ras\u0131nda bir ba\u011flant\u0131 sorunu olu\u015ftu.' );
})
. finally ( function () {
isSearching = false ;
});
}
2026-08-29 19:22:42 +03:00
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
2026-08-29 22:37:27 +03:00
// --------------------------------------------------------------------------
2026-08-29 22:43:55 +03:00
// --------------------------------------------------------------------------
2026-08-29 22:59:18 +03:00
// --------------------------------------------------------------------------
// 8. YOUTUBE-IDENTICAL SEARCH BAR & VOICE MODAL ENGINE (v1.2.3 - Universal)
2026-08-29 21:47:30 +03:00
// --------------------------------------------------------------------------
var activeVoiceRecognition = null ;
var isVoiceListeningActive = false ;
var modalTargetInput = null ;
2026-08-29 19:22:42 +03:00
2026-08-29 21:47:30 +03:00
function createYouTubeVoiceModal () {
var existingModal = document . getElementById ( 'yaktube-voice-modal' );
if ( existingModal ) return existingModal ;
2026-08-29 19:22:42 +03:00
2026-08-29 21:47:30 +03:00
var modal = document . createElement ( 'div' );
modal . id = 'yaktube-voice-modal' ;
modal . className = 'yaktube-voice-modal-overlay' ;
modal . setAttribute ( 'role' , 'dialog' );
modal . setAttribute ( 'aria-modal' , 'true' );
modal . setAttribute ( 'aria-labelledby' , 'yaktube-voice-modal-title' );
modal . style . display = 'none' ;
2026-08-29 19:22:42 +03:00
2026-08-29 21:47:30 +03:00
modal . innerHTML =
'<div class="yaktube-voice-modal-card">' +
'<div class="yaktube-voice-modal-header">' +
'<h2 id="yaktube-voice-modal-title" class="yaktube-voice-modal-title">Sesle arama yapı n</h2>' +
'<button id="yaktube-voice-modal-close" class="yaktube-voice-modal-close-btn" aria-label="Sesli aramayı kapat">✕</button>' +
'</div>' +
'<div class="yaktube-voice-modal-body">' +
'<div id="yaktube-voice-status-text" class="yaktube-voice-status-text">Dinleniyor...</div>' +
'<div id="yaktube-voice-transcript-preview" class="yaktube-voice-transcript-preview"></div>' +
'<div class="yaktube-voice-mic-wrapper">' +
'<button id="yaktube-voice-mic-pulse-btn" class="yaktube-voice-mic-pulse-btn" aria-label="Mikrofonu durdur veya başlat">' +
'🎙️' +
'</button>' +
'<div id="yaktube-voice-ripple" class="yaktube-voice-ripple"></div>' +
'</div>' +
'<div class="yaktube-voice-hint-text">Aramak istediğiniz sanatçıyı , şarkı yı veya konuyu söyleyin.</div>' +
'</div>' +
'</div>' ;
2026-08-29 19:22:42 +03:00
2026-08-29 21:47:30 +03:00
document . body . appendChild ( modal );
var closeBtn = modal . querySelector ( '#yaktube-voice-modal-close' );
if ( closeBtn ) {
closeBtn . addEventListener ( 'click' , function () {
closeYouTubeVoiceModal ();
2026-08-29 19:22:42 +03:00
});
}
2026-08-29 21:47:30 +03:00
modal . addEventListener ( 'click' , function ( e ) {
if ( e . target === modal ) {
closeYouTubeVoiceModal ();
}
});
var pulseBtn = modal . querySelector ( '#yaktube-voice-mic-pulse-btn' );
if ( pulseBtn ) {
pulseBtn . addEventListener ( 'click' , function () {
if ( isVoiceListeningActive ) {
stopVoiceRecognition ();
} else {
startVoiceRecognition ();
}
});
}
return modal ;
}
function openYouTubeVoiceModal ( targetInput ) {
var SpeechRec = window . SpeechRecognition || window . webkitSpeechRecognition ;
if ( ! SpeechRec ) {
announce (
'Tarayı cı nı z sesli arama özelliğini desteklemiyor. Lütfen Chrome, Edge veya güncel bir mobil tarayı cı kullanı n.'
);
return ;
}
var modal = createYouTubeVoiceModal ();
modal . style . display = 'flex' ;
announce ( 'Sesli arama penceresi açıldı . Dinleniyor, lütfen aramak istediğiniz videoyu söyleyin.' , true );
modalTargetInput = targetInput ;
startVoiceRecognition ();
var closeBtn = modal . querySelector ( '#yaktube-voice-modal-close' );
if ( closeBtn ) closeBtn . focus ();
}
function closeYouTubeVoiceModal () {
var modal = document . getElementById ( 'yaktube-voice-modal' );
if ( modal ) modal . style . display = 'none' ;
stopVoiceRecognition ();
announce ( 'Sesli arama kapatı ldı .' );
}
function startVoiceRecognition () {
var SpeechRec = window . SpeechRecognition || window . webkitSpeechRecognition ;
if ( ! SpeechRec ) return ;
if ( activeVoiceRecognition ) {
try {
activeVoiceRecognition . stop ();
} catch ( e ) {}
}
var modal = document . getElementById ( 'yaktube-voice-modal' );
var statusText = modal ? modal . querySelector ( '#yaktube-voice-status-text' ) : null ;
var transcriptPreview = modal ? modal . querySelector ( '#yaktube-voice-transcript-preview' ) : null ;
var micBtn = modal ? modal . querySelector ( '#yaktube-voice-mic-pulse-btn' ) : null ;
var ripple = modal ? modal . querySelector ( '#yaktube-voice-ripple' ) : null ;
if ( statusText ) statusText . textContent = 'Dinleniyor...' ;
if ( transcriptPreview ) transcriptPreview . textContent = '' ;
if ( micBtn ) micBtn . classList . remove ( 'yaktube-idle-mic' );
if ( ripple ) ripple . style . display = 'block' ;
try {
activeVoiceRecognition = new SpeechRec ();
activeVoiceRecognition . lang = navigator . language || 'tr-TR' ;
activeVoiceRecognition . continuous = false ;
activeVoiceRecognition . interimResults = true ;
activeVoiceRecognition . maxAlternatives = 1 ;
isVoiceListeningActive = true ;
activeVoiceRecognition . onresult = function ( ev ) {
if ( ev . results && ev . results [ 0 ]) {
var transcript = ev . results [ 0 ][ 0 ]. transcript . trim ();
if ( transcriptPreview ) transcriptPreview . textContent = '"' + transcript + '"' ;
if ( ev . results [ 0 ]. isFinal ) {
announce ( '"' + transcript + '" için arama başlatı lı yor...' , true );
if ( modalTargetInput ) {
modalTargetInput . value = transcript ;
modalTargetInput . dispatchEvent ( new Event ( 'input' , { bubbles : true }));
modalTargetInput . dispatchEvent ( new Event ( 'change' , { bubbles : true }));
setTimeout ( function () {
closeYouTubeVoiceModal ();
var form = modalTargetInput . closest ( 'form' );
if ( form ) {
form . dispatchEvent ( new Event ( 'submit' , { bubbles : true , cancelable : true }));
} else {
window . location . href = '/search/videos?search=' + encodeURIComponent ( transcript );
}
}, 400 );
}
}
}
};
activeVoiceRecognition . onerror = function ( ev ) {
console . warn ( '[YakTube] Voice modal error:' , ev . error );
if ( statusText ) {
if ( ev . error === 'not-allowed' ) {
statusText . textContent = 'Mikrofon İzni Gerekli' ;
announce ( 'Mikrofon izni verilmedi.' );
} else if ( ev . error === 'no-speech' ) {
statusText . textContent = 'Ses Algı lanamadı ' ;
announce ( 'Ses algı lanamadı , tekrar denemek için mikrofona dokunun.' );
} else {
statusText . textContent = 'Bir sorun oluştu' ;
}
}
stopVoiceRecognition ();
};
activeVoiceRecognition . onend = function () {
if ( isVoiceListeningActive ) {
stopVoiceRecognition ();
}
};
activeVoiceRecognition . start ();
} catch ( err ) {
console . error ( '[YakTube] SpeechRec start failed:' , err );
stopVoiceRecognition ();
}
}
function stopVoiceRecognition () {
isVoiceListeningActive = false ;
if ( activeVoiceRecognition ) {
try {
activeVoiceRecognition . stop ();
} catch ( e ) {}
activeVoiceRecognition = null ;
}
var modal = document . getElementById ( 'yaktube-voice-modal' );
if ( modal ) {
var micBtn = modal . querySelector ( '#yaktube-voice-mic-pulse-btn' );
var ripple = modal . querySelector ( '#yaktube-voice-ripple' );
var statusText = modal . querySelector ( '#yaktube-voice-status-text' );
if ( micBtn ) micBtn . classList . add ( 'yaktube-idle-mic' );
if ( ripple ) ripple . style . display = 'none' ;
if ( statusText && statusText . textContent === 'Dinleniyor...' ) {
statusText . textContent = 'Mikrofona dokunup konuşun' ;
}
}
}
function transformToYouTubeSearchBar ( searchInp , searchContainer ) {
if ( searchInp . hasAttribute ( 'data-yaktube-yt-styled' )) return ;
searchInp . setAttribute ( 'data-yaktube-yt-styled' , 'true' );
2026-08-29 22:43:55 +03:00
searchInp . setAttribute ( 'aria-label' , getInstanceName () + ' Video Arama' );
2026-08-29 21:47:30 +03:00
2026-08-29 22:59:18 +03:00
// 1. Create Outer Wrapper
var outerWrap = searchContainer . querySelector ( '.yaktube-search-outer-wrap' );
if ( ! outerWrap ) {
outerWrap = document . createElement ( 'div' );
outerWrap . className = 'yaktube-search-outer-wrap' ;
searchInp . parentNode . insertBefore ( outerWrap , searchInp );
2026-08-29 21:47:30 +03:00
}
2026-08-29 22:59:18 +03:00
// 2. Create Pill Box
var pillWrapper = outerWrap . querySelector ( '.yaktube-search-pill-wrapper' );
2026-08-29 21:47:30 +03:00
if ( ! pillWrapper ) {
pillWrapper = document . createElement ( 'div' );
pillWrapper . className = 'yaktube-search-pill-wrapper' ;
2026-08-29 22:59:18 +03:00
outerWrap . appendChild ( pillWrapper );
2026-08-29 21:47:30 +03:00
2026-08-29 22:59:18 +03:00
// Move Input Inside Pill
2026-08-29 21:47:30 +03:00
pillWrapper . appendChild ( searchInp );
// Add Clear Button (✕)
var clearBtn = document . createElement ( 'button' );
2026-08-29 19:22:42 +03:00
clearBtn . type = 'button' ;
clearBtn . className = 'yaktube-clear-search-btn' ;
2026-08-29 22:43:55 +03:00
clearBtn . setAttribute ( 'aria-label' , 'Arama Metnini Temizle' );
2026-08-29 19:22:42 +03:00
clearBtn . innerHTML = '✕' ;
2026-08-29 21:47:30 +03:00
pillWrapper . appendChild ( clearBtn );
2026-08-29 19:22:42 +03:00
2026-08-29 22:59:18 +03:00
// Add Search Submit Button (🔍)
var submitBtn = document . createElement ( 'button' );
submitBtn . type = 'submit' ;
submitBtn . className = 'yaktube-search-submit-btn' ;
submitBtn . setAttribute ( 'aria-label' , 'Ara' );
submitBtn . setAttribute ( 'title' , 'Ara' );
submitBtn . innerHTML = '🔍' ;
pillWrapper . appendChild ( submitBtn );
2026-08-29 21:47:30 +03:00
function updateClearState () {
if ( searchInp . value && searchInp . value . trim (). length > 0 ) {
clearBtn . style . display = 'inline-flex' ;
} else {
clearBtn . style . display = 'none' ;
}
}
searchInp . addEventListener ( 'input' , updateClearState );
2026-08-29 22:59:18 +03:00
searchInp . addEventListener ( 'focus' , updateClearState );
2026-08-29 19:22:42 +03:00
clearBtn . addEventListener ( 'click' , function ( e ) {
e . preventDefault ();
e . stopPropagation ();
searchInp . value = '' ;
searchInp . dispatchEvent ( new Event ( 'input' , { bubbles : true }));
searchInp . dispatchEvent ( new Event ( 'change' , { bubbles : true }));
clearBtn . style . display = 'none' ;
searchInp . focus ();
2026-08-29 21:47:30 +03:00
announce ( 'Arama kutusu temizlendi.' );
});
2026-08-29 22:59:18 +03:00
submitBtn . addEventListener ( 'click' , function ( e ) {
var query = ( searchInp . value || '' ). trim ();
if ( query ) {
var form = searchInp . closest ( 'form' );
if ( form ) {
form . dispatchEvent ( new Event ( 'submit' , { bubbles : true , cancelable : true }));
} else {
window . location . href = '/search/videos?search=' + encodeURIComponent ( query );
}
}
});
2026-08-29 19:22:42 +03:00
}
2026-08-29 22:59:18 +03:00
// 3. YouTube Circular Voice Button (🎙️) Beside the Pill Box
var voiceBtn = outerWrap . querySelector ( '.yaktube-voice-search-btn' );
2026-08-29 21:47:30 +03:00
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 = '🎙️' ;
2026-08-29 22:59:18 +03:00
outerWrap . appendChild ( voiceBtn );
2026-08-29 21:47:30 +03:00
voiceBtn . addEventListener ( 'click' , function ( e ) {
e . preventDefault ();
e . stopPropagation ();
openYouTubeVoiceModal ( searchInp );
});
2026-08-29 19:22:42 +03:00
}
2026-08-29 21:47:30 +03:00
// 4. Escape key handling
2026-08-29 19:22:42 +03:00
searchInp . addEventListener ( 'keydown' , function ( e ) {
if ( e . key === 'Escape' || e . keyCode === 27 ) {
if ( searchInp . value && searchInp . value . trim (). length > 0 ) {
searchInp . value = '' ;
searchInp . dispatchEvent ( new Event ( 'input' , { bubbles : true }));
announce ( 'Arama metni temizlendi.' );
} else {
searchInp . blur ();
announce ( 'Arama kutusundan çıkı ldı .' );
}
}
});
}
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' );
2026-08-29 21:47:30 +03:00
if ( searchInp ) {
transformToYouTubeSearchBar ( searchInp , container );
2026-08-29 19:22:42 +03:00
}
});
2026-08-29 22:59:18 +03:00
var headerInputs = document . querySelectorAll ( 'my-header input' );
headerInputs . forEach ( function ( inp ) {
var parent = inp . closest ( 'my-search-typeahead' ) || inp . parentElement ;
if (
parent &&
( inp . type === 'search' ||
inp . type === 'text' ||
( inp . placeholder && inp . placeholder . toLowerCase (). includes ( 'ara' )))
) {
transformToYouTubeSearchBar ( inp , parent );
}
});
2026-08-29 22:43:55 +03:00
// 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 );
2026-08-29 19:22:42 +03:00
}
});
}
2026-08-29 21:47:30 +03:00
// Global Keyboard Shortcuts
2026-08-29 19:22:42 +03:00
document . addEventListener ( 'keydown' , function ( e ) {
var activeTag = document . activeElement ? document . activeElement . tagName . toLowerCase () : '' ;
var isInputActive = activeTag === 'input' || activeTag === 'textarea' ;
2026-08-29 21:47:30 +03:00
// Shift + V: Open YouTube Voice Modal
2026-08-29 19:22:42 +03:00
if ( e . shiftKey && ( e . key === 'V' || e . key === 'v' ) && ! isInputActive ) {
e . preventDefault ();
var searchInp = document . querySelector (
'my-search-typeahead input, my-header input[type="search"], input[type="search"]'
);
2026-08-29 21:47:30 +03:00
openYouTubeVoiceModal ( searchInp );
}
2026-08-29 22:59:18 +03:00
// Escape: Close Voice Modal
2026-08-29 21:47:30 +03:00
else if ( e . key === 'Escape' || e . keyCode === 27 ) {
var modal = document . getElementById ( 'yaktube-voice-modal' );
if ( modal && modal . style . display === 'flex' ) {
e . preventDefault ();
closeYouTubeVoiceModal ();
2026-08-29 19:22:42 +03:00
}
2026-08-29 21:47:30 +03:00
}
// / : Focus Search Input
else if ( e . key === '/' && ! isInputActive && ! e . ctrlKey && ! e . metaKey ) {
2026-08-29 19:22:42 +03:00
e . preventDefault ();
var searchInp = document . querySelector (
'my-search-typeahead input, my-header input[type="search"], input[type="search"]'
);
if ( searchInp ) {
searchInp . focus ();
searchInp . select ();
announce ( 'Arama kutusuna odaklanı ldı .' );
}
}
});
2026-08-29 12:42:36 +03:00
// 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 , '"' ) +
' videosunu YakTube\'dan hemen izle">▶️ Hemen İzle</a>'
: '<button class="yaktube-import-btn yaktube-related-import" data-url="' +
item . url +
'" data-title="' +
item . title . replace ( /"/g , '"' ) +
'" style="font-size:11px; padding:6px 10px;" aria-label="' +
item . title . replace ( /"/g , '"' ) +
' 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 , '"' ) +
'">' +
'<div class="yaktube-thumb-wrap">' +
'<img class="yaktube-thumb-img" src="' +
item . thumbnail +
'" alt="' +
item . title . replace ( /"/g , '"' ) +
'" 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 , '&' )
. replace ( /</g , '<' )
. replace ( />/g , '>' )
. 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 . indexOf ( 'YakTubeMobileApp' ) !== - 1 ||
window . matchMedia ( '(display-mode: standalone)' ). matches ||
window . navigator . standalone === true ;
var isMobileDevice = /Android|iPhone|iPad|iPod|Mobile|Tablet/i . test ( navigator . userAgent );
var deferredInstallPrompt = null ;
window . addEventListener ( 'beforeinstallprompt' , function ( e ) {
if ( isInsideYakTubeApp ) return ;
e . preventDefault ();
deferredInstallPrompt = e ;
injectPWAInstallButton ();
});
function injectPWAInstallButton () {
if ( isInsideYakTubeApp ) {
var existBtn = document . getElementById ( 'yaktube-pwa-install-btn' );
if ( existBtn ) {
existBtn . setAttribute ( 'aria-hidden' , 'true' );
existBtn . remove ();
}
return ;
}
if ( ! deferredInstallPrompt || document . getElementById ( 'yaktube-pwa-install-btn' )) return ;
var headerButtons =
document . querySelector ( 'my-header .buttons-container' ) ||
document . querySelector ( '.buttons-container' ) ||
document . querySelector ( 'my-header' );
if ( ! headerButtons ) return ;
var installBtn = document . createElement ( 'button' );
installBtn . id = 'yaktube-pwa-install-btn' ;
installBtn . className = 'yaktube-pwa-btn' ;
installBtn . setAttribute ( 'type' , 'button' );
installBtn . setAttribute ( 'aria-label' , 'YakTube Mobil Uygulaması nı Yükle' );
installBtn . style . cssText =
'background: linear-gradient(135deg, #ff8f37, #f97316); color: #000; border: none; padding: 6px 14px; border-radius: 20px; font-weight: 700; font-size: 12px; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; margin-right: 8px; box-shadow: 0 4px 12px rgba(255,143,55,0.4);' ;
installBtn . innerHTML = '📲 Uygulamayı Yükle' ;
installBtn . addEventListener ( 'click' , function () {
if ( deferredInstallPrompt ) {
deferredInstallPrompt . prompt ();
deferredInstallPrompt . userChoice . then ( function ( choiceResult ) {
if ( choiceResult . outcome === 'accepted' ) {
installBtn . remove ();
announce ( 'YakTube uygulaması cihazı nı za yükleniyor.' );
}
deferredInstallPrompt = null ;
});
}
});
headerButtons . prepend ( installBtn );
}
function injectApkDownloadButton () {
// 1. If inside APK: NEVER show APK button
if ( isInsideYakTubeApp ) {
var existBtnApp = document . getElementById ( 'yaktube-apk-download-btn' );
if ( existBtnApp ) {
existBtnApp . setAttribute ( 'aria-hidden' , 'true' );
existBtnApp . remove ();
}
return ;
}
// 2. If on Desktop: NEVER show in top header (keep header clean)
if ( ! isMobileDevice ) {
var existBtnDesktop = document . getElementById ( 'yaktube-apk-download-btn' );
if ( existBtnDesktop ) {
existBtnDesktop . setAttribute ( 'aria-hidden' , 'true' );
existBtnDesktop . remove ();
}
return ;
}
// 3. If on Mobile Web: Show in header
if ( document . getElementById ( 'yaktube-apk-download-btn' )) return ;
var headerButtons =
document . querySelector ( 'my-header .buttons-container' ) ||
document . querySelector ( 'my-header .header-right' ) ||
document . querySelector ( '.buttons-container' ) ||
document . querySelector ( 'my-header' );
if ( ! headerButtons ) return ;
var apkBtn = document . createElement ( 'a' );
apkBtn . id = 'yaktube-apk-download-btn' ;
apkBtn . href = '/yaktube.apk' ;
apkBtn . className = 'yaktube-apk-btn' ;
apkBtn . setAttribute ( 'download' , 'YakTube_Mobile_v1.0.apk' );
apkBtn . setAttribute ( 'aria-label' , 'YakTube Android APK İndir (4.5 MB)' );
apkBtn . style . cssText =
'background: #1e293b; color: #ff8f37; border: 1px solid #ff8f37; padding: 6px 12px; border-radius: 20px; font-weight: 700; font-size: 11px; text-decoration: none; display: inline-flex; align-items: center; gap: 4px; margin-right: 6px; transition: all 0.2s; box-shadow: 0 4px 12px rgba(0,0,0,0.3);' ;
apkBtn . innerHTML = '🤖 APK İndir (4.5 MB)' ;
headerButtons . prepend ( apkBtn );
}
function injectSidebarApkLink () {
// If inside APK: NEVER show in sidebar
if ( isInsideYakTubeApp ) {
var existLink = document . getElementById ( 'yaktube-sidebar-apk-link' );
if ( existLink ) {
existLink . setAttribute ( 'aria-hidden' , 'true' );
existLink . remove ();
}
return ;
}
if ( document . getElementById ( 'yaktube-sidebar-apk-link' )) return ;
var menuContainer = document . querySelector ( 'my-menu, .menu, .main-menu, .nav-list' );
if ( ! menuContainer ) return ;
var sidebarLink = document . createElement ( 'a' );
sidebarLink . id = 'yaktube-sidebar-apk-link' ;
sidebarLink . href = '/yaktube.apk' ;
sidebarLink . className = 'menu-link item-link' ;
sidebarLink . setAttribute ( 'download' , 'YakTube_Mobile_v1.0.apk' );
sidebarLink . setAttribute ( 'aria-label' , 'YakTube Android Uygulaması nı İndir (4.5 MB)' );
sidebarLink . style . cssText =
'display: flex; align-items: center; gap: 10px; padding: 10px 16px; margin: 8px 12px; background: rgba(255, 143, 55, 0.1); color: #ff8f37; border: 1px solid rgba(255, 143, 55, 0.3); border-radius: 8px; font-weight: 700; font-size: 13px; text-decoration: none;' ;
sidebarLink . innerHTML = '<span>🤖</span> <span>YakTube Android APK (4.5 MB)</span>' ;
menuContainer . appendChild ( sidebarLink );
}
setInterval ( function () {
checkAndInjectSearchResults ();
2026-08-29 19:22:42 +03:00
injectSearchEnhancements ();
2026-08-29 12:42:36 +03:00
checkAndInjectRelatedVideos ( false );
restoreActiveLiveStream ();
checkAndInjectWatchPageComments ();
injectPWAInstallButton ();
injectApkDownloadButton ();
injectSidebarApkLink ();
}, 800 );
restoreActiveLiveStream ();
window . addEventListener ( 'popstate' , function () {
currentActiveVideoKey = null ;
checkAndInjectSearchResults ();
checkAndInjectRelatedVideos ( true );
checkAndInjectWatchPageComments ();
});
checkAndInjectWatchPageComments ();
})();