/* Dona V6: Spotify Connect — live player, REST-only polling; voice/text keep Hermes. */ (() => { 'use strict'; const $ = id => document.getElementById(id); const state = { data: null, expanded: false, loaded: false, busy: false, timestamp: 0 }; const icons = {play:'▶',pause:'Ⅱ',prev:'⏮',next:'⏭',music:'♫'}; function node(tag,cls,txt){const e=document.createElement(tag); if(cls)e.className=cls;if(txt)e.textContent=txt;return e;} function fmt(ms){const seconds=Math.floor(Math.max(0,ms)/1000);return Math.floor(seconds/60)+':'+String(seconds%60).padStart(2,'0');} function progress(){ const d=state.data;if(!d||!d.available)return 0; return Math.min(d.duration_ms||0, Math.max(0,(d.progress_ms||0)+(d.is_playing?Date.now()-state.timestamp:0))); } function init(){ const main=$('alfredMainV1');if(!main){setTimeout(init,180);return;} if($('donaSpotifyPanel'))return; const head=main.querySelector('.av-head-actions'); if(head){const open=node('button','av-round dona-music-open',icons.music);open.type='button';open.title='Lecteur Spotify';open.setAttribute('aria-label','Ouvrir le lecteur Spotify');open.id='donaSpotifyOpen';head.prepend(open);open.addEventListener('click',()=>toggle(true));} const mini=node('button','dona-spotify-mini');mini.id='donaSpotifyMini';mini.type='button';mini.hidden=true;mini.innerHTML='♫Spotify⌃'; mini.addEventListener('click',()=>toggle(true));document.body.append(mini); const overlay=node('div','dona-spotify-overlay');overlay.id='donaSpotifyOverlay';overlay.hidden=true; overlay.innerHTML=``; overlay.addEventListener('click',e=>{if(e.target===overlay)toggle(false);});document.body.append(overlay); $('donaSpotifyClose').addEventListener('click',()=>toggle(false)); overlay.querySelectorAll('[data-saction]').forEach(b=>b.addEventListener('click',()=>{ const action=b.dataset.saction==='toggle'?(state.data?.is_playing?'pause':'play'):b.dataset.saction; send(action); })); $('donaSpotifySeek').addEventListener('change',e=>send('seek',{position_ms:Number(e.target.value)})); $('donaSpotifyVolume').addEventListener('change',e=>send('volume',{volume_percent:Number(e.target.value)})); $('donaSpotifyDevice').addEventListener('change',e=>{if(e.target.value)send('transfer',{device_id:e.target.value});}); window.addEventListener('keydown',e=>{if(e.key==='Escape'&&state.expanded)toggle(false);}); setInterval(updateBar,1000); setInterval(()=>{if(!document.hidden)load();},12000); document.addEventListener('visibilitychange',()=>{if(!document.hidden)load();}); load(); } function toggle(flag){state.expanded=flag;const overlay=$('donaSpotifyOverlay');overlay.hidden=!flag;document.body.classList.toggle('dona-spotify-open',flag);if(flag)load();} function message(txt){const m=$('donaSpotifyMessage');if(m)m.textContent=txt;} function updateBar(){ const d=state.data;if(!d)return; $('donaSpotifyElapsed').textContent=fmt(progress());$('donaSpotifyDuration').textContent=fmt(d.duration_ms||0); const seek=$('donaSpotifySeek');if(document.activeElement!==seek){seek.max=Math.max(1,d.duration_ms||1);seek.value=Math.min(seek.max,progress());seek.disabled=!d.available;} } function redraw(){ const d=state.data;if(!d)return; const active=Boolean(d.available); $('donaSpotifyMini').hidden=!active; $('donaMiniTitle').textContent=d.title||'Spotify';$('donaMiniArtist').textContent=d.artist||''; $('donaSpotifyTitle').textContent=d.title||(d.devices?.length?'Aucune lecture active':'Aucun appareil connecté'); $('donaSpotifyArtist').textContent=d.artist||(d.devices?.length?'Choisis un appareil et lance un morceau.':'Ouvre Spotify sur un appareil.'); $('donaSpotifyAlbum').textContent=d.album||''; const cover=$('donaSpotifyCover');cover.hidden=!d.cover;if(d.cover)cover.src=d.cover;else cover.removeAttribute('src'); $('donaSpotifyFallback').hidden=!!d.cover; $('donaSpotifyPause').textContent=d.is_playing?icons.pause:icons.play; $('donaSpotifyPause').setAttribute('aria-label',d.is_playing?'Mettre en pause':'Reprendre la lecture'); const select=$('donaSpotifyDevice');const chosen=select.value; select.replaceChildren(); const option=node('option','',d.device_name||'Choisir un appareil');option.value='';select.append(option); (d.devices||[]).forEach(x=>{const o=node('option','',x.name||x.id);o.value=x.id;select.append(o);}); select.value=(d.devices||[]).some(x=>x.id===d.device_id)?d.device_id:(chosen&&[...select.options].some(x=>x.value===chosen)?chosen:''); select.disabled=!d.devices?.length; const volume=$('donaSpotifyVolume');if(document.activeElement!==volume && Number.isFinite(d.volume_percent))volume.value=d.volume_percent; $('donaSpotifyVolumeNum').textContent=Number.isFinite(d.volume_percent)?d.volume_percent+' %':'—'; updateBar(); } async function load(){ if(state.busy)return; state.busy=true; try{ const r=await fetch('/api/dona/spotify/state',{cache:'no-store',credentials:'same-origin'}); const d=await r.json();if(!r.ok||!d.ok)throw Error(d.error||'Spotify non disponible'); state.data=d;state.timestamp=Date.now();state.loaded=true;redraw();message(''); }catch(e){if(state.expanded||!state.loaded)message(e.message||'Spotify indisponible');} finally{state.busy=false;} } async function send(action,extra={}){ message('Commande en cours…'); try{ const r=await fetch('/api/dona/spotify/control',{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},body:JSON.stringify({action,...extra})}); const data=await r.json();if(!r.ok||!data.ok)throw Error(data.error||'Commande refusée'); message('');await new Promise(resolve=>setTimeout(resolve,450));await load(); }catch(e){message(e.message||'Commande indisponible');} } init(); })();