package weather import ( "context" "encoding/json" "fmt" "io" "net/http" "net/url" "time" ) type OpenMeteoProvider struct { httpClient *http.Client } func NewOpenMeteoProvider() *OpenMeteoProvider { return &OpenMeteoProvider{ httpClient: &http.Client{Timeout: 10 * time.Second}, } } func (p *OpenMeteoProvider) SetClient(c *http.Client) { p.httpClient = c } type geoResult struct { Name string `json:"name"` Latitude float64 `json:"latitude"` Longitude float64 `json:"longitude"` Country string `json:"country"` } type geoResponse struct { Results []geoResult `json:"results"` } type currentWeather struct { Temperature float64 `json:"temperature"` WMO int `json:"weathercode"` WindSpeed float64 `json:"windspeed"` } type forecastResponse struct { CurrentWeather currentWeather `json:"current_weather"` } var wmoCodes = map[int]string{ 0: "ясно", 1: "преимущественно ясно", 2: "облачно", 3: "пасмурно", 45: "туман", 51: "морось", 61: "дождь", 71: "снег", 95: "гроза", } func (p *OpenMeteoProvider) CurrentWeather(ctx context.Context, location string) (Weather, error) { lat, lon, name, err := p.geocode(ctx, location) if err != nil { return Weather{}, fmt.Errorf("open-meteo: geocode: %w", err) } u := fmt.Sprintf("https://api.open-meteo.com/v1/forecast?latitude=%.4f&longitude=%.4f¤t_weather=true", lat, lon) req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) if err != nil { return Weather{}, fmt.Errorf("open-meteo: request: %w", err) } resp, err := p.httpClient.Do(req) if err != nil { return Weather{}, fmt.Errorf("open-meteo: http: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return Weather{}, fmt.Errorf("open-meteo: read: %w", err) } var forecast forecastResponse if err := json.Unmarshal(body, &forecast); err != nil { return Weather{}, fmt.Errorf("open-meteo: decode: %w", err) } condition := wmoCodes[forecast.CurrentWeather.WMO] if condition == "" { condition = "неизвестно" } return Weather{ Location: name, Temperature: forecast.CurrentWeather.Temperature, Condition: condition, }, nil } func (p *OpenMeteoProvider) geocode(ctx context.Context, location string) (lat, lon float64, name string, err error) { u := fmt.Sprintf("https://geocoding-api.open-meteo.com/v1/search?name=%s&count=1&language=ru&format=json", url.QueryEscape(location)) req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) if err != nil { return 0, 0, "", err } resp, err := p.httpClient.Do(req) if err != nil { return 0, 0, "", err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return 0, 0, "", err } var geo geoResponse if err := json.Unmarshal(body, &geo); err != nil { return 0, 0, "", err } if len(geo.Results) == 0 { return 0, 0, "", fmt.Errorf("location %q not found", location) } r := geo.Results[0] return r.Latitude, r.Longitude, r.Name, nil }