1(function () { 2 const cfg = () => window.RQST_CONFIG; 3 const programRefreshTimer = { 1: null, 2: null }; 4 let initialLoad = true; 5 6 function chileToGMT(hhmm) { 7 const [h, m] = hhmm.trim().split(':').map(Number); 8 const base = new Date(); 9 base.setHours(h, m, 0, 0); 10 const clStr = base.toLocaleString('en-US', { timeZone: cfg().schedule.timezone }); 11 const clDate = new Date(clStr); 12 return { 13 txt: clDate.toUTCString().slice(17, 22), 14 mins: clDate.getUTCHours() * 60 + clDate.getUTCMinutes() 15 }; 16 } 17 18 function isCurrentBlock(start, end, nowMin) { 19 if (start.mins < end.mins) return nowMin >= start.mins && nowMin < end.mins; 20 if (start.mins > end.mins) return nowMin >= start.mins || nowMin < end.mins; 21 return true; 22 } 23 24 function normalizeCover(value) { 25 const raw = String(value || '').trim(); 26 if (!raw) return cfg().fallbackCover; 27 28 // La planilla guarda siempre el nombre lógico, sin extensión. 29 // Se normaliza por seguridad y se fuerza el formato PNG local. 30 const fileName = raw.replace(/\.png$/i, '') + '.png'; 31 const base = `${cfg().programCoverPath || 'assets/img/covers/'}${fileName}`; 32 const version = String(cfg().programCoverVersion || '').trim(); 33 return version ? `${base}?v=${encodeURIComponent(version)}` : base; 34 } 35 36 function createCell(className, text) { 37 const cell = document.createElement('span'); 38 cell.className = className; 39 cell.textContent = text; 40 cell.title = text; 41 return cell; 42 } 43 44 function render(items) { 45 const roller = document.getElementById('program-roller'); 46 if (!roller) return; 47 roller.replaceChildren(); 48 49 const list = document.createElement('div'); 50 list.className = 'schedule-list'; 51 52 items.forEach(it => { 53 const row = document.createElement('div'); 54 row.className = `roll-item${it.current ? ' current' : ''}`; 55 if (it.current) row.setAttribute('aria-current', 'true'); 56 57 const time = document.createElement('span'); 58 time.className = 'schedule-time'; 59 time.title = `${it.startTxt}â${it.endTxt} GMT`; 60 const range = document.createElement('strong'); 61 range.textContent = `${it.startTxt}`; 62 time.appendChild(range); 63 if (it.current) { 64 const badge = document.createElement('small'); 65 badge.textContent = 'NOW'; 66 time.appendChild(badge); 67 } 68 69 row.append( 70 time, 71 createCell('schedule-program', it.ch1), 72 createCell('schedule-program', it.ch2) 73 ); 74 list.appendChild(row); 75 }); 76 77 roller.appendChild(list); 78 requestAnimationFrame(() => { 79 roller.querySelector('.roll-item.current')?.scrollIntoView({ block: 'center', behavior: 'smooth' }); 80 }); 81 } 82 83 function applyProgramCover(signalId, coverValue, forceDisplay = false) { 84 const covers = window.RQST_COVERS; 85 if (!covers) return; 86 87 const changed = covers.setProgramCover(signalId, normalizeCover(coverValue), 'RQST Program'); 88 if (!changed && !forceDisplay) return false; 89 90 // Una carátula editorial nueva âal iniciar o al cambiar de bloqueâ se 91 // protege exactamente por el tiempo configurado. Al terminar, forzamos 92 // metadata para evitar esperar al ciclo general de 15 segundos. 93 const durationMs = Math.max(0, Number(cfg().temporaryCoverMs || 10000)); 94 const program = covers.getProgramCover(signalId); 95 covers.showTemporaryCover(signalId, program.src, { 96 durationMs, 97 sourceText: 'RQST Program' 98 }); 99 100 if (programRefreshTimer[signalId]) clearTimeout(programRefreshTimer[signalId]); 101 programRefreshTimer[signalId] = setTimeout(() => { 102 programRefreshTimer[signalId] = null; 103 window.RQST_METADATA?.update(signalId); 104 }, durationMs + 100); 105 return true; 106 } 107 108 function applyCurrent(items) { 109 const current = items.find(it => it.current); 110 if (!current) return; 111 window.RQST_UI?.setProgram(1, current.ch1 || cfg().signals[1].defaultProgram); 112 window.RQST_UI?.setProgram(2, current.ch2 || cfg().signals[2].defaultProgram); 113 const forceDisplay = initialLoad; 114 applyProgramCover(1, current.cover1, forceDisplay); 115 applyProgramCover(2, current.cover2, forceDisplay); 116 initialLoad = false;
117 } 118 119 function load() { 120 const url = `https://opensheet.elk.sh/${cfg().schedule.sheetId}/${cfg().schedule.sheetName}`; 121 return window.RQST_NET.fetchJson(url, { timeoutMs: cfg().network?.timeoutMs || 7000, cache: 'no-store' }) 122 .then(data => { 123 const now = new Date(); 124 const nowMin = now.getUTCHours() * 60 + now.getUTCMinutes(); 125 const items = []; 126 127 for (const row of data) { 128 if (row.Begin?.trim() === '<EOT>') break; 129 if (!row.Begin || !row.End || !row['RQST 1'] || !row['RQST 2']) continue; 130 const start = chileToGMT(row.Begin); 131 const end = chileToGMT(row.End); 132 items.push({ 133 startTxt: start.txt, 134 endTxt: end.txt, 135 ch1: row['RQST 1'].trim(), 136 ch2: row['RQST 2'].trim(), 137 cover1: String(row['COVER 1'] || '').trim(), 138 cover2: String(row['COVER 2'] || '').trim(), 139 current: isCurrentBlock(start, end, nowMin) 140 }); 141 } 142 render(items); 143 applyCurrent(items); 144 }) 145 .catch(err => { 146 console.warn('Scheduler load error:', err); 147 if (initialLoad) { 148 initialLoad = false; 149 [1, 2].forEach(signalId => { 150 const covers = window.RQST_COVERS; 151 const program = covers.getProgramCover(signalId); 152 const durationMs = Math.max(0, Number(cfg().temporaryCoverMs || 10000)); 153 covers.showTemporaryCover(signalId, program.src, { durationMs, sourceText: 'RQST Program' }); 154 setTimeout(() => window.RQST_METADATA?.update(signalId), durationMs + 100); 155 }); 156 } 157 }); 158 } 159 160 window.RQST_SCHEDULE = { 161 init() { 162 load(); 163 setInterval(load, 60000); 164 }, 165 load 166 }; 167})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.