class PetrolAction { constructor($UD, context) { this.$UD = $UD; this.context = context; this.config = { url: '', stationUuid: '', fuelType: '', refreshRate: '1', }; this.refreshTimer = null; this.debounceTimer = 0; this.previousPrice = null; this.lastDelta = null; this.$UD.onDidReceiveSettings(jsn => { if (jsn.context !== this.context) return; const s = jsn.settings || {}; this.config = Object.assign(this.config, s); if (s.previousPrice != null) this.previousPrice = s.previousPrice; if (s.lastDelta != null) this.lastDelta = s.lastDelta; }); } setActive() {} onClear() { if (this.refreshTimer) { clearInterval(this.refreshTimer); this.refreshTimer = null; } if (this.debounceTimer) { clearTimeout(this.debounceTimer); this.debounceTimer = 0; } } setParams(jsn) { const params = (jsn && jsn.param) || {}; this.config = Object.assign(this.config, params); this.$UD.getSettings(this.context); this.start(); } onRun() { this.fetchPrice(); } start() { this.onClear(); this.fetchPrice(); const duration = this.getRefreshDuration(); if (duration > 0) { this.refreshTimer = setInterval(() => this.fetchPrice(), duration * 60 * 1000); } } async fetchPrice() { if (this.debounceTimer) clearTimeout(this.debounceTimer); this.debounceTimer = setTimeout(async () => { if (!this.config.url || !this.config.stationUuid || !this.config.fuelType) { this.renderButton('?', null); return; } try { const response = await fetch(this.config.url); const text = await response.text(); const raw = this.parsePrometheus(text); if (raw) { const current = parseFloat(raw); if (this.previousPrice !== null) { const delta = current - this.previousPrice; if (delta !== 0) this.lastDelta = delta; } this.previousPrice = current; this.renderButton(`${raw}€`, this.lastDelta); this.$UD.setSettings({ ...this.config, previousPrice: this.previousPrice, lastDelta: this.lastDelta }, this.context); } else { this.renderButton('N/A', null); } } catch (e) { this.renderButton('ERR', null); } }, 300); } parsePrometheus(text) { for (const line of text.split('\n')) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; if (trimmed.startsWith('petrol_price_euro') && trimmed.includes(this.config.stationUuid) && trimmed.includes(this.config.fuelType)) { const closingBrace = trimmed.indexOf('}'); if (closingBrace !== -1) return trimmed.slice(closingBrace + 1).trim(); } } return null; } renderButton(priceText, delta) { const canvas = document.createElement('canvas'); canvas.width = 196; canvas.height = 196; const ctx = canvas.getContext('2d'); ctx.fillStyle = '#000000'; ctx.fillRect(0, 0, 196, 196); ctx.strokeStyle = '#000'; ctx.lineWidth = 3; ctx.textBaseline = 'middle'; ctx.textAlign = 'center'; // Fuel type — top, light blue ctx.fillStyle = '#7ec8e3'; ctx.font = 'bold 28px "Source Han Sans SC"'; ctx.strokeText(this.config.fuelType, 98, 45); ctx.fillText(this.config.fuelType, 98, 45); // Price — middle, yellow const fSize = priceText.length > 6 ? 34 - priceText.length : 40; ctx.fillStyle = '#f0c040'; ctx.font = `bold ${fSize}px "Source Han Sans SC"`; ctx.strokeText(priceText, 98, 105); ctx.fillText(priceText, 98, 105); // Delta — bottom if (delta !== null) { const arrow = delta > 0 ? '▲' : '▼'; const color = delta > 0 ? '#ff6b6b' : '#6bff6b'; const deltaText = `${arrow} ${Math.abs(delta).toFixed(3)}`; ctx.fillStyle = color; ctx.font = '18px "Source Han Sans SC"'; ctx.strokeText(deltaText, 98, 142); ctx.fillText(deltaText, 98, 142); } this.$UD.setBaseDataIcon(this.context, canvas.toDataURL('image/png')); } getRefreshDuration() { switch (this.config.refreshRate) { case '1': return 1; case '2': return 2; case '3': return 5; case '4': return 10; case '5': return 30; case '6': return 60; default: return 0; } } }