Files
Alfredv2/alfred/app/static/js/dona-youtube-v8-2.js
T
2026-10-30 22:10:03 +01:00

186 lines
9.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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();
})();