/* DONA_YOUTUBE_V82: public YouTube tutorial viewer; no API key by default. */ (() => { 'use strict'; if (window.DonaYouTubeV82) return; const ID = 'donaYoutubeV82'; const videoIdOK = id => typeof id === 'string' && /^[A-Za-z0-9_-]{11}$/.test(id); const $ = id => document.getElementById(id); const el = (tag, cls, value) => { const n = document.createElement(tag); if (cls) n.className = cls; if (value !== undefined) n.textContent = String(value); return n; }; const button = (title, fn, cls='dy-btn') => { const b = el('button', cls, title); b.type='button'; b.addEventListener('click',fn); return b; }; let player=null, ready=false, active=false, items=[], chosen='', requestBusy=false; let apiPromise=null, playerVersion=0; const label = s => { if($('dyStatus')) $('dyStatus').textContent=s||''; }; const postState = state => fetch('/api/dona/youtube/player-state', { method:'POST', credentials:'same-origin', headers:{'Content-Type':'application/json','X-Alfred-Request':'1'}, body:JSON.stringify({active:state}) }).catch(()=>{}); function initialize(){ const root = document.getElementById('alfredMainV1'); if(!root){setTimeout(initialize,200);return;} const head = root.querySelector('.av-head-actions'); if(head){ const trigger=button('▶',()=>open(),'av-round dy-open-button'); trigger.title='Ouvrir YouTube';trigger.setAttribute('aria-label','Ouvrir le lecteur YouTube'); trigger.id='donaYouTubeOpen';head.prepend(trigger); } const overlay=el('div','dy-overlay');overlay.id=ID;overlay.hidden=true; const panel=el('section','dy-panel'); panel.setAttribute('role','dialog');panel.setAttribute('aria-modal','true');panel.setAttribute('aria-label','Lecteur YouTube de Dona'); const top=el('header','dy-header'); const h=el('div');h.append(el('span','dy-eyebrow','DONA · YOUTUBE'),el('h1','','Tutoriels et vidéos')); top.append(h,button('×',close,'dy-close'));panel.append(top); const search=el('form','dy-search');search.id='dySearchForm'; const input=el('input','dy-input');input.id='dyQuery';input.type='search';input.maxLength=200; input.placeholder='Chercher une vidéo YouTube…';input.setAttribute('aria-label','Rechercher une vidéo');input.required=true; const submit=button('Rechercher',()=>{},'dy-btn dy-primary');submit.type='submit';submit.id='dySubmit'; search.append(input,submit);search.addEventListener('submit',e=>{e.preventDefault();searchFor(input.value);});panel.append(search); const status=el('p','dy-status');status.id='dyStatus';status.setAttribute('role','status');status.setAttribute('aria-live','polite');panel.append(status); const body=el('div','dy-body'); const viewer=el('div','dy-viewer'); const stage=el('div','dy-stage');stage.id='dyStage'; stage.append(el('div','dy-placeholder','Sélectionne un tutoriel pour afficher sa vidéo.')); viewer.append(stage); const controls=el('div','dy-controls'); controls.append(button('↶ 30 s',()=>control({action:'seek',seconds:-30})), button('▶',()=>control({action:'play'})),button('Ⅱ',()=>control({action:'pause'})), button('30 s ↷',()=>control({action:'seek',seconds:30})), button('⛶',()=>control({action:'fullscreen'}))); viewer.append(controls); const link=el('a','dy-external','Voir sur YouTube ↗');link.id='dyExternal';link.hidden=true;link.target='_blank';link.rel='noopener noreferrer'; viewer.append(link); const results=el('div','dy-results'); results.append(el('h2','','Vidéos trouvées')); const cards=el('div','dy-results-list');cards.id='dyResults';results.append(cards); body.append(viewer,results);panel.append(body); panel.append(el('p','dy-note','Vidéos publiques YouTube · Lecture depuis le site officiel · Sans connexion à un compte. Certaines vidéos peuvent refuser l’intégration.')); overlay.append(panel);document.body.append(overlay); overlay.addEventListener('click',e=>{if(e.target===overlay)close();}); window.addEventListener('keydown',e=>{if(e.key==='Escape'&&active)close();}); bindSocket(); } function open(){active=true;$(ID).hidden=false;document.body.classList.add('dy-active');postState(true);} function close(){ active=false;$(ID).hidden=true;document.body.classList.remove('dy-active');postState(false); // Stop playback when leaving YouTube; avoid hidden audio. try { if(player&&typeof player.stopVideo==='function')player.stopVideo(); } catch(_){} } function loadAPI(){ if(window.YT && typeof window.YT.Player==='function')return Promise.resolve(); if(apiPromise)return apiPromise; apiPromise=new Promise((resolve,reject)=>{ const previous=window.onYouTubeIframeAPIReady; window.onYouTubeIframeAPIReady=()=>{try{if(typeof previous==='function')previous();}catch(_){ } resolve();}; const script=document.createElement('script');script.src='https://www.youtube.com/iframe_api'; script.async=true;script.onerror=()=>reject(new Error('Lecteur YouTube inaccessible.')); document.head.append(script); setTimeout(()=>{if(!(window.YT&&window.YT.Player))reject(new Error('Le lecteur YouTube ne répond pas.'));},15000); }); return apiPromise; } async function loadVideo(id){ if(!videoIdOK(id))return; chosen=id;open(); const item=items.find(x=>x.id===id); $('dyExternal').href='https://www.youtube.com/watch?v='+id; $('dyExternal').hidden=false; $('dyStage').replaceChildren(); const mount=el('div','dy-frame');mount.id='dyPlayer';$('dyStage').append(mount); const version=++playerVersion; ready=false; try { if(player&&typeof player.destroy==='function')player.destroy(); }catch(_){ } player=null; label(item ? 'Vidéo sélectionnée : '+item.title : 'Chargement de la vidéo…'); try { await loadAPI();if(version!==playerVersion)return; player=new window.YT.Player('dyPlayer', { width:'100%', height:'100%', videoId:id, playerVars:{autoplay:0,controls:1,playsinline:1,rel:0,origin:window.location.origin}, events:{ onReady:()=>{ready=true;label('Appuie sur Lecture pour démarrer la vidéo.');}, onError:(event)=>label('Cette vidéo ne peut pas être lue ici (code '+event.data+'). Utilise « Voir sur YouTube ».') } }); } catch(err) { if(version===playerVersion){ $('dyStage').replaceChildren(el('p','dy-placeholder','Le lecteur intégré est indisponible. Ouvre cette vidéo sur YouTube.')); label(err.message||'Lecteur indisponible.'); } } } function control(command){ if(!active||!command||!command.action)return; if(command.action==='close'){close();return;} if(!ready||!player){label('Le lecteur n’est pas encore prêt.');return;} try { switch(command.action){ case 'play':player.playVideo();break; case 'pause':player.pauseVideo();break; case 'seek':{ const offset=Number(command.seconds)||0; player.seekTo(Math.max(0,(Number(player.getCurrentTime())||0)+offset),true);break; } case 'fullscreen':{ const frame=$('dyStage').querySelector('iframe'); if(frame&&frame.requestFullscreen)frame.requestFullscreen();break; } } } catch(e){label('Commande vidéo indisponible : '+e.message);} } function showResults(payload){ if(!payload||payload.type!=='dona_youtube')return; items=(Array.isArray(payload.videos)?payload.videos:[]).filter(v=>v&&videoIdOK(v.id)).slice(0,6); $('dyQuery').value=String(payload.query||'').slice(0,200); const list=$('dyResults');list.replaceChildren(); for(const v of items){ const b=button('',()=>loadVideo(v.id),'dy-video-item');b.textContent=''; const img=el('img');img.loading='lazy';img.alt='Miniature : '+String(v.title||'Vidéo'); img.src='https://i.ytimg.com/vi/'+v.id+'/hqdefault.jpg'; const txt=el('span','dy-video-title');txt.append(el('strong','',v.title||'Vidéo YouTube')); if(v.channel)txt.append(el('small','',v.channel));b.append(img,txt);list.append(b); } open(); if(payload.error)label(payload.error); else if(items.length)label(items.length+' vidéo(s) trouvée(s). Choisis celle qui t’intéresse.'); else label('Aucune vidéo exploitable trouvée. Essaie une autre formulation.'); if(items.length)loadVideo(items[0].id); else {chosen='';ready=false;playerVersion++;try{player?.destroy?.()}catch(_){ }player=null; $('dyStage').replaceChildren(el('p','dy-placeholder','Aucun résultat : tente une autre recherche.')); $('dyExternal').hidden=true; } } async function searchFor(query){ query=String(query||'').trim(); if(query.length<2||query.length>200){label('Saisis entre 2 et 200 caractères.');return;} if(requestBusy)return; requestBusy=true;$('dySubmit').disabled=true;open();label('Dona recherche des vidéos via Hermes…'); try { const res=await fetch('/api/dona/youtube/search',{ method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','X-Alfred-Request':'1'}, body:JSON.stringify({query}) }); const data=await res.json(); if(!res.ok||!data.ok)throw new Error(data.error||'Recherche impossible'); showResults({type:'dona_youtube',query:data.query,videos:data.videos}); }catch(e){label(e.message||'Recherche indisponible.');} finally{requestBusy=false;$('dySubmit').disabled=false;} } function bindSocket(){ const socket=window.alfredSocket; if(!socket){setTimeout(bindSocket,250);return;} socket.on('alfred_render',payload=>{if(payload?.type==='dona_youtube')showResults(payload);}); socket.on('dona_youtube_control',command=>control(command)); } window.DonaYouTubeV82={open,close,control,showResults,search:searchFor}; initialize(); })();