303 lines
8.0 KiB
Go
303 lines
8.0 KiB
Go
// Copyright 2022 Brian J. Downs
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package openweathermap
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
var (
|
|
errUnitUnavailable = errors.New("unit unavailable")
|
|
errLangUnavailable = errors.New("language unavailable")
|
|
errInvalidKey = errors.New("invalid api key")
|
|
errInvalidOption = errors.New("invalid option")
|
|
errInvalidHttpClient = errors.New("invalid http client")
|
|
errForecastUnavailable = errors.New("forecast unavailable")
|
|
errExcludesUnavailable = errors.New("onecall excludes unavailable")
|
|
errCountOfCityIDs = errors.New("count of ids should not be more than 20 per request")
|
|
)
|
|
|
|
// DataUnits represents the character chosen to represent the temperature notation
|
|
var DataUnits = map[string]string{"C": "metric", "F": "imperial", "K": "internal"}
|
|
var (
|
|
baseURL = "https://api.openweathermap.org/data/2.5/weather?%s"
|
|
onecallURL = "https://api.openweathermap.org/data/3.0/onecall%s"
|
|
iconURL = "https://openweathermap.org/img/w/%s"
|
|
groupURL = "http://api.openweathermap.org/data/2.5/group?%s"
|
|
stationURL = "https://api.openweathermap.org/data/2.5/station?id=%d"
|
|
forecast5Base = "https://api.openweathermap.org/data/2.5/forecast?appid=%s&%s&mode=json&units=%s&lang=%s&cnt=%d"
|
|
forecast16Base = "https://api.openweathermap.org/data/2.5/forecast/daily?appid=%s&%s&mode=json&units=%s&lang=%s&cnt=%d"
|
|
historyURL = "https://api.openweathermap.org/data/2.5/history/%s"
|
|
pollutionURL = "https://api.openweathermap.org/data/2.5/air_pollution?appid=%s&lat=%s&lon=%s"
|
|
uvURL = "https://api.openweathermap.org/data/2.5/"
|
|
dataPostURL = "https://openweathermap.org/data/post"
|
|
)
|
|
|
|
// LangCodes holds all supported languages to be used
|
|
// inspried and sourced from @bambocher (github.com/bambocher)
|
|
var LangCodes = map[string]string{
|
|
"AF": "Afrikaans",
|
|
"AL": "Albanian",
|
|
"AR": "Arabic",
|
|
"AZ": "Azerbaijani",
|
|
"BG": "Bulgarian",
|
|
"CA": "Catalan",
|
|
"CZ": "Czech",
|
|
"DA": "Danish",
|
|
"DE": "German",
|
|
"EL": "Greek",
|
|
"EN": "English",
|
|
"ES": "Spanish",
|
|
"EU": "Basque",
|
|
"FA": "Persian (Farsi)",
|
|
"FI": "Finnish",
|
|
"FR": "French",
|
|
"GL": "Galician",
|
|
"HE": "Hebrew",
|
|
"HI": "Hindi",
|
|
"HR": "Croatian",
|
|
"HU": "Hungarian",
|
|
"ID": "Indonesian",
|
|
"IT": "Italian",
|
|
"JA": "Japanese",
|
|
"KR": "Korean",
|
|
"LA": "Latvian",
|
|
"LT": "Lithuanian",
|
|
"MK": "Macedonian",
|
|
"NL": "Dutch",
|
|
"NO": "Norwegian",
|
|
"PL": "Polish",
|
|
"PT": "Portuguese",
|
|
"PT_BR": "Português Brasil",
|
|
"RO": "Romanian",
|
|
"RU": "Russian",
|
|
"SE": "Swedish",
|
|
"SK": "Slovak",
|
|
"SL": "Slovenian",
|
|
"SP": "Spanish",
|
|
"SR": "Serbian",
|
|
"SV": "Swedish",
|
|
"TH": "Thai",
|
|
"TR": "Turkish",
|
|
"UA": "Ukrainian",
|
|
"UK": "Ukrainian",
|
|
"VI": "Vietnamese",
|
|
"ZH_CN": "Chinese Simplified",
|
|
"ZH_TW": "Chinese Traditional",
|
|
"ZU": "Zulu",
|
|
}
|
|
|
|
// Exclude holds all supported excludes option to be used
|
|
const (
|
|
ExcludeCurrent = "current"
|
|
ExcludeMinutely = "minutely"
|
|
ExcludeHourly = "hourly"
|
|
ExcludeDaily = "daily"
|
|
ExcludeAlerts = "alerts"
|
|
)
|
|
|
|
var Excludes []string = []string{
|
|
ExcludeCurrent,
|
|
ExcludeMinutely,
|
|
ExcludeHourly,
|
|
ExcludeDaily,
|
|
ExcludeAlerts,
|
|
}
|
|
|
|
// Config will hold default settings to be passed into the
|
|
// "NewCurrent, NewForecast, etc}" functions.
|
|
type Config struct {
|
|
Mode string // user choice of JSON or XML
|
|
Unit string // measurement for results to be displayed. F, C, or K
|
|
Lang string // should reference a key in the LangCodes map
|
|
APIKey string // API Key for connecting to the OWM
|
|
Username string // Username for posting data
|
|
Password string // Pasword for posting data
|
|
}
|
|
|
|
// APIError returned on failed API calls.
|
|
type APIError struct {
|
|
Message string `json:"message"`
|
|
COD string `json:"cod"`
|
|
}
|
|
|
|
// Coordinates struct holds longitude and latitude data in returned
|
|
// JSON or as parameter data for requests using longitude and latitude.
|
|
type Coordinates struct {
|
|
Longitude float64 `json:"lon"`
|
|
Latitude float64 `json:"lat"`
|
|
}
|
|
|
|
// Sys struct contains general information about the request
|
|
// and the surrounding area for where the request was made.
|
|
type Sys struct {
|
|
Type int `json:"type"`
|
|
ID int `json:"id"`
|
|
Message float64 `json:"message"`
|
|
Country string `json:"country"`
|
|
Sunrise int `json:"sunrise"`
|
|
Sunset int `json:"sunset"`
|
|
}
|
|
|
|
// Wind struct contains the speed and degree of the wind.
|
|
type Wind struct {
|
|
Speed float64 `json:"speed"`
|
|
Deg float64 `json:"deg"`
|
|
}
|
|
|
|
// Weather struct holds high-level, basic info on the returned
|
|
// data.
|
|
type Weather struct {
|
|
ID int `json:"id"`
|
|
Main string `json:"main"`
|
|
Description string `json:"description"`
|
|
Icon string `json:"icon"`
|
|
}
|
|
|
|
// Main struct contains the temperates, humidity, pressure for the request.
|
|
type Main struct {
|
|
Temp float64 `json:"temp"`
|
|
TempMin float64 `json:"temp_min"`
|
|
TempMax float64 `json:"temp_max"`
|
|
FeelsLike float64 `json:"feels_like"`
|
|
Pressure float64 `json:"pressure"`
|
|
SeaLevel float64 `json:"sea_level"`
|
|
GrndLevel float64 `json:"grnd_level"`
|
|
Humidity int `json:"humidity"`
|
|
}
|
|
|
|
// Clouds struct holds data regarding cloud cover.
|
|
type Clouds struct {
|
|
All int `json:"all"`
|
|
}
|
|
|
|
// return key
|
|
// }
|
|
func setKey(key string) (string, error) {
|
|
if err := ValidAPIKey(key); err != nil {
|
|
return "", err
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
// ValidDataUnit makes sure the string passed in is an accepted
|
|
// unit of measure to be used for the return data.
|
|
func ValidDataUnit(u string) bool {
|
|
for d := range DataUnits {
|
|
if u == d {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ValidLangCode makes sure the string passed in is an
|
|
// acceptable lang code.
|
|
func ValidLangCode(c string) bool {
|
|
for d := range LangCodes {
|
|
if c == d {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ValidDataUnitSymbol makes sure the string passed in is an
|
|
// acceptable data unit symbol.
|
|
func ValidDataUnitSymbol(u string) bool {
|
|
for _, d := range DataUnits {
|
|
if u == d {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ValidExcludes makes sure the string passed in is an
|
|
// acceptable excludes options.
|
|
func ValidExcludes(e []string) (string, error) {
|
|
list := make([]string, 0)
|
|
for _, v := range e {
|
|
vl := strings.ToLower(v)
|
|
notFound := true
|
|
|
|
for _, d := range Excludes {
|
|
if d == vl {
|
|
list = append(list, v)
|
|
notFound = false
|
|
break
|
|
}
|
|
}
|
|
|
|
if notFound {
|
|
return "", errExcludesUnavailable
|
|
}
|
|
}
|
|
return strings.Join(list, ","), nil
|
|
}
|
|
|
|
// ValidAPIKey makes sure that the key given is a valid one
|
|
func ValidAPIKey(key string) error {
|
|
if len(key) > 64 {
|
|
return errInvalidKey
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CheckAPIKeyExists will see if an API key has been set.
|
|
func (c *Config) CheckAPIKeyExists() bool { return len(c.APIKey) > 1 }
|
|
|
|
// Settings holds the client settings
|
|
type Settings struct {
|
|
client *http.Client
|
|
}
|
|
|
|
// NewSettings returns a new Setting pointer with default http client.
|
|
func NewSettings() *Settings {
|
|
return &Settings{
|
|
client: http.DefaultClient,
|
|
}
|
|
}
|
|
|
|
// Optional client settings
|
|
type Option func(s *Settings) error
|
|
|
|
// WithHttpClient sets custom http client when creating a new Client.
|
|
func WithHttpClient(c *http.Client) Option {
|
|
return func(s *Settings) error {
|
|
if c == nil {
|
|
return errInvalidHttpClient
|
|
}
|
|
s.client = c
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// setOptions sets Optional client settings to the Settings pointer
|
|
func setOptions(settings *Settings, options []Option) error {
|
|
for _, option := range options {
|
|
if option == nil {
|
|
return errInvalidOption
|
|
}
|
|
err := option(settings)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|