From a2fb233363b602f0656384eca82c518bc1de80aa Mon Sep 17 00:00:00 2001 From: Cosmo Date: Wed, 22 Apr 2026 10:37:43 +0000 Subject: [PATCH] fix: use Open-Meteo instead of wttr.in (wttr.in response gets truncated in container) --- app/api/weather/route.ts | 113 +++++++++++++++++++++++---------------- 1 file changed, 67 insertions(+), 46 deletions(-) diff --git a/app/api/weather/route.ts b/app/api/weather/route.ts index 8debf10..a35d359 100644 --- a/app/api/weather/route.ts +++ b/app/api/weather/route.ts @@ -1,63 +1,84 @@ export const dynamic = 'force-dynamic'; import { NextResponse } from "next/server"; -function getWeatherCode(desc: string): string { - const d = desc.toLowerCase(); - if (d.includes("overcast")) return "122"; - if (d.includes("partly cloudy") || d.includes("partly") ) return "116"; - if (d.includes("cloudy") || d.includes("cloud")) return "119"; - if (d.includes("drizzle")) return "185"; - if (d.includes("rain") || d.includes("shower")) return "305"; - if (d.includes("snow") || d.includes("blizzard")) return "230"; - if (d.includes("thunder") || d.includes("storm")) return "200"; - if (d.includes("fog") || d.includes("mist")) return "248"; - if (d.includes("sunny") || d.includes("clear")) return "113"; +// Convert WMO weather codes to wttr.in-compatible codes for emoji mapping +function wmoToWttrCode(wmo: number): string { + if (wmo === 0) return "113"; // Clear sunny + if (wmo === 1) return "116"; // Mainly clear + if (wmo === 2) return "116"; // Partly cloudy + if (wmo === 3) return "122"; // Overcast + if (wmo >= 45 && wmo <= 48) return "143"; // Fog + if (wmo >= 51 && wmo <= 55) return "185"; // Drizzle + if (wmo >= 56 && wmo <= 57) return "281"; // Freezing drizzle + if (wmo >= 61 && wmo <= 65) return "305"; // Rain + if (wmo >= 66 && wmo <= 67) return "281"; // Freezing rain + if (wmo >= 71 && wmo <= 77) return "227"; // Snow + if (wmo >= 80 && wmo <= 82) return "305"; // Rain showers + if (wmo >= 85 && wmo <= 86) return "260"; // Snow showers + if (wmo === 95) return "200"; // Thunderstorm + if (wmo >= 96 && wmo <= 99) return "389"; // Thunderstorm with hail return "116"; } +function wmoToDesc(wmo: number): string { + if (wmo === 0) return "Ясно"; + if (wmo === 1) return "Преим. ясно"; + if (wmo === 2) return "Переменная облачность"; + if (wmo === 3) return "Пасмурно"; + if (wmo === 45 || wmo === 48) return "Туман"; + if (wmo >= 51 && wmo <= 55) return "Морось"; + if (wmo >= 61 && wmo <= 65) return "Дождь"; + if (wmo >= 71 && wmo <= 77) return "Снег"; + if (wmo >= 80 && wmo <= 82) return "Ливень"; + if (wmo >= 85 && wmo <= 86) return "Снегопад"; + if (wmo === 95) return "Гроза"; + if (wmo >= 96 && wmo <= 99) return "Гроза с градом"; + return "Облачно"; +} + export async function GET() { try { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 8000); - const res = await fetch( - "https://wttr.in/Saint+Petersburg?format=j1", - { - signal: controller.signal, - cache: "no-store", - headers: { - "User-Agent": "SmartHomeDashboard/1.0", - "Accept": "application/json", - }, - } - ); - clearTimeout(timeout); - - if (!res.ok) throw new Error(`wttr responded ${res.status}`); - const data = await res.json(); - - const current = data.current_condition[0]; - const days = (data.weather || []).slice(0, 3).map((day: any) => { - const hourly = day.hourly?.[4] || day.hourly?.[0] || {}; - const desc = hourly.weatherDesc?.[0]?.value || ""; - const code = hourly.weatherCode || getWeatherCode(desc); - return { - date: day.date, - maxTemp: day.maxtempC, - minTemp: day.mintempC, - desc, - weatherCode: String(code), - }; + const url = "https://api.open-meteo.com/v1/forecast?" + new URLSearchParams({ + latitude: "59.9343", + longitude: "30.3351", + current: "temperature_2m,relative_humidity_2m,apparent_temperature,weather_code,wind_speed_10m", + daily: "weather_code,temperature_2m_max,temperature_2m_min", + timezone: "Europe/Moscow", + forecast_days: "3", }); + const res = await fetch(url, { + signal: controller.signal, + cache: "no-store", + headers: { "Accept": "application/json" }, + }); + clearTimeout(timeout); + + if (!res.ok) throw new Error(`Open-Meteo responded ${res.status}`); + const data = await res.json(); + + const current = data.current; + const daily = data.daily; + + const forecast = (daily.time || []).slice(0, 3).map((date: string, i: number) => ({ + date, + maxTemp: String(Math.round(daily.temperature_2m_max[i])), + minTemp: String(Math.round(daily.temperature_2m_min[i])), + desc: wmoToDesc(daily.weather_code[i]), + weatherCode: wmoToWttrCode(daily.weather_code[i]), + })); + return NextResponse.json({ - temp: current.temp_C, - feelsLike: current.FeelsLikeC, - humidity: current.humidity, - desc: current.weatherDesc[0]?.value || "", - weatherCode: String(current.weatherCode), - windSpeed: current.windspeedKmph, - forecast: days, + temp: String(Math.round(current.temperature_2m)), + feelsLike: String(Math.round(current.apparent_temperature)), + humidity: String(current.relative_humidity_2m), + desc: wmoToDesc(current.weather_code), + weatherCode: wmoToWttrCode(current.weather_code), + windSpeed: String(Math.round(current.wind_speed_10m)), + forecast, }); } catch (e) { console.error("Weather fetch error:", e);