feature: add hourly forecast, precipitation, and icon hints to weather extractor
Add HourlyForecast struct and Hourly field to WeatherData for hourly temperature/condition data. Add Precipitation (int, -1 if unavailable) and IconHint (from aria-label/title/alt attributes) to both DayForecast and HourlyForecast. This enables downstream consumers like mort to replace inline DuckDuckGo scraping with a single GetWeather() call. Closes #51 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,14 +20,26 @@ type WeatherData struct {
|
||||
Humidity string
|
||||
Wind string
|
||||
Forecast []DayForecast
|
||||
Hourly []HourlyForecast
|
||||
}
|
||||
|
||||
// DayForecast holds a single day's forecast.
|
||||
type DayForecast struct {
|
||||
Day string
|
||||
HighTemp float64
|
||||
LowTemp float64
|
||||
Condition string
|
||||
Day string
|
||||
HighTemp float64
|
||||
LowTemp float64
|
||||
Condition string
|
||||
Precipitation int // percentage 0-100, -1 if unavailable
|
||||
IconHint string // icon type from element attributes (e.g. "PartlyCloudy", "Snow")
|
||||
}
|
||||
|
||||
// HourlyForecast holds a single hour's forecast.
|
||||
type HourlyForecast struct {
|
||||
Time string
|
||||
Temp float64
|
||||
Condition string
|
||||
Precipitation int // percentage 0-100, -1 if unavailable
|
||||
IconHint string // icon type from element attributes (e.g. "MostlyCloudy", "Rain")
|
||||
}
|
||||
|
||||
// GetWeather extracts weather data from DuckDuckGo's weather widget.
|
||||
@@ -106,6 +118,7 @@ func extractWeather(doc extractor.Node) (*WeatherData, error) {
|
||||
// Daily forecast
|
||||
_ = doc.ForEach("div.module--weather .module__forecast-day", func(n extractor.Node) error {
|
||||
var day DayForecast
|
||||
day.Precipitation = -1
|
||||
|
||||
days := n.Select(".forecast-day__name")
|
||||
if len(days) > 0 {
|
||||
@@ -129,9 +142,65 @@ func extractWeather(doc extractor.Node) (*WeatherData, error) {
|
||||
day.Condition, _ = dayConds[0].Text()
|
||||
}
|
||||
|
||||
precips := n.Select(".forecast-day__precip")
|
||||
if len(precips) > 0 {
|
||||
txt, _ := precips[0].Text()
|
||||
day.Precipitation = int(parse.NumericOnly(txt))
|
||||
}
|
||||
|
||||
day.IconHint = extractIconHint(n.Select(".forecast-day__icon"))
|
||||
|
||||
data.Forecast = append(data.Forecast, day)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Hourly forecast
|
||||
_ = doc.ForEach("div.module--weather .module__hourly-item", func(n extractor.Node) error {
|
||||
var hour HourlyForecast
|
||||
hour.Precipitation = -1
|
||||
|
||||
times := n.Select(".hourly-item__time")
|
||||
if len(times) > 0 {
|
||||
hour.Time, _ = times[0].Text()
|
||||
}
|
||||
|
||||
temps := n.Select(".hourly-item__temp")
|
||||
if len(temps) > 0 {
|
||||
txt, _ := temps[0].Text()
|
||||
hour.Temp = parse.NumericOnly(txt)
|
||||
}
|
||||
|
||||
conds := n.Select(".hourly-item__condition")
|
||||
if len(conds) > 0 {
|
||||
hour.Condition, _ = conds[0].Text()
|
||||
}
|
||||
|
||||
precips := n.Select(".hourly-item__precip")
|
||||
if len(precips) > 0 {
|
||||
txt, _ := precips[0].Text()
|
||||
hour.Precipitation = int(parse.NumericOnly(txt))
|
||||
}
|
||||
|
||||
hour.IconHint = extractIconHint(n.Select(".hourly-item__icon"))
|
||||
|
||||
data.Hourly = append(data.Hourly, hour)
|
||||
return nil
|
||||
})
|
||||
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
// extractIconHint reads the icon type from an element's aria-label, title, or alt attribute.
|
||||
func extractIconHint(nodes extractor.Nodes) string {
|
||||
if len(nodes) == 0 {
|
||||
return ""
|
||||
}
|
||||
n := nodes[0]
|
||||
for _, attr := range []string{"aria-label", "title", "alt"} {
|
||||
v, _ := n.Attr(attr)
|
||||
if v != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user