display-epaper/weather/weather.go

180 lines
4.5 KiB
Go

package weather
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/carlmjohnson/requests"
"log/slog"
"net/http"
"os"
"time"
)
type Config struct {
APIKey string
CacheLocation string
}
type Client struct {
config Config
client *http.Client
}
func New(httpClient *http.Client, config Config) *Client {
return &Client{
config: config,
client: httpClient,
}
}
type Prevision struct {
Lat float64 `json:"lat"`
Lon float64 `json:"lon"`
Timezone string `json:"timezone"`
TimezoneOffset int `json:"timezone_offset"`
Current struct {
Dt int `json:"dt"`
Sunrise int `json:"sunrise"`
Sunset int `json:"sunset"`
Temp float64 `json:"temp"`
FeelsLike float64 `json:"feels_like"`
Pressure int `json:"pressure"`
Humidity int `json:"humidity"`
DewPoint float64 `json:"dew_point"`
Uvi float64 `json:"uvi"`
Clouds int `json:"clouds"`
Visibility int `json:"visibility"`
WindSpeed float64 `json:"wind_speed"`
WindDeg int `json:"wind_deg"`
WindGust float64 `json:"wind_gust"`
Weather []struct {
Id int `json:"id"`
Main string `json:"main"`
Description string `json:"description"`
Icon string `json:"icon"`
} `json:"weather"`
} `json:"current"`
Daily []Daily `json:"daily"`
Alerts []struct {
SenderName string `json:"sender_name"`
Event string `json:"event"`
Start int `json:"start"`
End int `json:"end"`
Description string `json:"description"`
Tags []string `json:"tags"`
} `json:"alerts"`
}
var errTooOld = errors.New("prevision is too old")
func loadFromDisk(location string) (Prevision, error) {
stat, err := os.Stat(location)
if err != nil {
return Prevision{}, fmt.Errorf("getting file info: %w", err)
}
if stat.ModTime().Add(10 * time.Minute).Before(time.Now()) {
return Prevision{}, errTooOld
}
file, err := os.Open(location)
if err != nil {
return Prevision{}, fmt.Errorf("opening prevision: %w", err)
}
defer file.Close()
var res Prevision
if err = json.NewDecoder(file).Decode(&res); err != nil {
return Prevision{}, fmt.Errorf("decoding prevision: %w", err)
}
return res, nil
}
func (p Prevision) dumpToDisk(location string) error {
file, err := os.Create(location)
if err != nil {
return fmt.Errorf("creating prevision: %w", err)
}
defer file.Close()
if err = json.NewEncoder(file).Encode(p); err != nil {
return fmt.Errorf("dumping prevision: %w", err)
}
return nil
}
type Daily struct {
Dt int `json:"dt"`
Sunrise int `json:"sunrise"`
Sunset int `json:"sunset"`
Moonrise int `json:"moonrise"`
Moonset int `json:"moonset"`
MoonPhase float64 `json:"moon_phase"`
Summary string `json:"summary"`
Temp struct {
Day float64 `json:"day"`
Min float64 `json:"min"`
Max float64 `json:"max"`
Night float64 `json:"night"`
Eve float64 `json:"eve"`
Morn float64 `json:"morn"`
} `json:"temp"`
FeelsLike struct {
Day float64 `json:"day"`
Night float64 `json:"night"`
Eve float64 `json:"eve"`
Morn float64 `json:"morn"`
} `json:"feels_like"`
Pressure int `json:"pressure"`
Humidity int `json:"humidity"`
DewPoint float64 `json:"dew_point"`
WindSpeed float64 `json:"wind_speed"`
WindDeg int `json:"wind_deg"`
WindGust float64 `json:"wind_gust"`
Weather []Weather `json:"weather"`
Clouds int `json:"clouds"`
Pop float64 `json:"pop"`
Rain float64 `json:"rain"`
Uvi float64 `json:"uvi"`
}
type Weather struct {
Id int `json:"id"`
Main string `json:"main"`
Description string `json:"description"`
Icon string `json:"icon"`
}
func (c *Client) GetWeather(ctx context.Context) (res *Prevision, err error) {
if val, err := loadFromDisk(c.config.CacheLocation); nil == err {
slog.InfoContext(ctx, "found weather in cache")
return &val, nil
}
slog.InfoContext(ctx, "querying weather")
err = requests.URL("https://api.openweathermap.org/data/3.0/onecall").
Client(c.client).
Param("lat", "45.78").
Param("lon", "4.89").
Param("appid", c.config.APIKey).
Param("units", "metric").
Param("lang", "fr").
Param("exclude", "minutely,hourly").
ToJSON(&res).
Fetch(ctx)
if err != nil {
return nil, fmt.Errorf("calling openweathermap: %w", err)
}
if err := res.dumpToDisk(c.config.CacheLocation); err != nil {
slog.ErrorContext(ctx, "error dumping files to disk", "err", err)
}
return res, nil
}