From d7b986a0ad2d8b0391f5711c4a37d60922b1f470 Mon Sep 17 00:00:00 2001 From: madraceee Date: Tue, 1 Oct 2024 20:56:43 +0530 Subject: [PATCH 01/22] Added main funcs and func to get conditions --- backends/weatherapi.com.go | 146 +++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 backends/weatherapi.com.go diff --git a/backends/weatherapi.com.go b/backends/weatherapi.com.go new file mode 100644 index 0000000..7e9920c --- /dev/null +++ b/backends/weatherapi.com.go @@ -0,0 +1,146 @@ +package backends + +import ( + "encoding/json" + "flag" + "fmt" + "io" + "net/http" + "time" + + "github.com/schachmat/wego/iface" +) + +type weatherApiResponse struct { + Location struct { + Name string `json:"name"` + Country string `json:"country"` + } `json:"location"` + Forcast struct { + List []forcastBlock `json:"forcastday"` + } `json:"forcast"` +} + +type forcastBlock struct { + Date time.Time `json:"date"` + Day struct { + TempC float32 `json:"avgtemp_c"` + Humidity int `json:"avghumidity"` + MaxWindSpeed float32 `json:"maxwind_kph"` + Weather struct { + Description string `json:"text"` + Code int `json:"code"` + } `json:"condition"` + } `json:"day"` +} + +type weatherApiConfig struct { + apiKey string + debug bool +} + +func (c *weatherApiConfig) Setup() { + flag.StringVar(&c.apiKey, "wth-api-key", "", "weatherapi backend: the api `Key` to use") + flag.BoolVar(&c.debug, "wth-debug", false, "weatherapi backend: print raw requests and responses") +} + +func (c *weatherApiConfig) fetch(url string) (*weatherApiResponse, error) { + res, err := http.Get(url) + if c.debug { + fmt.Printf("Fetching %s\n", url) + } + if err != nil { + return nil, fmt.Errorf("Unable to get (%s) %v", url, err) + } + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("Unable to read response body (%s): %v", url, err) + } + + if c.debug { + fmt.Printf("Response (%s):\n%s\n", url, string(body)) + } + + var resp weatherApiResponse + if err := json.Unmarshal(body, &resp); err != nil { + return nil, fmt.Errorf("Unable to unmarshal response (%s): %v\nThe json body is: %s", url, err, string(body)) + } + + return &resp, nil +} + +func (c *weatherApiConfig) parseCond(forcastInfo forcastBlock) (iface.Cond, error) { + var ret iface.Cond + codemap := map[int]iface.WeatherCode{ + 1000: iface.CodeSunny, + 1003: iface.CodePartlyCloudy, + 1006: iface.CodeCloudy, + 1009: iface.CodeVeryCloudy, + 1030: iface.CodeVeryCloudy, + 1063: iface.CodeLightRain, + 1066: iface.CodeLightSnowShowers, + 1069: iface.CodeLightSnowShowers, + 1071: iface.CodeLightShowers, + 1087: iface.CodeThunderyShowers, + 1114: iface.CodeHeavySnow, + 1117: iface.CodeHeavySnow, + 1135: iface.CodeFog, + 1147: iface.CodeFog, + 1150: iface.CodeLightRain, + 1153: iface.CodeLightRain, + 1168: iface.CodeLightRain, + 1171: iface.CodeHeavyRain, + 1180: iface.CodeLightRain, + 1183: iface.CodeLightRain, + 1186: iface.CodeHeavyRain, + 1189: iface.CodeHeavyRain, + 1192: iface.CodeHeavyRain, + 1195: iface.CodeHeavyRain, + 1198: iface.CodeLightRain, + 1201: iface.CodeHeavyRain, + 1204: iface.CodeLightSleet, + 1207: iface.CodeLightSleetShowers, + 1210: iface.CodeLightSnow, + 1213: iface.CodeLightSnow, + 1216: iface.CodeHeavySnow, + 1219: iface.CodeHeavySnow, + 1222: iface.CodeHeavySnow, + 1225: iface.CodeHeavySnow, + 1237: iface.CodeHeavySnow, + 1240: iface.CodeLightShowers, + 1243: iface.CodeHeavyShowers, + 1246: iface.CodeThunderyShowers, + 1249: iface.CodeLightSleetShowers, + 1252: iface.CodeLightSleetShowers, + 1255: iface.CodeLightSnowShowers, + 1258: iface.CodeHeavySnowShowers, + 1261: iface.CodeLightSnowShowers, + 1264: iface.CodeHeavySnowShowers, + 1273: iface.CodeThunderyShowers, + 1276: iface.CodeThunderyHeavyRain, + 1279: iface.CodeThunderySnowShowers, + 1282: iface.CodeThunderySnowShowers, + } + + ret.Code = iface.CodeUnknown + ret.Desc = forcastInfo.Day.Weather.Description + ret.Humidity = &(forcastInfo.Day.Humidity) + ret.TempC = &(forcastInfo.Day.TempC) + ret.WindspeedKmph = &(forcastInfo.Day.MaxWindSpeed) + + if val, ok := codemap[forcastInfo.Day.Weather.Code]; ok { + ret.Code = val + } + + return ret, nil +} + +func (c *weatherApiConfig) Fetch(location string, numdays int) iface.Data { + + return iface.Data{} +} + +func init() { + iface.AllBackends["weatherapi"] = &weatherApiConfig{} +} From 150878a8eaa06726ac15a154a0bcd0c2daff4582 Mon Sep 17 00:00:00 2001 From: madraceee Date: Tue, 1 Oct 2024 21:15:47 +0530 Subject: [PATCH 02/22] Added func to handle daily data --- backends/weatherapi.com.go | 39 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/backends/weatherapi.com.go b/backends/weatherapi.com.go index 7e9920c..809a166 100644 --- a/backends/weatherapi.com.go +++ b/backends/weatherapi.com.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "io" + "log" "net/http" "time" @@ -22,8 +23,9 @@ type weatherApiResponse struct { } type forcastBlock struct { - Date time.Time `json:"date"` - Day struct { + Date time.Time `json:"date"` + DateEpoch int64 `json:"date_epocj"` + Day struct { TempC float32 `json:"avgtemp_c"` Humidity int `json:"avghumidity"` MaxWindSpeed float32 `json:"maxwind_kph"` @@ -70,6 +72,37 @@ func (c *weatherApiConfig) fetch(url string) (*weatherApiResponse, error) { return &resp, nil } +func (c *weatherApiConfig) parseDaily(dataBlock []forcastBlock, numdays int) []iface.Day { + var forcast []iface.Day + var day *iface.Day + + for _, data := range dataBlock { + slot, err := c.parseCond(data) + if err != nil { + log.Println("Error parsing weather condition:", err) + continue + } + if day == nil { + day = new(iface.Day) + day.Date = slot.Time + } + if day.Date.Day() == slot.Time.Day() { + day.Slots = append(day.Slots, slot) + } + if day.Date.Day() != slot.Time.Day() { + forcast = append(forcast, *day) + if len(forcast) >= numdays { + break + } + day = new(iface.Day) + day.Date = slot.Time + day.Slots = append(day.Slots, slot) + } + } + + return forcast +} + func (c *weatherApiConfig) parseCond(forcastInfo forcastBlock) (iface.Cond, error) { var ret iface.Cond codemap := map[int]iface.WeatherCode{ @@ -133,6 +166,8 @@ func (c *weatherApiConfig) parseCond(forcastInfo forcastBlock) (iface.Cond, erro ret.Code = val } + ret.Time = time.Unix(forcastInfo.DateEpoch, 0) + return ret, nil } From bbc138d4f7390f8cb01821ac188c9f64eab4c891 Mon Sep 17 00:00:00 2001 From: madraceee Date: Tue, 1 Oct 2024 22:23:27 +0530 Subject: [PATCH 03/22] Fixed spelling issue --- backends/openweathermap.org.go | 8 +- backends/weatherapi.com.go | 218 +++++++++++++++++++++------------ 2 files changed, 146 insertions(+), 80 deletions(-) diff --git a/backends/openweathermap.org.go b/backends/openweathermap.org.go index 1eae102..0ca04c4 100644 --- a/backends/openweathermap.org.go +++ b/backends/openweathermap.org.go @@ -22,12 +22,12 @@ type openWeatherConfig struct { type openWeatherResponse struct { Cod string `json:"cod"` City struct { - Name string `json:"name"` - Country string `json:"country"` - TimeZone int64 `json: "timezone"` + Name string `json:"name"` + Country string `json:"country"` + TimeZone int64 `json: "timezone"` // sunrise/sunset are once per call SunRise int64 `json: "sunrise"` - SunSet int64 `json: "sunset"` + SunSet int64 `json: "sunset"` } `json:"city"` List []dataBlock `json:"list"` } diff --git a/backends/weatherapi.com.go b/backends/weatherapi.com.go index 809a166..ccfffa4 100644 --- a/backends/weatherapi.com.go +++ b/backends/weatherapi.com.go @@ -17,14 +17,14 @@ type weatherApiResponse struct { Name string `json:"name"` Country string `json:"country"` } `json:"location"` - Forcast struct { - List []forcastBlock `json:"forcastday"` - } `json:"forcast"` + Forecast struct { + List []forecastBlock `json:"forecastday"` + } `json:"forecast"` } -type forcastBlock struct { +type forecastBlock struct { Date time.Time `json:"date"` - DateEpoch int64 `json:"date_epocj"` + DateEpoch int64 `json:"date_epoch"` Day struct { TempC float32 `json:"avgtemp_c"` Humidity int `json:"avghumidity"` @@ -33,79 +33,35 @@ type forcastBlock struct { Description string `json:"text"` Code int `json:"code"` } `json:"condition"` + Hour []hourlyWeather `json:"hour"` } `json:"day"` } +type hourlyWeather struct { + TimeEpoch int64 `json:"time_epoch"` + TempC float32 `json:"temp_c"` + FeelsLikeC float32 `json:"feelslike_c"` + Humidity int `json:"humidity"` + Condition struct { + Code int `json:"code"` + Desc string `json:"text"` + } `json:"condition"` + WindspeedKmph *float32 `json:"wind_kph"` + WinddirDegree int `json:"wind_degree"` + ChanceOfRainPercent int `json:"chance_of_rain"` +} + type weatherApiConfig struct { apiKey string debug bool } -func (c *weatherApiConfig) Setup() { - flag.StringVar(&c.apiKey, "wth-api-key", "", "weatherapi backend: the api `Key` to use") - flag.BoolVar(&c.debug, "wth-debug", false, "weatherapi backend: print raw requests and responses") -} - -func (c *weatherApiConfig) fetch(url string) (*weatherApiResponse, error) { - res, err := http.Get(url) - if c.debug { - fmt.Printf("Fetching %s\n", url) - } - if err != nil { - return nil, fmt.Errorf("Unable to get (%s) %v", url, err) - } - defer res.Body.Close() - body, err := io.ReadAll(res.Body) - if err != nil { - return nil, fmt.Errorf("Unable to read response body (%s): %v", url, err) - } - - if c.debug { - fmt.Printf("Response (%s):\n%s\n", url, string(body)) - } - - var resp weatherApiResponse - if err := json.Unmarshal(body, &resp); err != nil { - return nil, fmt.Errorf("Unable to unmarshal response (%s): %v\nThe json body is: %s", url, err, string(body)) - } - - return &resp, nil -} - -func (c *weatherApiConfig) parseDaily(dataBlock []forcastBlock, numdays int) []iface.Day { - var forcast []iface.Day - var day *iface.Day - - for _, data := range dataBlock { - slot, err := c.parseCond(data) - if err != nil { - log.Println("Error parsing weather condition:", err) - continue - } - if day == nil { - day = new(iface.Day) - day.Date = slot.Time - } - if day.Date.Day() == slot.Time.Day() { - day.Slots = append(day.Slots, slot) - } - if day.Date.Day() != slot.Time.Day() { - forcast = append(forcast, *day) - if len(forcast) >= numdays { - break - } - day = new(iface.Day) - day.Date = slot.Time - day.Slots = append(day.Slots, slot) - } - } - - return forcast -} +const ( + weatherApiURI = "http://api.weatherapi.com/v1/forecast.json?key=%s&q=%s&days=3&aqi=no&alerts=no" +) -func (c *weatherApiConfig) parseCond(forcastInfo forcastBlock) (iface.Cond, error) { - var ret iface.Cond - codemap := map[int]iface.WeatherCode{ +var ( + codemapping = map[int]iface.WeatherCode{ 1000: iface.CodeSunny, 1003: iface.CodePartlyCloudy, 1006: iface.CodeCloudy, @@ -155,25 +111,135 @@ func (c *weatherApiConfig) parseCond(forcastInfo forcastBlock) (iface.Cond, erro 1279: iface.CodeThunderySnowShowers, 1282: iface.CodeThunderySnowShowers, } +) + +func (c *weatherApiConfig) Setup() { + flag.StringVar(&c.apiKey, "wth-api-key", "", "weatherapi backend: the api `Key` to use") + flag.BoolVar(&c.debug, "wth-debug", false, "weatherapi backend: print raw requests and responses") +} + +func (c *weatherApiConfig) fetch(url string) (*weatherApiResponse, error) { + res, err := http.Get(url) + if c.debug { + fmt.Printf("Fetching %s\n", url) + } + if err != nil { + return nil, fmt.Errorf("Unable to get (%s) %v", url, err) + } + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("Unable to read response body (%s): %v", url, err) + } + + if c.debug { + fmt.Printf("Response (%s):\n%s\n", url, string(body)) + } + + var resp weatherApiResponse + if err := json.Unmarshal(body, &resp); err != nil { + return nil, fmt.Errorf("Unable to unmarshal response (%s): %v\nThe json body is: %s", url, err, string(body)) + } + + return &resp, nil +} + +func (c *weatherApiConfig) parseDaily(dataBlock []forecastBlock, numdays int) []iface.Day { + var forecast []iface.Day + var day *iface.Day + + for _, dayData := range dataBlock { + for _, data := range dayData.Day.Hour { + slot, err := c.parseCond(data) + if err != nil { + log.Println("Error parsing weather condition:", err) + continue + } + if day == nil { + day = new(iface.Day) + day.Date = slot.Time + } + if day.Date.Day() == slot.Time.Day() { + day.Slots = append(day.Slots, slot) + } + if day.Date.Day() != slot.Time.Day() { + forecast = append(forecast, *day) + if len(forecast) >= numdays { + break + } + day = new(iface.Day) + day.Date = slot.Time + day.Slots = append(day.Slots, slot) + } + } + } + + return forecast +} + +func (c *weatherApiConfig) parseCond(forecastInfo hourlyWeather) (iface.Cond, error) { + var ret iface.Cond ret.Code = iface.CodeUnknown - ret.Desc = forcastInfo.Day.Weather.Description - ret.Humidity = &(forcastInfo.Day.Humidity) - ret.TempC = &(forcastInfo.Day.TempC) - ret.WindspeedKmph = &(forcastInfo.Day.MaxWindSpeed) + ret.Desc = forecastInfo.Condition.Desc + ret.Humidity = &(forecastInfo.Humidity) + ret.TempC = &(forecastInfo.TempC) + ret.FeelsLikeC = &(forecastInfo.FeelsLikeC) + ret.WindspeedKmph = forecastInfo.WindspeedKmph + ret.WinddirDegree = &forecastInfo.WinddirDegree + ret.ChanceOfRainPercent = &forecastInfo.ChanceOfRainPercent + + if val, ok := codemapping[forecastInfo.Condition.Code]; ok { + ret.Code = val + } - if val, ok := codemap[forcastInfo.Day.Weather.Code]; ok { + ret.Time = time.Unix(forecastInfo.TimeEpoch, 0) + + return ret, nil +} + +func (c *weatherApiConfig) parseCurCond(forecastInfo forecastBlock) (iface.Cond, error) { + var ret iface.Cond + + ret.Code = iface.CodeUnknown + ret.Desc = forecastInfo.Day.Weather.Description + ret.Humidity = &(forecastInfo.Day.Humidity) + ret.TempC = &(forecastInfo.Day.TempC) + + if val, ok := codemapping[forecastInfo.Day.Weather.Code]; ok { ret.Code = val } - ret.Time = time.Unix(forcastInfo.DateEpoch, 0) + ret.Time = time.Unix(forecastInfo.DateEpoch, 0) return ret, nil } func (c *weatherApiConfig) Fetch(location string, numdays int) iface.Data { + var ret iface.Data + + if len(c.apiKey) == 0 { + log.Fatal("No openweathermap.org API key specified.\nYou have to register for one at https://home.openweathermap.org/users/sign_up") + } + + resp, err := c.fetch(fmt.Sprintf(weatherApiURI, c.apiKey, location)) + if err != nil { + log.Fatalf("Failed to fetch weather data: %v\n", err) + } + fmt.Println(resp) + ret.Current, err = c.parseCurCond(resp.Forecast.List[0]) + ret.Location = fmt.Sprintf("%s, %s", resp.Location.Name, resp.Location.Country) + + if err != nil { + log.Fatalf("Failed to fetch weather data: %v\n", err) + } + + if numdays == 0 { + return ret + } + ret.Forecast = c.parseDaily(resp.Forecast.List, numdays) - return iface.Data{} + return ret } func init() { From 7b4369bc123695b7c46f1e77161ed2c34944fc85 Mon Sep 17 00:00:00 2001 From: madraceee Date: Tue, 1 Oct 2024 22:38:05 +0530 Subject: [PATCH 04/22] Fixed current Todo - Fix forecast --- backends/weatherapi.com.go | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/backends/weatherapi.com.go b/backends/weatherapi.com.go index ccfffa4..d07b386 100644 --- a/backends/weatherapi.com.go +++ b/backends/weatherapi.com.go @@ -17,14 +17,27 @@ type weatherApiResponse struct { Name string `json:"name"` Country string `json:"country"` } `json:"location"` + Current currentCond `json:"current"` Forecast struct { List []forecastBlock `json:"forecastday"` } `json:"forecast"` } +type currentCond struct { + TempC float32 `json:"temp_c"` + FeelsLikeC float32 `json:"feelslike_c"` + Humidity int `json:"humidity"` + Condition struct { + Code int `json:"code"` + Desc string `json:"text"` + } `json:"condition"` + WindspeedKmph *float32 `json:"wind_kph"` + WinddirDegree int `json:"wind_degree"` + ChanceOfRainPercent int `json:"chance_of_rain"` +} + type forecastBlock struct { - Date time.Time `json:"date"` - DateEpoch int64 `json:"date_epoch"` + DateEpoch int64 `json:"date_epoch"` Day struct { TempC float32 `json:"avgtemp_c"` Humidity int `json:"avghumidity"` @@ -57,7 +70,7 @@ type weatherApiConfig struct { } const ( - weatherApiURI = "http://api.weatherapi.com/v1/forecast.json?key=%s&q=%s&days=3&aqi=no&alerts=no" + weatherApiURI = "https://api.weatherapi.com/v1/forecast.json?key=%s&q=%s&days=3&aqi=no&alerts=no" ) var ( @@ -198,20 +211,22 @@ func (c *weatherApiConfig) parseCond(forecastInfo hourlyWeather) (iface.Cond, er return ret, nil } -func (c *weatherApiConfig) parseCurCond(forecastInfo forecastBlock) (iface.Cond, error) { +func (c *weatherApiConfig) parseCurCond(forecastInfo currentCond) (iface.Cond, error) { var ret iface.Cond ret.Code = iface.CodeUnknown - ret.Desc = forecastInfo.Day.Weather.Description - ret.Humidity = &(forecastInfo.Day.Humidity) - ret.TempC = &(forecastInfo.Day.TempC) + ret.Desc = forecastInfo.Condition.Desc + ret.Humidity = &(forecastInfo.Humidity) + ret.TempC = &(forecastInfo.TempC) + ret.FeelsLikeC = &(forecastInfo.FeelsLikeC) + ret.WindspeedKmph = forecastInfo.WindspeedKmph + ret.WinddirDegree = &forecastInfo.WinddirDegree + ret.ChanceOfRainPercent = &forecastInfo.ChanceOfRainPercent - if val, ok := codemapping[forecastInfo.Day.Weather.Code]; ok { + if val, ok := codemapping[forecastInfo.Condition.Code]; ok { ret.Code = val } - ret.Time = time.Unix(forecastInfo.DateEpoch, 0) - return ret, nil } @@ -226,8 +241,7 @@ func (c *weatherApiConfig) Fetch(location string, numdays int) iface.Data { if err != nil { log.Fatalf("Failed to fetch weather data: %v\n", err) } - fmt.Println(resp) - ret.Current, err = c.parseCurCond(resp.Forecast.List[0]) + ret.Current, err = c.parseCurCond(resp.Current) ret.Location = fmt.Sprintf("%s, %s", resp.Location.Name, resp.Location.Country) if err != nil { From bb96d8d618d5567d216e04710a0b33331d53b155 Mon Sep 17 00:00:00 2001 From: madraceee Date: Tue, 1 Oct 2024 23:13:55 +0530 Subject: [PATCH 05/22] weatherAPI functioning --- backends/weatherapi.com.go | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/backends/weatherapi.com.go b/backends/weatherapi.com.go index d07b386..d33cd0f 100644 --- a/backends/weatherapi.com.go +++ b/backends/weatherapi.com.go @@ -46,8 +46,8 @@ type forecastBlock struct { Description string `json:"text"` Code int `json:"code"` } `json:"condition"` - Hour []hourlyWeather `json:"hour"` } `json:"day"` + Hour []hourlyWeather `json:"hour"` } type hourlyWeather struct { @@ -158,36 +158,26 @@ func (c *weatherApiConfig) fetch(url string) (*weatherApiResponse, error) { } func (c *weatherApiConfig) parseDaily(dataBlock []forecastBlock, numdays int) []iface.Day { - var forecast []iface.Day - var day *iface.Day + var ret []iface.Day - for _, dayData := range dataBlock { - for _, data := range dayData.Day.Hour { - slot, err := c.parseCond(data) + for i, day := range dataBlock { + if i == numdays { + break + } + newDay := new(iface.Day) + newDay.Date = time.Unix(day.DateEpoch, 0) + for _, hour := range day.Hour { + slot, err := c.parseCond(hour) if err != nil { - log.Println("Error parsing weather condition:", err) + log.Println("Error parsing hourly weather condition:", err) continue } - if day == nil { - day = new(iface.Day) - day.Date = slot.Time - } - if day.Date.Day() == slot.Time.Day() { - day.Slots = append(day.Slots, slot) - } - if day.Date.Day() != slot.Time.Day() { - forecast = append(forecast, *day) - if len(forecast) >= numdays { - break - } - day = new(iface.Day) - day.Date = slot.Time - day.Slots = append(day.Slots, slot) - } + newDay.Slots = append(newDay.Slots, slot) } + ret = append(ret, *newDay) } - return forecast + return ret } func (c *weatherApiConfig) parseCond(forecastInfo hourlyWeather) (iface.Cond, error) { From b606c25d5134fde3a66935d39f32d4d2cb833ad6 Mon Sep 17 00:00:00 2001 From: madraceee Date: Wed, 2 Oct 2024 10:56:11 +0530 Subject: [PATCH 06/22] Fixed code mapping in weatherapi --- backends/weatherapi.com.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/backends/weatherapi.com.go b/backends/weatherapi.com.go index d33cd0f..af13dba 100644 --- a/backends/weatherapi.com.go +++ b/backends/weatherapi.com.go @@ -81,23 +81,24 @@ var ( 1009: iface.CodeVeryCloudy, 1030: iface.CodeVeryCloudy, 1063: iface.CodeLightRain, - 1066: iface.CodeLightSnowShowers, - 1069: iface.CodeLightSnowShowers, + 1066: iface.CodeLightSnow, + 1069: iface.CodeLightSleet, 1071: iface.CodeLightShowers, + 1072: iface.CodeLightShowers, 1087: iface.CodeThunderyShowers, 1114: iface.CodeHeavySnow, - 1117: iface.CodeHeavySnow, + 1117: iface.CodeHeavySnowShowers, 1135: iface.CodeFog, 1147: iface.CodeFog, 1150: iface.CodeLightRain, 1153: iface.CodeLightRain, 1168: iface.CodeLightRain, - 1171: iface.CodeHeavyRain, + 1171: iface.CodeLightRain, 1180: iface.CodeLightRain, 1183: iface.CodeLightRain, 1186: iface.CodeHeavyRain, 1189: iface.CodeHeavyRain, - 1192: iface.CodeHeavyRain, + 1192: iface.CodeHeavyShowers, 1195: iface.CodeHeavyRain, 1198: iface.CodeLightRain, 1201: iface.CodeHeavyRain, From bd7691e61a14eef6ff35bfdd7f5fc5a9dc2bf299 Mon Sep 17 00:00:00 2001 From: madraceee Date: Wed, 2 Oct 2024 11:19:08 +0530 Subject: [PATCH 07/22] Added language support and numDays support --- backends/weatherapi.com.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/backends/weatherapi.com.go b/backends/weatherapi.com.go index af13dba..f357e9f 100644 --- a/backends/weatherapi.com.go +++ b/backends/weatherapi.com.go @@ -7,6 +7,7 @@ import ( "io" "log" "net/http" + "strconv" "time" "github.com/schachmat/wego/iface" @@ -67,10 +68,11 @@ type hourlyWeather struct { type weatherApiConfig struct { apiKey string debug bool + lang string } const ( - weatherApiURI = "https://api.weatherapi.com/v1/forecast.json?key=%s&q=%s&days=3&aqi=no&alerts=no" + weatherApiURI = "https://api.weatherapi.com/v1/forecast.json?key=%s&q=%s&days=%s&aqi=no&alerts=no&lang=%s" ) var ( @@ -129,6 +131,7 @@ var ( func (c *weatherApiConfig) Setup() { flag.StringVar(&c.apiKey, "wth-api-key", "", "weatherapi backend: the api `Key` to use") + flag.StringVar(&c.lang, "wth-lang", "en", "weatherapi backend: the `LANGUAGE` to request from weatherapi") flag.BoolVar(&c.debug, "wth-debug", false, "weatherapi backend: print raw requests and responses") } @@ -225,10 +228,10 @@ func (c *weatherApiConfig) Fetch(location string, numdays int) iface.Data { var ret iface.Data if len(c.apiKey) == 0 { - log.Fatal("No openweathermap.org API key specified.\nYou have to register for one at https://home.openweathermap.org/users/sign_up") + log.Fatal("No weatherapi.com API key specified.\nYou have to register for one at https://weatherapi.com/signup.aspx") } - resp, err := c.fetch(fmt.Sprintf(weatherApiURI, c.apiKey, location)) + resp, err := c.fetch(fmt.Sprintf(weatherApiURI, c.apiKey, location, strconv.Itoa(numdays), c.lang)) if err != nil { log.Fatalf("Failed to fetch weather data: %v\n", err) } From 846e050565f1f097d27b285de385dcc49ce04965 Mon Sep 17 00:00:00 2001 From: madraceee Date: Wed, 2 Oct 2024 11:34:23 +0530 Subject: [PATCH 08/22] Removed unused code --- backends/weatherapi.com.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/backends/weatherapi.com.go b/backends/weatherapi.com.go index f357e9f..6b75fd2 100644 --- a/backends/weatherapi.com.go +++ b/backends/weatherapi.com.go @@ -38,17 +38,8 @@ type currentCond struct { } type forecastBlock struct { - DateEpoch int64 `json:"date_epoch"` - Day struct { - TempC float32 `json:"avgtemp_c"` - Humidity int `json:"avghumidity"` - MaxWindSpeed float32 `json:"maxwind_kph"` - Weather struct { - Description string `json:"text"` - Code int `json:"code"` - } `json:"condition"` - } `json:"day"` - Hour []hourlyWeather `json:"hour"` + DateEpoch int64 `json:"date_epoch"` + Hour []hourlyWeather `json:"hour"` } type hourlyWeather struct { From 638bd1194e9b865eddaf048d12186ff1dcf99fac Mon Sep 17 00:00:00 2001 From: madraceee Date: Wed, 2 Oct 2024 14:05:04 +0530 Subject: [PATCH 09/22] Updated README.md with new backend info --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 5c9dea9..ae2c620 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,14 @@ go install github.com/schachmat/wego@latest location=New York wwo-api-key=YOUR_WORLDWEATHERONLINE_API_KEY_HERE ``` +0. __With a [WeatherAPI](https://www.weatherapi.com/) account__ + * You can create an account and get a free API key by [signing up](https://www.weatherapi.com/signup.aspx) + * Update the following `.wegorc` config variables to fit your needs: + ``` + backend=weatherapi + location=New York + wth-api-key=YOUR_WEATHERAPI_API_KEY_HERE + ``` 0. You may want to adjust other preferences like `days`, `units` and `…-lang` as well. Save the file. 0. Run `wego` once again and you should get the weather forecast for the current From 75ab651d52ac41ddb792e5a65b225c4870c7e08e Mon Sep 17 00:00:00 2001 From: peter81git <44998875+peter81git@users.noreply.github.com> Date: Sun, 6 Oct 2024 17:42:44 +0100 Subject: [PATCH 10/22] =?UTF-8?q?Remove=20=C2=BA=20from=20Kelvin=20tempera?= =?UTF-8?q?ture=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug fix for issue #170 Co-authored-by: Pedro Pereira --- iface/iface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iface/iface.go b/iface/iface.go index 0d4ea91..63b654a 100644 --- a/iface/iface.go +++ b/iface/iface.go @@ -123,7 +123,7 @@ func (u UnitSystem) Temp(tempC float32) (res float32, unit string) { } else if u == UnitsImperial { return tempC*1.8 + 32, "°F" } else if u == UnitsSi { - return tempC + 273.16, "°K" + return tempC + 273.16, "K" } log.Fatalln("Unknown unit system:", u) return From 2127ad01f0103aabe38b66dcad877ad12ff5f892 Mon Sep 17 00:00:00 2001 From: peter81git <44998875+peter81git@users.noreply.github.com> Date: Sun, 6 Oct 2024 17:43:54 +0100 Subject: [PATCH 11/22] Openmeteo correction for daily astronomy. (#190) Current openweather api usage does not have daily astronomy. Api "onecall" seems to have. Co-authored-by: Pedro Pereira --- backends/open-meteo.com.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backends/open-meteo.com.go b/backends/open-meteo.com.go index 696ba1e..76747ff 100644 --- a/backends/open-meteo.com.go +++ b/backends/open-meteo.com.go @@ -213,12 +213,13 @@ func (opmeteo *openmeteoConfig) Fetch(location string, numdays int) iface.Data { forecast := opmeteo.parseDaily(resp.Hourly) + for i, _ := range forecast { + forecast[i].Astronomy.Sunset = time.Unix(resp.Daily.Sunset[i], 0) + forecast[i].Astronomy.Sunrise = time.Unix(resp.Daily.Sunrise[i], 0) + } if len(forecast) > 0 { - forecast[0].Astronomy.Sunset = time.Unix(resp.Daily.Sunset[0], 0) - forecast[0].Astronomy.Sunrise = time.Unix(resp.Daily.Sunrise[0], 0) ret.Forecast = forecast } - return ret } From 6578d5ebf3d56132cd3903086db58efebcd78a27 Mon Sep 17 00:00:00 2001 From: intervinn <69168328+intervinn@users.noreply.github.com> Date: Sun, 6 Oct 2024 19:47:46 +0300 Subject: [PATCH 12/22] compact mode for aat (#191) compacts the table and hides cloud icons --- frontends/ascii-art-table.go | 58 ++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/frontends/ascii-art-table.go b/frontends/ascii-art-table.go index 01a85aa..f3cfacc 100644 --- a/frontends/ascii-art-table.go +++ b/frontends/ascii-art-table.go @@ -18,10 +18,12 @@ import ( type aatConfig struct { coords bool monochrome bool - unit iface.UnitSystem + compact bool + + unit iface.UnitSystem } -//TODO: replace s parameter with printf interface? +// TODO: replace s parameter with printf interface? func aatPad(s string, mustLen int) (ret string) { ansiEsc := regexp.MustCompile("\033.*?m") ret = s @@ -283,9 +285,13 @@ func (c *aatConfig) formatCond(cur []string, cond iface.Cond, current bool) (ret }, } - icon, ok := codes[cond.Code] - if !ok { - log.Fatalln("aat-frontend: The following weather code has no icon:", cond.Code) + icon := make([]string, 5) + if !c.compact { + var ok bool + icon, ok = codes[cond.Code] + if !ok { + log.Fatalln("aat-frontend: The following weather code has no icon:", cond.Code) + } } desc := cond.Desc @@ -352,19 +358,45 @@ func (c *aatConfig) printDay(day iface.Day) (ret []string) { } dateFmt := "┤ " + day.Date.Format("Mon 02. Jan") + " ├" - ret = append([]string{ - " ┌─────────────┐ ", - "┌──────────────────────────────┬───────────────────────" + dateFmt + "───────────────────────┬──────────────────────────────┐", - "│ Morning │ Noon └──────┬──────┘ Evening │ Night │", - "├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤"}, - ret...) - return append(ret, - "└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘") + if !c.compact { + ret = append([]string{ + " ┌─────────────┐ ", + "┌──────────────────────────────┬───────────────────────" + dateFmt + "───────────────────────┬──────────────────────────────┐", + "│ Morning │ Noon └──────┬──────┘ Evening │ Night │", + "├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤"}, + ret...) + ret = append(ret, + "└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘") + } else { + merge := func(src string, into string) string { + ret := []rune(into) + for k, v := range src { + ret[k] = v + } + return string(ret) + } + + spaces := (len(ret[0]) / 4) - 3 + bar := strings.Repeat("─", spaces) + + ret = append([]string{ + day.Date.Format("Mon 02. Jan"), + "┌" + merge("Morning", bar) + "┬" + merge("Noon", bar) + "┬" + merge("Evening", bar) + "┬" + merge("Night", bar) + "┐", + }, ret...) + + ret = append(ret, + "└"+bar+"┴"+bar+"┴"+bar+"┴"+bar+"┘", + ) + } + + return ret } func (c *aatConfig) Setup() { flag.BoolVar(&c.coords, "aat-coords", false, "aat-frontend: Show geo coordinates") flag.BoolVar(&c.monochrome, "aat-monochrome", false, "aat-frontend: Monochrome output") + + flag.BoolVar(&c.compact, "aat-compact", false, "aat-frontend: Compact output") } func (c *aatConfig) Render(r iface.Data, unitSystem iface.UnitSystem) { From 57b4c3de309890908e1bfb57abe8759a46cb13ef Mon Sep 17 00:00:00 2001 From: madraceee Date: Tue, 1 Oct 2024 20:56:43 +0530 Subject: [PATCH 13/22] Added main funcs and func to get conditions --- backends/weatherapi.com.go | 146 +++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 backends/weatherapi.com.go diff --git a/backends/weatherapi.com.go b/backends/weatherapi.com.go new file mode 100644 index 0000000..7e9920c --- /dev/null +++ b/backends/weatherapi.com.go @@ -0,0 +1,146 @@ +package backends + +import ( + "encoding/json" + "flag" + "fmt" + "io" + "net/http" + "time" + + "github.com/schachmat/wego/iface" +) + +type weatherApiResponse struct { + Location struct { + Name string `json:"name"` + Country string `json:"country"` + } `json:"location"` + Forcast struct { + List []forcastBlock `json:"forcastday"` + } `json:"forcast"` +} + +type forcastBlock struct { + Date time.Time `json:"date"` + Day struct { + TempC float32 `json:"avgtemp_c"` + Humidity int `json:"avghumidity"` + MaxWindSpeed float32 `json:"maxwind_kph"` + Weather struct { + Description string `json:"text"` + Code int `json:"code"` + } `json:"condition"` + } `json:"day"` +} + +type weatherApiConfig struct { + apiKey string + debug bool +} + +func (c *weatherApiConfig) Setup() { + flag.StringVar(&c.apiKey, "wth-api-key", "", "weatherapi backend: the api `Key` to use") + flag.BoolVar(&c.debug, "wth-debug", false, "weatherapi backend: print raw requests and responses") +} + +func (c *weatherApiConfig) fetch(url string) (*weatherApiResponse, error) { + res, err := http.Get(url) + if c.debug { + fmt.Printf("Fetching %s\n", url) + } + if err != nil { + return nil, fmt.Errorf("Unable to get (%s) %v", url, err) + } + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("Unable to read response body (%s): %v", url, err) + } + + if c.debug { + fmt.Printf("Response (%s):\n%s\n", url, string(body)) + } + + var resp weatherApiResponse + if err := json.Unmarshal(body, &resp); err != nil { + return nil, fmt.Errorf("Unable to unmarshal response (%s): %v\nThe json body is: %s", url, err, string(body)) + } + + return &resp, nil +} + +func (c *weatherApiConfig) parseCond(forcastInfo forcastBlock) (iface.Cond, error) { + var ret iface.Cond + codemap := map[int]iface.WeatherCode{ + 1000: iface.CodeSunny, + 1003: iface.CodePartlyCloudy, + 1006: iface.CodeCloudy, + 1009: iface.CodeVeryCloudy, + 1030: iface.CodeVeryCloudy, + 1063: iface.CodeLightRain, + 1066: iface.CodeLightSnowShowers, + 1069: iface.CodeLightSnowShowers, + 1071: iface.CodeLightShowers, + 1087: iface.CodeThunderyShowers, + 1114: iface.CodeHeavySnow, + 1117: iface.CodeHeavySnow, + 1135: iface.CodeFog, + 1147: iface.CodeFog, + 1150: iface.CodeLightRain, + 1153: iface.CodeLightRain, + 1168: iface.CodeLightRain, + 1171: iface.CodeHeavyRain, + 1180: iface.CodeLightRain, + 1183: iface.CodeLightRain, + 1186: iface.CodeHeavyRain, + 1189: iface.CodeHeavyRain, + 1192: iface.CodeHeavyRain, + 1195: iface.CodeHeavyRain, + 1198: iface.CodeLightRain, + 1201: iface.CodeHeavyRain, + 1204: iface.CodeLightSleet, + 1207: iface.CodeLightSleetShowers, + 1210: iface.CodeLightSnow, + 1213: iface.CodeLightSnow, + 1216: iface.CodeHeavySnow, + 1219: iface.CodeHeavySnow, + 1222: iface.CodeHeavySnow, + 1225: iface.CodeHeavySnow, + 1237: iface.CodeHeavySnow, + 1240: iface.CodeLightShowers, + 1243: iface.CodeHeavyShowers, + 1246: iface.CodeThunderyShowers, + 1249: iface.CodeLightSleetShowers, + 1252: iface.CodeLightSleetShowers, + 1255: iface.CodeLightSnowShowers, + 1258: iface.CodeHeavySnowShowers, + 1261: iface.CodeLightSnowShowers, + 1264: iface.CodeHeavySnowShowers, + 1273: iface.CodeThunderyShowers, + 1276: iface.CodeThunderyHeavyRain, + 1279: iface.CodeThunderySnowShowers, + 1282: iface.CodeThunderySnowShowers, + } + + ret.Code = iface.CodeUnknown + ret.Desc = forcastInfo.Day.Weather.Description + ret.Humidity = &(forcastInfo.Day.Humidity) + ret.TempC = &(forcastInfo.Day.TempC) + ret.WindspeedKmph = &(forcastInfo.Day.MaxWindSpeed) + + if val, ok := codemap[forcastInfo.Day.Weather.Code]; ok { + ret.Code = val + } + + return ret, nil +} + +func (c *weatherApiConfig) Fetch(location string, numdays int) iface.Data { + + return iface.Data{} +} + +func init() { + iface.AllBackends["weatherapi"] = &weatherApiConfig{} +} From 965943d26ac61f110d59230a9622de309e4a71c5 Mon Sep 17 00:00:00 2001 From: madraceee Date: Tue, 1 Oct 2024 21:15:47 +0530 Subject: [PATCH 14/22] Added func to handle daily data --- backends/weatherapi.com.go | 39 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/backends/weatherapi.com.go b/backends/weatherapi.com.go index 7e9920c..809a166 100644 --- a/backends/weatherapi.com.go +++ b/backends/weatherapi.com.go @@ -5,6 +5,7 @@ import ( "flag" "fmt" "io" + "log" "net/http" "time" @@ -22,8 +23,9 @@ type weatherApiResponse struct { } type forcastBlock struct { - Date time.Time `json:"date"` - Day struct { + Date time.Time `json:"date"` + DateEpoch int64 `json:"date_epocj"` + Day struct { TempC float32 `json:"avgtemp_c"` Humidity int `json:"avghumidity"` MaxWindSpeed float32 `json:"maxwind_kph"` @@ -70,6 +72,37 @@ func (c *weatherApiConfig) fetch(url string) (*weatherApiResponse, error) { return &resp, nil } +func (c *weatherApiConfig) parseDaily(dataBlock []forcastBlock, numdays int) []iface.Day { + var forcast []iface.Day + var day *iface.Day + + for _, data := range dataBlock { + slot, err := c.parseCond(data) + if err != nil { + log.Println("Error parsing weather condition:", err) + continue + } + if day == nil { + day = new(iface.Day) + day.Date = slot.Time + } + if day.Date.Day() == slot.Time.Day() { + day.Slots = append(day.Slots, slot) + } + if day.Date.Day() != slot.Time.Day() { + forcast = append(forcast, *day) + if len(forcast) >= numdays { + break + } + day = new(iface.Day) + day.Date = slot.Time + day.Slots = append(day.Slots, slot) + } + } + + return forcast +} + func (c *weatherApiConfig) parseCond(forcastInfo forcastBlock) (iface.Cond, error) { var ret iface.Cond codemap := map[int]iface.WeatherCode{ @@ -133,6 +166,8 @@ func (c *weatherApiConfig) parseCond(forcastInfo forcastBlock) (iface.Cond, erro ret.Code = val } + ret.Time = time.Unix(forcastInfo.DateEpoch, 0) + return ret, nil } From 99e7e95e077fd38745557ca9253a38c48331c668 Mon Sep 17 00:00:00 2001 From: madraceee Date: Tue, 1 Oct 2024 22:23:27 +0530 Subject: [PATCH 15/22] Fixed spelling issue --- backends/openweathermap.org.go | 8 +- backends/weatherapi.com.go | 218 +++++++++++++++++++++------------ 2 files changed, 146 insertions(+), 80 deletions(-) diff --git a/backends/openweathermap.org.go b/backends/openweathermap.org.go index 1eae102..0ca04c4 100644 --- a/backends/openweathermap.org.go +++ b/backends/openweathermap.org.go @@ -22,12 +22,12 @@ type openWeatherConfig struct { type openWeatherResponse struct { Cod string `json:"cod"` City struct { - Name string `json:"name"` - Country string `json:"country"` - TimeZone int64 `json: "timezone"` + Name string `json:"name"` + Country string `json:"country"` + TimeZone int64 `json: "timezone"` // sunrise/sunset are once per call SunRise int64 `json: "sunrise"` - SunSet int64 `json: "sunset"` + SunSet int64 `json: "sunset"` } `json:"city"` List []dataBlock `json:"list"` } diff --git a/backends/weatherapi.com.go b/backends/weatherapi.com.go index 809a166..ccfffa4 100644 --- a/backends/weatherapi.com.go +++ b/backends/weatherapi.com.go @@ -17,14 +17,14 @@ type weatherApiResponse struct { Name string `json:"name"` Country string `json:"country"` } `json:"location"` - Forcast struct { - List []forcastBlock `json:"forcastday"` - } `json:"forcast"` + Forecast struct { + List []forecastBlock `json:"forecastday"` + } `json:"forecast"` } -type forcastBlock struct { +type forecastBlock struct { Date time.Time `json:"date"` - DateEpoch int64 `json:"date_epocj"` + DateEpoch int64 `json:"date_epoch"` Day struct { TempC float32 `json:"avgtemp_c"` Humidity int `json:"avghumidity"` @@ -33,79 +33,35 @@ type forcastBlock struct { Description string `json:"text"` Code int `json:"code"` } `json:"condition"` + Hour []hourlyWeather `json:"hour"` } `json:"day"` } +type hourlyWeather struct { + TimeEpoch int64 `json:"time_epoch"` + TempC float32 `json:"temp_c"` + FeelsLikeC float32 `json:"feelslike_c"` + Humidity int `json:"humidity"` + Condition struct { + Code int `json:"code"` + Desc string `json:"text"` + } `json:"condition"` + WindspeedKmph *float32 `json:"wind_kph"` + WinddirDegree int `json:"wind_degree"` + ChanceOfRainPercent int `json:"chance_of_rain"` +} + type weatherApiConfig struct { apiKey string debug bool } -func (c *weatherApiConfig) Setup() { - flag.StringVar(&c.apiKey, "wth-api-key", "", "weatherapi backend: the api `Key` to use") - flag.BoolVar(&c.debug, "wth-debug", false, "weatherapi backend: print raw requests and responses") -} - -func (c *weatherApiConfig) fetch(url string) (*weatherApiResponse, error) { - res, err := http.Get(url) - if c.debug { - fmt.Printf("Fetching %s\n", url) - } - if err != nil { - return nil, fmt.Errorf("Unable to get (%s) %v", url, err) - } - defer res.Body.Close() - body, err := io.ReadAll(res.Body) - if err != nil { - return nil, fmt.Errorf("Unable to read response body (%s): %v", url, err) - } - - if c.debug { - fmt.Printf("Response (%s):\n%s\n", url, string(body)) - } - - var resp weatherApiResponse - if err := json.Unmarshal(body, &resp); err != nil { - return nil, fmt.Errorf("Unable to unmarshal response (%s): %v\nThe json body is: %s", url, err, string(body)) - } - - return &resp, nil -} - -func (c *weatherApiConfig) parseDaily(dataBlock []forcastBlock, numdays int) []iface.Day { - var forcast []iface.Day - var day *iface.Day - - for _, data := range dataBlock { - slot, err := c.parseCond(data) - if err != nil { - log.Println("Error parsing weather condition:", err) - continue - } - if day == nil { - day = new(iface.Day) - day.Date = slot.Time - } - if day.Date.Day() == slot.Time.Day() { - day.Slots = append(day.Slots, slot) - } - if day.Date.Day() != slot.Time.Day() { - forcast = append(forcast, *day) - if len(forcast) >= numdays { - break - } - day = new(iface.Day) - day.Date = slot.Time - day.Slots = append(day.Slots, slot) - } - } - - return forcast -} +const ( + weatherApiURI = "http://api.weatherapi.com/v1/forecast.json?key=%s&q=%s&days=3&aqi=no&alerts=no" +) -func (c *weatherApiConfig) parseCond(forcastInfo forcastBlock) (iface.Cond, error) { - var ret iface.Cond - codemap := map[int]iface.WeatherCode{ +var ( + codemapping = map[int]iface.WeatherCode{ 1000: iface.CodeSunny, 1003: iface.CodePartlyCloudy, 1006: iface.CodeCloudy, @@ -155,25 +111,135 @@ func (c *weatherApiConfig) parseCond(forcastInfo forcastBlock) (iface.Cond, erro 1279: iface.CodeThunderySnowShowers, 1282: iface.CodeThunderySnowShowers, } +) + +func (c *weatherApiConfig) Setup() { + flag.StringVar(&c.apiKey, "wth-api-key", "", "weatherapi backend: the api `Key` to use") + flag.BoolVar(&c.debug, "wth-debug", false, "weatherapi backend: print raw requests and responses") +} + +func (c *weatherApiConfig) fetch(url string) (*weatherApiResponse, error) { + res, err := http.Get(url) + if c.debug { + fmt.Printf("Fetching %s\n", url) + } + if err != nil { + return nil, fmt.Errorf("Unable to get (%s) %v", url, err) + } + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, fmt.Errorf("Unable to read response body (%s): %v", url, err) + } + + if c.debug { + fmt.Printf("Response (%s):\n%s\n", url, string(body)) + } + + var resp weatherApiResponse + if err := json.Unmarshal(body, &resp); err != nil { + return nil, fmt.Errorf("Unable to unmarshal response (%s): %v\nThe json body is: %s", url, err, string(body)) + } + + return &resp, nil +} + +func (c *weatherApiConfig) parseDaily(dataBlock []forecastBlock, numdays int) []iface.Day { + var forecast []iface.Day + var day *iface.Day + + for _, dayData := range dataBlock { + for _, data := range dayData.Day.Hour { + slot, err := c.parseCond(data) + if err != nil { + log.Println("Error parsing weather condition:", err) + continue + } + if day == nil { + day = new(iface.Day) + day.Date = slot.Time + } + if day.Date.Day() == slot.Time.Day() { + day.Slots = append(day.Slots, slot) + } + if day.Date.Day() != slot.Time.Day() { + forecast = append(forecast, *day) + if len(forecast) >= numdays { + break + } + day = new(iface.Day) + day.Date = slot.Time + day.Slots = append(day.Slots, slot) + } + } + } + + return forecast +} + +func (c *weatherApiConfig) parseCond(forecastInfo hourlyWeather) (iface.Cond, error) { + var ret iface.Cond ret.Code = iface.CodeUnknown - ret.Desc = forcastInfo.Day.Weather.Description - ret.Humidity = &(forcastInfo.Day.Humidity) - ret.TempC = &(forcastInfo.Day.TempC) - ret.WindspeedKmph = &(forcastInfo.Day.MaxWindSpeed) + ret.Desc = forecastInfo.Condition.Desc + ret.Humidity = &(forecastInfo.Humidity) + ret.TempC = &(forecastInfo.TempC) + ret.FeelsLikeC = &(forecastInfo.FeelsLikeC) + ret.WindspeedKmph = forecastInfo.WindspeedKmph + ret.WinddirDegree = &forecastInfo.WinddirDegree + ret.ChanceOfRainPercent = &forecastInfo.ChanceOfRainPercent + + if val, ok := codemapping[forecastInfo.Condition.Code]; ok { + ret.Code = val + } - if val, ok := codemap[forcastInfo.Day.Weather.Code]; ok { + ret.Time = time.Unix(forecastInfo.TimeEpoch, 0) + + return ret, nil +} + +func (c *weatherApiConfig) parseCurCond(forecastInfo forecastBlock) (iface.Cond, error) { + var ret iface.Cond + + ret.Code = iface.CodeUnknown + ret.Desc = forecastInfo.Day.Weather.Description + ret.Humidity = &(forecastInfo.Day.Humidity) + ret.TempC = &(forecastInfo.Day.TempC) + + if val, ok := codemapping[forecastInfo.Day.Weather.Code]; ok { ret.Code = val } - ret.Time = time.Unix(forcastInfo.DateEpoch, 0) + ret.Time = time.Unix(forecastInfo.DateEpoch, 0) return ret, nil } func (c *weatherApiConfig) Fetch(location string, numdays int) iface.Data { + var ret iface.Data + + if len(c.apiKey) == 0 { + log.Fatal("No openweathermap.org API key specified.\nYou have to register for one at https://home.openweathermap.org/users/sign_up") + } + + resp, err := c.fetch(fmt.Sprintf(weatherApiURI, c.apiKey, location)) + if err != nil { + log.Fatalf("Failed to fetch weather data: %v\n", err) + } + fmt.Println(resp) + ret.Current, err = c.parseCurCond(resp.Forecast.List[0]) + ret.Location = fmt.Sprintf("%s, %s", resp.Location.Name, resp.Location.Country) + + if err != nil { + log.Fatalf("Failed to fetch weather data: %v\n", err) + } + + if numdays == 0 { + return ret + } + ret.Forecast = c.parseDaily(resp.Forecast.List, numdays) - return iface.Data{} + return ret } func init() { From d17fc22d4f0d025f443a226c98c8acbbaf2ba678 Mon Sep 17 00:00:00 2001 From: madraceee Date: Tue, 1 Oct 2024 22:38:05 +0530 Subject: [PATCH 16/22] Fixed current Todo - Fix forecast --- backends/weatherapi.com.go | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/backends/weatherapi.com.go b/backends/weatherapi.com.go index ccfffa4..d07b386 100644 --- a/backends/weatherapi.com.go +++ b/backends/weatherapi.com.go @@ -17,14 +17,27 @@ type weatherApiResponse struct { Name string `json:"name"` Country string `json:"country"` } `json:"location"` + Current currentCond `json:"current"` Forecast struct { List []forecastBlock `json:"forecastday"` } `json:"forecast"` } +type currentCond struct { + TempC float32 `json:"temp_c"` + FeelsLikeC float32 `json:"feelslike_c"` + Humidity int `json:"humidity"` + Condition struct { + Code int `json:"code"` + Desc string `json:"text"` + } `json:"condition"` + WindspeedKmph *float32 `json:"wind_kph"` + WinddirDegree int `json:"wind_degree"` + ChanceOfRainPercent int `json:"chance_of_rain"` +} + type forecastBlock struct { - Date time.Time `json:"date"` - DateEpoch int64 `json:"date_epoch"` + DateEpoch int64 `json:"date_epoch"` Day struct { TempC float32 `json:"avgtemp_c"` Humidity int `json:"avghumidity"` @@ -57,7 +70,7 @@ type weatherApiConfig struct { } const ( - weatherApiURI = "http://api.weatherapi.com/v1/forecast.json?key=%s&q=%s&days=3&aqi=no&alerts=no" + weatherApiURI = "https://api.weatherapi.com/v1/forecast.json?key=%s&q=%s&days=3&aqi=no&alerts=no" ) var ( @@ -198,20 +211,22 @@ func (c *weatherApiConfig) parseCond(forecastInfo hourlyWeather) (iface.Cond, er return ret, nil } -func (c *weatherApiConfig) parseCurCond(forecastInfo forecastBlock) (iface.Cond, error) { +func (c *weatherApiConfig) parseCurCond(forecastInfo currentCond) (iface.Cond, error) { var ret iface.Cond ret.Code = iface.CodeUnknown - ret.Desc = forecastInfo.Day.Weather.Description - ret.Humidity = &(forecastInfo.Day.Humidity) - ret.TempC = &(forecastInfo.Day.TempC) + ret.Desc = forecastInfo.Condition.Desc + ret.Humidity = &(forecastInfo.Humidity) + ret.TempC = &(forecastInfo.TempC) + ret.FeelsLikeC = &(forecastInfo.FeelsLikeC) + ret.WindspeedKmph = forecastInfo.WindspeedKmph + ret.WinddirDegree = &forecastInfo.WinddirDegree + ret.ChanceOfRainPercent = &forecastInfo.ChanceOfRainPercent - if val, ok := codemapping[forecastInfo.Day.Weather.Code]; ok { + if val, ok := codemapping[forecastInfo.Condition.Code]; ok { ret.Code = val } - ret.Time = time.Unix(forecastInfo.DateEpoch, 0) - return ret, nil } @@ -226,8 +241,7 @@ func (c *weatherApiConfig) Fetch(location string, numdays int) iface.Data { if err != nil { log.Fatalf("Failed to fetch weather data: %v\n", err) } - fmt.Println(resp) - ret.Current, err = c.parseCurCond(resp.Forecast.List[0]) + ret.Current, err = c.parseCurCond(resp.Current) ret.Location = fmt.Sprintf("%s, %s", resp.Location.Name, resp.Location.Country) if err != nil { From 47e331543006b75ba72eb9ad343f7d49bf5d3ee4 Mon Sep 17 00:00:00 2001 From: madraceee Date: Tue, 1 Oct 2024 23:13:55 +0530 Subject: [PATCH 17/22] weatherAPI functioning --- backends/weatherapi.com.go | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/backends/weatherapi.com.go b/backends/weatherapi.com.go index d07b386..d33cd0f 100644 --- a/backends/weatherapi.com.go +++ b/backends/weatherapi.com.go @@ -46,8 +46,8 @@ type forecastBlock struct { Description string `json:"text"` Code int `json:"code"` } `json:"condition"` - Hour []hourlyWeather `json:"hour"` } `json:"day"` + Hour []hourlyWeather `json:"hour"` } type hourlyWeather struct { @@ -158,36 +158,26 @@ func (c *weatherApiConfig) fetch(url string) (*weatherApiResponse, error) { } func (c *weatherApiConfig) parseDaily(dataBlock []forecastBlock, numdays int) []iface.Day { - var forecast []iface.Day - var day *iface.Day + var ret []iface.Day - for _, dayData := range dataBlock { - for _, data := range dayData.Day.Hour { - slot, err := c.parseCond(data) + for i, day := range dataBlock { + if i == numdays { + break + } + newDay := new(iface.Day) + newDay.Date = time.Unix(day.DateEpoch, 0) + for _, hour := range day.Hour { + slot, err := c.parseCond(hour) if err != nil { - log.Println("Error parsing weather condition:", err) + log.Println("Error parsing hourly weather condition:", err) continue } - if day == nil { - day = new(iface.Day) - day.Date = slot.Time - } - if day.Date.Day() == slot.Time.Day() { - day.Slots = append(day.Slots, slot) - } - if day.Date.Day() != slot.Time.Day() { - forecast = append(forecast, *day) - if len(forecast) >= numdays { - break - } - day = new(iface.Day) - day.Date = slot.Time - day.Slots = append(day.Slots, slot) - } + newDay.Slots = append(newDay.Slots, slot) } + ret = append(ret, *newDay) } - return forecast + return ret } func (c *weatherApiConfig) parseCond(forecastInfo hourlyWeather) (iface.Cond, error) { From cab577f15f913b53639f4dfb9da6fd5713504b8e Mon Sep 17 00:00:00 2001 From: madraceee Date: Wed, 2 Oct 2024 10:56:11 +0530 Subject: [PATCH 18/22] Fixed code mapping in weatherapi --- backends/weatherapi.com.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/backends/weatherapi.com.go b/backends/weatherapi.com.go index d33cd0f..af13dba 100644 --- a/backends/weatherapi.com.go +++ b/backends/weatherapi.com.go @@ -81,23 +81,24 @@ var ( 1009: iface.CodeVeryCloudy, 1030: iface.CodeVeryCloudy, 1063: iface.CodeLightRain, - 1066: iface.CodeLightSnowShowers, - 1069: iface.CodeLightSnowShowers, + 1066: iface.CodeLightSnow, + 1069: iface.CodeLightSleet, 1071: iface.CodeLightShowers, + 1072: iface.CodeLightShowers, 1087: iface.CodeThunderyShowers, 1114: iface.CodeHeavySnow, - 1117: iface.CodeHeavySnow, + 1117: iface.CodeHeavySnowShowers, 1135: iface.CodeFog, 1147: iface.CodeFog, 1150: iface.CodeLightRain, 1153: iface.CodeLightRain, 1168: iface.CodeLightRain, - 1171: iface.CodeHeavyRain, + 1171: iface.CodeLightRain, 1180: iface.CodeLightRain, 1183: iface.CodeLightRain, 1186: iface.CodeHeavyRain, 1189: iface.CodeHeavyRain, - 1192: iface.CodeHeavyRain, + 1192: iface.CodeHeavyShowers, 1195: iface.CodeHeavyRain, 1198: iface.CodeLightRain, 1201: iface.CodeHeavyRain, From fd47779b8a183bcdbfeba371625b921b665758bc Mon Sep 17 00:00:00 2001 From: madraceee Date: Wed, 2 Oct 2024 11:19:08 +0530 Subject: [PATCH 19/22] Added language support and numDays support --- backends/weatherapi.com.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/backends/weatherapi.com.go b/backends/weatherapi.com.go index af13dba..f357e9f 100644 --- a/backends/weatherapi.com.go +++ b/backends/weatherapi.com.go @@ -7,6 +7,7 @@ import ( "io" "log" "net/http" + "strconv" "time" "github.com/schachmat/wego/iface" @@ -67,10 +68,11 @@ type hourlyWeather struct { type weatherApiConfig struct { apiKey string debug bool + lang string } const ( - weatherApiURI = "https://api.weatherapi.com/v1/forecast.json?key=%s&q=%s&days=3&aqi=no&alerts=no" + weatherApiURI = "https://api.weatherapi.com/v1/forecast.json?key=%s&q=%s&days=%s&aqi=no&alerts=no&lang=%s" ) var ( @@ -129,6 +131,7 @@ var ( func (c *weatherApiConfig) Setup() { flag.StringVar(&c.apiKey, "wth-api-key", "", "weatherapi backend: the api `Key` to use") + flag.StringVar(&c.lang, "wth-lang", "en", "weatherapi backend: the `LANGUAGE` to request from weatherapi") flag.BoolVar(&c.debug, "wth-debug", false, "weatherapi backend: print raw requests and responses") } @@ -225,10 +228,10 @@ func (c *weatherApiConfig) Fetch(location string, numdays int) iface.Data { var ret iface.Data if len(c.apiKey) == 0 { - log.Fatal("No openweathermap.org API key specified.\nYou have to register for one at https://home.openweathermap.org/users/sign_up") + log.Fatal("No weatherapi.com API key specified.\nYou have to register for one at https://weatherapi.com/signup.aspx") } - resp, err := c.fetch(fmt.Sprintf(weatherApiURI, c.apiKey, location)) + resp, err := c.fetch(fmt.Sprintf(weatherApiURI, c.apiKey, location, strconv.Itoa(numdays), c.lang)) if err != nil { log.Fatalf("Failed to fetch weather data: %v\n", err) } From 7e10730328aaaab6d9e65eea52c1af7653628868 Mon Sep 17 00:00:00 2001 From: madraceee Date: Wed, 2 Oct 2024 11:34:23 +0530 Subject: [PATCH 20/22] Removed unused code --- backends/weatherapi.com.go | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/backends/weatherapi.com.go b/backends/weatherapi.com.go index f357e9f..6b75fd2 100644 --- a/backends/weatherapi.com.go +++ b/backends/weatherapi.com.go @@ -38,17 +38,8 @@ type currentCond struct { } type forecastBlock struct { - DateEpoch int64 `json:"date_epoch"` - Day struct { - TempC float32 `json:"avgtemp_c"` - Humidity int `json:"avghumidity"` - MaxWindSpeed float32 `json:"maxwind_kph"` - Weather struct { - Description string `json:"text"` - Code int `json:"code"` - } `json:"condition"` - } `json:"day"` - Hour []hourlyWeather `json:"hour"` + DateEpoch int64 `json:"date_epoch"` + Hour []hourlyWeather `json:"hour"` } type hourlyWeather struct { From ccec02f4d51b3e0e9c8affc747806b5bc39251a3 Mon Sep 17 00:00:00 2001 From: madraceee Date: Wed, 2 Oct 2024 14:05:04 +0530 Subject: [PATCH 21/22] Updated README.md with new backend info --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 5c9dea9..ae2c620 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,14 @@ go install github.com/schachmat/wego@latest location=New York wwo-api-key=YOUR_WORLDWEATHERONLINE_API_KEY_HERE ``` +0. __With a [WeatherAPI](https://www.weatherapi.com/) account__ + * You can create an account and get a free API key by [signing up](https://www.weatherapi.com/signup.aspx) + * Update the following `.wegorc` config variables to fit your needs: + ``` + backend=weatherapi + location=New York + wth-api-key=YOUR_WEATHERAPI_API_KEY_HERE + ``` 0. You may want to adjust other preferences like `days`, `units` and `…-lang` as well. Save the file. 0. Run `wego` once again and you should get the weather forecast for the current From c1f3a67a601b037a7b3d4a0e9a8881762b3d40d9 Mon Sep 17 00:00:00 2001 From: madraceee Date: Mon, 7 Oct 2024 10:04:27 +0530 Subject: [PATCH 22/22] fix: changed wth to weather --- README.md | 2 +- backends/weatherapi.com.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ae2c620..8838bc6 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ go install github.com/schachmat/wego@latest ``` backend=weatherapi location=New York - wth-api-key=YOUR_WEATHERAPI_API_KEY_HERE + weather-api-key=YOUR_WEATHERAPI_API_KEY_HERE ``` 0. You may want to adjust other preferences like `days`, `units` and `…-lang` as well. Save the file. diff --git a/backends/weatherapi.com.go b/backends/weatherapi.com.go index 6b75fd2..4a4b2fa 100644 --- a/backends/weatherapi.com.go +++ b/backends/weatherapi.com.go @@ -121,9 +121,9 @@ var ( ) func (c *weatherApiConfig) Setup() { - flag.StringVar(&c.apiKey, "wth-api-key", "", "weatherapi backend: the api `Key` to use") - flag.StringVar(&c.lang, "wth-lang", "en", "weatherapi backend: the `LANGUAGE` to request from weatherapi") - flag.BoolVar(&c.debug, "wth-debug", false, "weatherapi backend: print raw requests and responses") + flag.StringVar(&c.apiKey, "weather-api-key", "", "weatherapi backend: the api `Key` to use") + flag.StringVar(&c.lang, "weather-lang", "en", "weatherapi backend: the `LANGUAGE` to request from weatherapi") + flag.BoolVar(&c.debug, "weather-debug", false, "weatherapi backend: print raw requests and responses") } func (c *weatherApiConfig) fetch(url string) (*weatherApiResponse, error) {