123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- // This file is subject to a 1-clause BSD license.
- // Its contents can be found in the enclosed LICENSE file.
- package owm
- import (
- "encoding/json"
- "fmt"
- "log"
- "math"
- "net/url"
- "strings"
- "time"
- "notabug.org/mouz/bot/app/util"
- "notabug.org/mouz/bot/irc"
- "notabug.org/mouz/bot/irc/cmd"
- "notabug.org/mouz/bot/irc/proto"
- )
- const currentWeatherURL = "https://api.openweathermap.org/data/2.5/weather?" +
- "units=%s&mode=json&lang=%s&appid=%s&q=%s"
- // cmdCurrentWeather yields current weather data for a given location.
- func (p *plugin) cmdCurrentWeather(w irc.ResponseWriter, r *irc.Request, params cmd.ParamList) {
- p.m.Lock()
- defer p.m.Unlock()
- if len(p.config.OwmAPIKey) == 0 {
- log.Println("[owm] API key not in configuration")
- _ = proto.PrivMsg(w, r.Target, TextNoWeather, r.SenderName)
- return
- }
- loc := getLocation(r)
- if cached, ok := p.currentWeatherCache[loc]; ok {
- // If the cached result is younger than the timeout, print its
- // contents for the user and exit. Otherwise, consider it stale,
- // delete it and re-fetch.
- if time.Since(cached.Timestamp) <= CacheTimeout {
- log.Println("[owm] using cached item for current weather")
- err := sendCurrentWeather(w, r, cached)
- if err != nil {
- log.Println("[owm]", err)
- }
- return
- }
- delete(p.currentWeatherCache, loc)
- }
- // Fetch new response.
- data, err := p.fetch(currentWeatherURL, loc)
- if err != nil {
- log.Println("[owm]", err)
- return
- }
- err = p.sendResult(w, r, data)
- if err != nil {
- log.Println("[owm]", err)
- return
- }
- }
- // sendResult sends the result back to the user. The result is either
- // an error message or a meaningful current weather report.
- func (p *plugin) sendResult(w irc.ResponseWriter, r *irc.Request, data []byte) error {
- // unmarshal into a generic map, just to be able to detect the kind
- // of data that came in from the weather server
- var js map[string]interface{}
- err := json.Unmarshal(data, &js)
- if err != nil {
- return err
- }
- if len(js) == 2 {
- // assume error message from weather server
- var oerr owmError
- err := json.Unmarshal(data, &oerr)
- if err != nil {
- return err
- }
- return sendError(w, r, &oerr)
- }
- // assume meaningful current weather response
- var owmcur owmCurrent
- owmcur.Timestamp = time.Now()
- err = json.Unmarshal(data, &owmcur)
- if err != nil {
- return err
- }
- loc := getLocation(r)
- p.currentWeatherCache[loc] = &owmcur
- return sendCurrentWeather(w, r, &owmcur)
- }
- // getLocation extracts the requested location
- func getLocation(r *irc.Request) string {
- l := ""
- for _, s := range r.Fields(1) {
- l += url.QueryEscape(strings.ToLower(s)) + " "
- }
- return strings.TrimSuffix(l, " ")
- }
- // sendError reports on an error message coming from
- // openweathermap.org back to the user
- func sendError(w irc.ResponseWriter, r *irc.Request, oErr *owmError) error {
- loc, _ := url.QueryUnescape(getLocation(r))
- str, ok := oErr.Code.(string)
- if ok && str == "404" {
- return proto.PrivMsg(w, r.Target, TextNotFound404, util.Bold(loc))
- } else {
- log.Printf("[owm] weather server returned error %+v %s [%s]\n", oErr.Code, oErr.Message, loc)
- }
- return nil
- }
- // sendCurrentWeather formats the current weather and sends it back to
- // the user
- func sendCurrentWeather(w irc.ResponseWriter, r *irc.Request, owmcur *owmCurrent) error {
- location := util.Bold(owmcur.Name)
- location += fmt.Sprintf(" (%s)", owmcur.Sys.Country)
- temp := fmt.Sprintf(TextTempExact, owmcur.Main.Temp)
- if owmcur.Main.TempMax-owmcur.Main.TempMin > 1 {
- temp = fmt.Sprintf(TextTempRange, owmcur.Main.TempMin, owmcur.Main.TempMax)
- }
- precip := ""
- if owmcur.Snow.OneH > 0 {
- precip = fmt.Sprintf(TextSnowFall, owmcur.Snow.OneH)
- } else if owmcur.Snow.ThreeH > 0 {
- precip = fmt.Sprintf(TextSnowFall, owmcur.Snow.ThreeH/3)
- } else if owmcur.Rain.OneH > 0 {
- precip = fmt.Sprintf(TextRainFall, owmcur.Rain.OneH)
- } else if owmcur.Rain.ThreeH > 0 {
- precip = fmt.Sprintf(TextRainFall, owmcur.Rain.ThreeH/3)
- }
- index := int(0.5+owmcur.Wind.Deg/(360/16)) % 16
- winddir := TextWindDirection[index]
- speedBf := math.Pow(float64(owmcur.Wind.Speed)/0.836, 0.666)
- loc, _ := time.LoadLocation(TextTimeZone)
- t := time.Unix(owmcur.Sys.Sunrise, 0).In(loc)
- sunrise := t.Format(TextTimeFormat)
- t = time.Unix(owmcur.Sys.Sunset, 0).In(loc)
- sunset := t.Format(TextTimeFormat)
- sunriseset := fmt.Sprintf(TextSunRiseSet, sunrise, sunset)
- return proto.PrivMsg(w, r.Target, TextCurrentWeatherDisplay,
- r.SenderName,
- location,
- temp,
- owmcur.Weather[0].Description,
- precip,
- owmcur.Main.Pressure,
- owmcur.Main.Humidity,
- owmcur.Wind.Speed,
- speedBf,
- winddir,
- sunriseset,
- )
- }
- // owmError defines the API response when the request for the
- // current weather was not succesful.
- type owmError struct {
- Code interface{} `json:"cod"`
- Message string `json:"message"`
- }
- // owmCurrent defines the API response when the request for the
- // current weather was succesful.
- type owmCurrent struct {
- Timestamp time.Time
- Coord coordinate `json:"coord"`
- Weather []weatherCondition `json:"weather"`
- Base string `json:"base"`
- Main mainWeather `json:"main"`
- Visibility int `json:"visibility"`
- Wind windData `json:"wind"`
- Clouds cloudiness `json:"clouds"`
- Rain rainVolume `json:"rain"`
- Snow snowVolume `json:"snow"`
- Dt int64 `json:"dt"`
- Sys sysData `json:"sys"`
- ID int `json:"id"`
- Name string `json:"name"`
- Cod int `json:"cod"`
- }
- type coordinate struct {
- Lon float32 `json:"lon"`
- Lat float32 `json:"lat"`
- }
- type weatherCondition struct {
- ID int `json:"id"`
- Main string `json:"main"`
- Description string `json:"description"`
- Icon string `json:"icon"`
- }
- type mainWeather struct {
- Temp float32 `json:"temp"`
- Pressure float32 `json:"pressure"`
- Humidity float32 `json:"humidity"`
- TempMin float32 `json:"temp_min"`
- TempMax float32 `json:"temp_max"`
- SeaLevel float32 `json:"sea_level"`
- GroundLevel float32 `json:"grnd_level"`
- }
- type windData struct {
- Speed float32 `json:"speed"`
- Deg float32 `json:"deg"`
- }
- type cloudiness struct {
- All int `json:"all"`
- }
- type rainVolume struct {
- OneH float32 `json:"1h"`
- ThreeH float32 `json:"3h"`
- }
- type snowVolume struct {
- OneH float32 `json:"1h"`
- ThreeH float32 `json:"3h"`
- }
- type sysData struct {
- Type int `json:"type"`
- ID int `json:"id"`
- Message float32 `json:"message"`
- Country string `json:"country"`
- Sunrise int64 `json:"sunrise"`
- Sunset int64 `json:"sunset"`
- }
|