From b6cb10630dd39bfd666924007d6cbd0f3df3431c Mon Sep 17 00:00:00 2001 From: Him-an-shi Date: Thu, 11 Jun 2026 22:05:07 +0530 Subject: [PATCH 1/2] fixed live weather --- api/widget.js | 9 +--- index.html | 130 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/widgets.js | 7 ++- vercel.json | 17 +++---- 4 files changed, 142 insertions(+), 21 deletions(-) diff --git a/api/widget.js b/api/widget.js index 4c22f29..e1cdf80 100644 --- a/api/widget.js +++ b/api/widget.js @@ -36,22 +36,15 @@ const CACHE_POLICIES = { weather: 'public, max-age=1800, s-maxage=1800, stale-while-revalidate=600', // Daily-change widgets — refresh every hour -<<<<<<< HEAD - date: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', - quote: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', - word: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', - streak: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', - profile: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', -======= date: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', quote: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', + word: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', streak: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', profile: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', marker: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', glass: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', countdown: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', marketplace: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', ->>>>>>> 4140561cbdc6c521444ae7205935e4a2d5ab9bb7 // Static mock content — refresh every 5 minutes music: 'public, max-age=300, s-maxage=300, stale-while-revalidate=120', diff --git a/index.html b/index.html index b42eead..914d010 100644 --- a/index.html +++ b/index.html @@ -818,6 +818,11 @@

Stylish Readme

function $(id){return document.getElementById(id);} function scrollToId(id){ const el=$(id); if(el){el.scrollIntoView({behavior:'smooth',block:'start'});} } function pad(n){return String(n).padStart(2,'0');} + function escXml(s){ + return String(s == null ? '' : s) + .replace(/&/g,'&').replace(//g,'>') + .replace(/"/g,'"').replace(/'/g,'''); + } function getTzTime(tz){ try{ const d = new Date(); @@ -1066,6 +1071,9 @@

Stylish Readme

${themeSwatches()} +
+ Live data: The main preview and generated README embed fetch current weather from Open-Meteo. Gallery thumbnails use sample data only. +
`; } else if (state.widget==='weather'){ @@ -2732,6 +2740,108 @@

Stylish Readme

} } + function weatherCondition(code) { + if (code === 0) return 'Clear'; + if (code === 1 || code === 2) return 'Partly Cloudy'; + if (code === 3) return 'Overcast'; + if (code === 45 || code === 48) return 'Fog'; + if (code >= 51 && code <= 57) return 'Drizzle'; + if (code >= 61 && code <= 67) return 'Rain'; + if (code >= 71 && code <= 77) return 'Snow'; + if (code >= 80 && code <= 82) return 'Showers'; + if (code >= 85 && code <= 86) return 'Snow Showers'; + if (code >= 95) return 'Thunderstorm'; + return 'Weather'; + } + + function weatherGlyph(code) { + if (code === 0) return 'SUN'; + if (code === 1 || code === 2) return 'PART'; + if (code === 3) return 'CLD'; + if (code === 45 || code === 48) return 'FOG'; + if (code >= 51 && code <= 67) return 'RAIN'; + if (code >= 71 && code <= 77) return 'SNOW'; + if (code >= 80 && code <= 82) return 'SHWR'; + if (code >= 85 && code <= 86) return 'SNOW'; + if (code >= 95) return 'STRM'; + return 'WX'; + } + + async function fetchWeatherPreview(city, tempUnit) { + const trimmedCity = String(city || '').trim(); + if (!trimmedCity || !window.fetch) return null; + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), 6500); + try { + const geoUrl = new URL('https://geocoding-api.open-meteo.com/v1/search'); + geoUrl.searchParams.set('name', trimmedCity); + geoUrl.searchParams.set('count', '1'); + geoUrl.searchParams.set('language', 'en'); + geoUrl.searchParams.set('format', 'json'); + const geoRes = await fetch(geoUrl, { signal: controller.signal }); + const geoData = await geoRes.json(); + const loc = geoData && geoData.results && geoData.results[0]; + if (!loc) return null; + + const weatherUrl = new URL('https://api.open-meteo.com/v1/forecast'); + weatherUrl.searchParams.set('latitude', String(loc.latitude)); + weatherUrl.searchParams.set('longitude', String(loc.longitude)); + weatherUrl.searchParams.set('current', 'temperature_2m,weather_code'); + weatherUrl.searchParams.set('temperature_unit', tempUnit === 'F' ? 'fahrenheit' : 'celsius'); + const weatherRes = await fetch(weatherUrl, { signal: controller.signal }); + const weatherData = await weatherRes.json(); + if (!weatherData || !weatherData.current) return null; + return { + city: loc.name, + country: loc.country_code || '', + temp: Math.round(weatherData.current.temperature_2m), + unit: tempUnit === 'F' ? 'F' : 'C', + code: weatherData.current.weather_code + }; + } catch (_) { + return null; + } finally { + clearTimeout(timer); + } + } + + let weatherPreviewKey = ''; + let weatherPreviewSeq = 0; + + function svgWeather(weather=null, status='sample'){ + const theme = themeObj(state.theme); + const borderStyle = theme.border?`stroke:${theme.fg};stroke-width:2;`:''; + const isDetailed = state.weatherStyle === 'detailed'; + const W = isDetailed ? 320 : 200; + const H = isDetailed ? 110 : 80; + const sample = { city: state.city || 'Tokyo', country: '', temp: 24, unit: state.tempUnit, code: 0 }; + const data = weather || sample; + const glyph = status === 'loading' ? '...' : status === 'error' ? 'ERR' : weatherGlyph(data.code); + const tempStr = status === 'loading' || status === 'error' ? '--' : `${data.temp}°${data.unit}`; + const city = data.city || state.city || 'Tokyo'; + const locStr = `${city}${data.country ? ', ' + data.country : ''}`; + const condition = status === 'loading' ? 'Fetching' : status === 'error' ? 'Unavailable' : weatherCondition(data.code); + const header = status === 'live' ? 'CURRENT WEATHER' : status === 'loading' ? 'LOADING WEATHER' : status === 'error' ? 'WEATHER UNAVAILABLE' : 'SAMPLE PREVIEW'; + if (isDetailed) { + return ` + + ${header} + + ${glyph} + ${tempStr} + ${escXml(locStr)} + ${escXml(condition)} + `; + } + return ` + + + ${glyph} + ${tempStr} + ${escXml(city)} + `; + } + function getSvg(){ switch(state.widget){ case 'time': return svgTimeBadge(); @@ -2754,6 +2864,26 @@

Stylish Readme

} function renderPreview(){ + if (state.widget === 'weather') { + const key = [state.city, state.tempUnit, state.weatherStyle, state.theme, state.radius || 0].join('|'); + const stage = $('previewStage'); + if (stage.dataset.weatherKey === key) return; + const seq = ++weatherPreviewSeq; + weatherPreviewKey = key; + stage.dataset.weatherKey = key; + const loadingSvg = svgWeather(null, 'loading'); + stage.innerHTML = loadingSvg; + $('sizeInfo').textContent = (new Blob([loadingSvg]).size/1024).toFixed(1); + fetchWeatherPreview(state.city, state.tempUnit).then(weather=>{ + if (seq !== weatherPreviewSeq || state.widget !== 'weather' || weatherPreviewKey !== key) return; + const svg = weather ? svgWeather(weather, 'live') : svgWeather(null, 'error'); + stage.innerHTML = svg; + $('sizeInfo').textContent = (new Blob([svg]).size/1024).toFixed(1); + }); + return; + } + const stage = $('previewStage'); + if (stage) stage.dataset.weatherKey = ''; $('previewStage').innerHTML = getSvg(); const size = (new Blob([getSvg()]).size/1024).toFixed(1); $('sizeInfo').textContent = size; diff --git a/lib/widgets.js b/lib/widgets.js index 28e5634..d683be7 100644 --- a/lib/widgets.js +++ b/lib/widgets.js @@ -2370,6 +2370,7 @@ function fetchJson(urlObj) { }); req.on('error', () => resolve(null)); req.on('timeout', () => { req.destroy(); resolve(null); }); + req.setTimeout(6500); }); } @@ -2431,9 +2432,11 @@ async function renderWeather(p) { const H = isDetailed ? 110 : 80; if (!weather) { + const cityLabel = p.city ? ` for ${p.city}` : ''; return svgWrap(W, H, ` - City not found + Weather unavailable + ${escXml(cityLabel)} `); } @@ -2642,4 +2645,4 @@ module.exports = { normalizeBool, THEMES, TIMEZONES, COUNTRIES, FLAGS, SKILLS, CODING_PLATFORMS, MUSIC_PLATFORMS, flagSvg, skillIcon, platformBadge -}; \ No newline at end of file +}; diff --git a/vercel.json b/vercel.json index 4f2a5ab..452428b 100644 --- a/vercel.json +++ b/vercel.json @@ -11,22 +11,17 @@ { "source": "/api/(time|clock|timezone|skyline).svg", "headers": [ -<<<<<<< HEAD - { "key": "Content-Type", "value": "image/svg+xml; charset=utf-8" }, - { "key": "Cache-Control", "value": "public, max-age=60, s-maxage=60, stale-while-revalidate=30" }, + { "key": "Content-Type", "value": "image/svg+xml; charset=utf-8" }, + { "key": "Cache-Control", "value": "public, max-age=60, s-maxage=60, stale-while-revalidate=30" }, { "key": "Vary", "value": "Accept-Encoding" }, { "key": "Access-Control-Allow-Origin", "value": "*" } ] }, { - "source": "/api/(date|quote|word|streak|profile).svg", + "source": "/api/weather.svg", "headers": [ - { "key": "Content-Type", "value": "image/svg+xml; charset=utf-8" }, - { "key": "Cache-Control", "value": "public, max-age=3600, s-maxage=3600, stale-while-revalidate=600" }, -======= { "key": "Content-Type", "value": "image/svg+xml; charset=utf-8" }, - { "key": "Cache-Control", "value": "public, max-age=60, s-maxage=60, stale-while-revalidate=30" }, ->>>>>>> 4140561cbdc6c521444ae7205935e4a2d5ab9bb7 + { "key": "Cache-Control", "value": "public, max-age=1800, s-maxage=1800, stale-while-revalidate=600" }, { "key": "Vary", "value": "Accept-Encoding" }, { "key": "Access-Control-Allow-Origin", "value": "*" } ] @@ -41,7 +36,7 @@ ] }, { - "source": "/api/(date|quote|streak|profile|countdown|marker|glass|marketplace).svg", + "source": "/api/(date|quote|word|streak|profile|countdown|marker|glass|marketplace).svg", "headers": [ { "key": "Content-Type", "value": "image/svg+xml; charset=utf-8" }, { "key": "Cache-Control", "value": "public, max-age=3600, s-maxage=3600, stale-while-revalidate=600" }, @@ -59,4 +54,4 @@ ] } ] -} \ No newline at end of file +} From 3b289bc57895655681ec87a71221787ef7efeace Mon Sep 17 00:00:00 2001 From: Him-an-shi Date: Thu, 11 Jun 2026 22:45:31 +0530 Subject: [PATCH 2/2] fixed live weather --- api/widget.js | 18 ++---------- index.html | 75 ++++++++++++++++++++++++++++++++++++++++++-------- lib/widgets.js | 8 +++--- vercel.json | 13 ++------- 4 files changed, 72 insertions(+), 42 deletions(-) diff --git a/api/widget.js b/api/widget.js index 9742f65..ae2e3b8 100644 --- a/api/widget.js +++ b/api/widget.js @@ -36,16 +36,6 @@ const CACHE_POLICIES = { weather: 'public, max-age=1800, s-maxage=1800, stale-while-revalidate=600', // Daily-change widgets — refresh every hour -<<<<<<< HEAD -======= - - date: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', - quote: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', - word: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', - streak: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', - profile: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', - ->>>>>>> 33154487f31ac49fec80f7c3b0d13c9f08c76d8c date: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', quote: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', word: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', @@ -54,12 +44,8 @@ const CACHE_POLICIES = { marker: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', glass: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', countdown: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', - marketplace: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', -<<<<<<< HEAD -======= - - youtube: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', ->>>>>>> 33154487f31ac49fec80f7c3b0d13c9f08c76d8c + marketplace:'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', + youtube: 'public, max-age=3600, s-maxage=3600, stale-while-revalidate=600', // Static mock content — refresh every 5 minutes music: 'public, max-age=300, s-maxage=300, stale-while-revalidate=120', diff --git a/index.html b/index.html index 19b764a..db2df78 100644 --- a/index.html +++ b/index.html @@ -822,6 +822,9 @@

Stylish Readme

showChannel: true, showThumbnail: true }; + let weatherPreviewSeq = 0; + let weatherPreviewKey = ''; + let currentPreviewWidget = ''; // ========== UTIL ========== function $(id){return document.getElementById(id);} @@ -2772,19 +2775,58 @@

Stylish Readme

return `\n${svgBody}\n`; } - function svgWeather(){ + async function fetchWeatherPreview(city, tempUnit) { + if (!city) return null; + try { + const geoUrl = new URL('https://geocoding-api.open-meteo.com/v1/search'); + geoUrl.searchParams.set('name', city); + geoUrl.searchParams.set('count', '1'); + geoUrl.searchParams.set('language', 'en'); + + const geoRes = await fetch(geoUrl.toString()); + if (!geoRes.ok) return null; + const geoData = await geoRes.json(); + if (!geoData || !geoData.results || !geoData.results.length) return null; + + const loc = geoData.results[0]; + const weatherUrl = new URL('https://api.open-meteo.com/v1/forecast'); + weatherUrl.searchParams.set('latitude', loc.latitude); + weatherUrl.searchParams.set('longitude', loc.longitude); + weatherUrl.searchParams.set('current_weather', 'true'); + weatherUrl.searchParams.set('temperature_unit', tempUnit === 'F' ? 'fahrenheit' : 'celsius'); + + const weatherRes = await fetch(weatherUrl.toString()); + if (!weatherRes.ok) return null; + const weatherData = await weatherRes.json(); + if (!weatherData || !weatherData.current_weather) return null; + + return { + city: loc.name, + country: loc.country_code || '', + temp: Math.round(weatherData.current_weather.temperature), + unit: tempUnit === 'F' ? '°F' : '°C', + code: weatherData.current_weather.weathercode + }; + } catch (_err) { + return null; + } + } + + function svgWeather(weather, mode = 'live') { const theme = themeObj(state.theme); - const borderStyle = theme.border?`stroke:${theme.fg};stroke-width:2;`:''; + const borderStyle = theme.border ? `stroke:${theme.fg};stroke-width:2;` : ''; const isDetailed = state.weatherStyle === 'detailed'; const W = isDetailed ? 320 : 200; const H = isDetailed ? 110 : 80; const icon = '☀️'; - const tempStr = `24°${state.tempUnit}`; - const city = state.city || 'Tokyo'; + const title = mode === 'loading' ? 'LOADING WEATHER' : mode === 'error' ? 'WEATHER UNAVAILABLE' : 'CURRENT WEATHER'; + const tempStr = weather ? `${weather.temp}${weather.unit}` : `--°${state.tempUnit}`; + const city = weather ? `${weather.city}${weather.country ? ', ' + weather.country.toUpperCase() : ''}` : (state.city || 'Enter a city'); + if (isDetailed) { return ` - CURRENT WEATHER + ${title} ${icon} ${tempStr} ${city} @@ -2822,16 +2864,23 @@

Stylish Readme

} function renderPreview(){ + const stage = $('previewStage'); + if (!stage) return; + if (state.widget === 'weather') { const key = [state.city, state.tempUnit, state.weatherStyle, state.theme, state.radius || 0].join('|'); - const stage = $('previewStage'); - if (stage.dataset.weatherKey === key) return; + const alreadyRendered = currentPreviewWidget === 'weather' && stage.dataset.weatherKey === key; + currentPreviewWidget = 'weather'; + stage.dataset.weatherKey = key; + + if (alreadyRendered) return; + const seq = ++weatherPreviewSeq; weatherPreviewKey = key; - stage.dataset.weatherKey = key; const loadingSvg = svgWeather(null, 'loading'); stage.innerHTML = loadingSvg; $('sizeInfo').textContent = (new Blob([loadingSvg]).size/1024).toFixed(1); + fetchWeatherPreview(state.city, state.tempUnit).then(weather=>{ if (seq !== weatherPreviewSeq || state.widget !== 'weather' || weatherPreviewKey !== key) return; const svg = weather ? svgWeather(weather, 'live') : svgWeather(null, 'error'); @@ -2840,10 +2889,12 @@

Stylish Readme

}); return; } - const stage = $('previewStage'); - if (stage) stage.dataset.weatherKey = ''; - $('previewStage').innerHTML = getSvg(); - const size = (new Blob([getSvg()]).size/1024).toFixed(1); + + currentPreviewWidget = state.widget; + stage.dataset.weatherKey = ''; + const svg = getSvg(); + stage.innerHTML = svg; + const size = (new Blob([svg]).size/1024).toFixed(1); $('sizeInfo').textContent = size; } diff --git a/lib/widgets.js b/lib/widgets.js index 4339265..a63f55d 100644 --- a/lib/widgets.js +++ b/lib/widgets.js @@ -2398,18 +2398,18 @@ async function fetchWeather(city, tempUnit) { const weatherUrl = new URL('https://api.open-meteo.com/v1/forecast'); weatherUrl.searchParams.set('latitude', loc.latitude.toString()); weatherUrl.searchParams.set('longitude', loc.longitude.toString()); - weatherUrl.searchParams.set('current', 'temperature_2m,weather_code'); + weatherUrl.searchParams.set('current_weather', 'true'); weatherUrl.searchParams.set('temperature_unit', tempUnit === 'F' ? 'fahrenheit' : 'celsius'); const weatherData = await fetchJson(weatherUrl); - if (!weatherData || !weatherData.current) return null; + if (!weatherData || !weatherData.current_weather) return null; return { city: loc.name, country: loc.country_code || '', - temp: Math.round(weatherData.current.temperature_2m), + temp: Math.round(weatherData.current_weather.temperature), unit: tempUnit === 'F' ? '°F' : '°C', - code: weatherData.current.weather_code + code: weatherData.current_weather.weathercode }; } diff --git a/vercel.json b/vercel.json index f26014a..d1c43a2 100644 --- a/vercel.json +++ b/vercel.json @@ -25,16 +25,9 @@ { "source": "/api/weather.svg", "headers": [ - - { "key": "Content-Type", "value": "image/svg+xml; charset=utf-8" }, - { "key": "Cache-Control", "value": "public, max-age=1800, s-maxage=1800, stale-while-revalidate=600" }, - - { "key": "Content-Type", "value": "image/svg+xml; charset=utf-8" }, - { "key": "Cache-Control", "value": "public, max-age=3600, s-maxage=3600, stale-while-revalidate=600" }, - { "key": "Content-Type", "value": "image/svg+xml; charset=utf-8" }, - { "key": "Cache-Control", "value": "public, max-age=60, s-maxage=60, stale-while-revalidate=30" }, - - { "key": "Vary", "value": "Accept-Encoding" }, + { "key": "Content-Type", "value": "image/svg+xml; charset=utf-8" }, + { "key": "Cache-Control", "value": "public, max-age=1800, s-maxage=1800, stale-while-revalidate=600" }, + { "key": "Vary", "value": "Accept-Encoding" }, { "key": "Access-Control-Allow-Origin", "value": "*" } ] },