moving to open apis
This commit is contained in:
parent
7d14f3a9df
commit
b7c6c9e26a
185
location.go
Normal file
185
location.go
Normal file
@ -0,0 +1,185 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// free api for reverse geocoding based on IP
|
||||
const revGeoURL = "http://ip-api.com/json/"
|
||||
|
||||
type Location struct {
|
||||
Lat float64
|
||||
Lon float64
|
||||
City string
|
||||
State string
|
||||
Country string
|
||||
Zipcode string
|
||||
// NWS specific location data
|
||||
// Station string
|
||||
}
|
||||
|
||||
func NewLocation() *Location {
|
||||
return &Location{}
|
||||
}
|
||||
|
||||
func (l *Location) UnmarshalJSON(b []byte) error {
|
||||
|
||||
var aux struct {
|
||||
Lat float64 `json:"lat,string"`
|
||||
Lon float64 `json:"lon,string"`
|
||||
Address struct {
|
||||
City string `json:"city"`
|
||||
State string `json:"state"`
|
||||
Country string `json:"country"`
|
||||
Zipcode string `json:"postcode"`
|
||||
} `json:"address"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(b, &aux); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// copy over values
|
||||
l.Lat = aux.Lat
|
||||
l.Lon = aux.Lon
|
||||
l.City = aux.Address.City
|
||||
l.State = aux.Address.State
|
||||
l.Country = aux.Address.Country
|
||||
l.Zipcode = aux.Address.Zipcode
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SearchLocations(query string) ([]Location, error) {
|
||||
// url to perform location queries against
|
||||
//locations := []Location{}
|
||||
queryURL := fmt.Sprintf("https://nominatim.openstreetmap.org/search?q=%s&format=jsonv2&addressdetails=1", query)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, queryURL, nil)
|
||||
if err != nil {
|
||||
return []Location{}, err
|
||||
}
|
||||
|
||||
req.Header.Set("User-Agent",
|
||||
"weather/0.1 (https://git.keegandeppe.com/kdeppe/weather; contact=19keegandeppe@gmail.com)")
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 15 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return []Location{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return []Location{}, err
|
||||
}
|
||||
|
||||
var locations []Location
|
||||
|
||||
if err := json.Unmarshal(b, &locations); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// l := NewLocation()
|
||||
// l.UnmarshalJSON(b)
|
||||
|
||||
fmt.Printf("%+v\n", locations)
|
||||
|
||||
return locations, nil
|
||||
}
|
||||
|
||||
// func (l *Location) getCoords() error {
|
||||
//
|
||||
// res, err := http.Get(revGeoURL)
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// body, err := io.ReadAll(res.Body)
|
||||
// res.Body.Close()
|
||||
// if res.StatusCode > 299 {
|
||||
// errMsg := fmt.Sprintf("Request failed with status code: %d\n", res.StatusCode)
|
||||
// return errors.New(errMsg)
|
||||
// }
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// // unmarshall response into struct
|
||||
// if err := json.Unmarshal(body, l); err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// l.Found = true
|
||||
//
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// func (l *Location) getStation() error {
|
||||
// // generate url based on coords
|
||||
// url := fmt.Sprintf("https://api.weather.gov/points/%f,%f", l.Lat, l.Lon)
|
||||
//
|
||||
// res, err := http.Get(url)
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// body, err := io.ReadAll(res.Body)
|
||||
// res.Body.Close()
|
||||
// if res.StatusCode > 299 {
|
||||
// errMsg := fmt.Sprintf("Request failed with status code: %d\n", res.StatusCode)
|
||||
// return errors.New(errMsg)
|
||||
// }
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// // send another request to station URL to find closest
|
||||
// stationURL := gjson.Get(string(body), "properties.observationStations")
|
||||
//
|
||||
// res, err = http.Get(stationURL.String())
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// body, err = io.ReadAll(res.Body)
|
||||
// res.Body.Close()
|
||||
// if res.StatusCode > 299 {
|
||||
// errMsg := fmt.Sprintf("Request failed with status code: %d\n", res.StatusCode)
|
||||
// return errors.New(errMsg)
|
||||
// }
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// station := gjson.Get(string(body), "features.0.properties.stationIdentifier")
|
||||
//
|
||||
// if station.String() == "" {
|
||||
// return errors.New("Station not found")
|
||||
// }
|
||||
//
|
||||
// l.Station = station.String()
|
||||
//
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// for debugging
|
||||
func (l *Location) String() string {
|
||||
|
||||
return fmt.Sprintf("%s, %s %s (%f, %f)", l.City, l.State, l.Zipcode, l.Lat, l.Lon)
|
||||
}
|
||||
224
main.go
224
main.go
@ -1,187 +1,89 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
"time"
|
||||
)
|
||||
|
||||
// free api for reverse geocoding based on IP
|
||||
const revGeoURL = "http://ip-api.com/json/"
|
||||
|
||||
func main() {
|
||||
|
||||
SearchLocations("Somerville")
|
||||
|
||||
return
|
||||
w := NewWeather()
|
||||
|
||||
if err := w.getLatest(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// if err := w.getLatest(); err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
fmt.Println(w)
|
||||
}
|
||||
|
||||
type HourForecast struct {
|
||||
StartTime time.Time
|
||||
EndTime time.Time
|
||||
Temperature float64
|
||||
TemperatureUnit string
|
||||
ProbabilityOfPercipitation float64
|
||||
WindSpeed float64
|
||||
WindDirection string
|
||||
Icon string
|
||||
}
|
||||
|
||||
type Weather struct {
|
||||
Temperature float64
|
||||
Humidity float64
|
||||
Conditions string
|
||||
Icon string
|
||||
*Location
|
||||
Static bool //
|
||||
Forecast []*HourForecast
|
||||
ForecastURL string
|
||||
}
|
||||
|
||||
func NewWeather() *Weather {
|
||||
l := NewLocation()
|
||||
// try to set location
|
||||
if err := l.getCoords(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := l.getStation(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// if err := l.getCoords(); err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
//
|
||||
// if err := l.getStation(); err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
|
||||
return &Weather{
|
||||
Location: l,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Weather) getLatest() error {
|
||||
url := fmt.Sprintf("https://api.weather.gov/stations/%s/observations/latest", w.Location.Station)
|
||||
|
||||
res, err := http.Get(url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
if res.StatusCode > 299 {
|
||||
errMsg := fmt.Sprintf("Request failed with status code: %d\n", res.StatusCode)
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
temp := gjson.Get(string(body), "properties.temperature.value")
|
||||
humidity := gjson.Get(string(body), "properties.relativeHumidity.value")
|
||||
cond := gjson.Get(string(body), "properties.textDescription")
|
||||
|
||||
// convert to Farenheit
|
||||
w.Temperature = temp.Float()*(9.0/5) + 32
|
||||
w.Humidity = humidity.Float()
|
||||
w.Conditions = cond.String()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Weather) String() string {
|
||||
return fmt.Sprintf("%s %.1f deg %.1f%% RH in %s, %s", w.Conditions, w.Temperature, w.Humidity, w.Location.City, w.Location.State)
|
||||
}
|
||||
|
||||
type Location struct {
|
||||
Lat float32 `json:"lat"`
|
||||
Lon float32 `json:"lon"`
|
||||
City string `json:"city"`
|
||||
State string `json:"region"`
|
||||
Zipcode string `json:"zip"`
|
||||
Found bool
|
||||
// NWS specific location data
|
||||
Station string
|
||||
}
|
||||
|
||||
func NewLocation() *Location {
|
||||
return &Location{}
|
||||
}
|
||||
|
||||
func (l *Location) getCoords() error {
|
||||
|
||||
res, err := http.Get(revGeoURL)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
if res.StatusCode > 299 {
|
||||
errMsg := fmt.Sprintf("Request failed with status code: %d\n", res.StatusCode)
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// unmarshall response into struct
|
||||
if err := json.Unmarshal(body, l); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.Found = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Location) getStation() error {
|
||||
// generate url based on coords
|
||||
url := fmt.Sprintf("https://api.weather.gov/points/%f,%f", l.Lat, l.Lon)
|
||||
|
||||
res, err := http.Get(url)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
if res.StatusCode > 299 {
|
||||
errMsg := fmt.Sprintf("Request failed with status code: %d\n", res.StatusCode)
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// send another request to station URL to find closest
|
||||
stationURL := gjson.Get(string(body), "properties.observationStations")
|
||||
|
||||
res, err = http.Get(stationURL.String())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body, err = io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
if res.StatusCode > 299 {
|
||||
errMsg := fmt.Sprintf("Request failed with status code: %d\n", res.StatusCode)
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
station := gjson.Get(string(body), "features.0.properties.stationIdentifier")
|
||||
|
||||
if station.String() == "" {
|
||||
return errors.New("Station not found")
|
||||
}
|
||||
|
||||
l.Station = station.String()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// for debugging
|
||||
func (l *Location) String() string {
|
||||
|
||||
return fmt.Sprintf("%s, %s %s (%f, %f) station: %s", l.City, l.State, l.Zipcode, l.Lat, l.Lon, l.Station)
|
||||
}
|
||||
// func (w *Weather) getLatest() error {
|
||||
// url := fmt.Sprintf("https://api.weather.gov/stations/%s/observations/latest", w.Location.Station)
|
||||
//
|
||||
// res, err := http.Get(url)
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// body, err := io.ReadAll(res.Body)
|
||||
// res.Body.Close()
|
||||
// if res.StatusCode > 299 {
|
||||
// errMsg := fmt.Sprintf("Request failed with status code: %d\n", res.StatusCode)
|
||||
// return errors.New(errMsg)
|
||||
// }
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// temp := gjson.Get(string(body), "properties.temperature.value")
|
||||
// humidity := gjson.Get(string(body), "properties.relativeHumidity.value")
|
||||
// cond := gjson.Get(string(body), "properties.textDescription")
|
||||
//
|
||||
// // convert to Farenheit
|
||||
// w.Temperature = temp.Float()*(9.0/5) + 32
|
||||
// w.Humidity = humidity.Float()
|
||||
// w.Conditions = cond.String()
|
||||
//
|
||||
// return nil
|
||||
// }
|
||||
//
|
||||
// func (w *Weather) String() string {
|
||||
// return fmt.Sprintf("%s %.1f deg %.1f%% RH in %s, %s", w.Conditions, w.Temperature, w.Humidity, w.Location.City, w.Location.State)
|
||||
// }
|
||||
|
||||
462
weather.sh
Executable file
462
weather.sh
Executable file
@ -0,0 +1,462 @@
|
||||
#!/bin/bash
|
||||
|
||||
# client version, calls server for actual API info
|
||||
usage() {
|
||||
cat <<EOF
|
||||
usage: $0 [-a KEY][-s QUERY | -z ZIPCODE][-u <c|f>][-p][-i][-l][-h]
|
||||
|
||||
Queries OpenWeatherMapAPI for weather or lat/long
|
||||
options:
|
||||
-a, --apikey set the api key to use to KEY
|
||||
-l, --location print the location being used
|
||||
-p, --pretty enables nerdfont symbols
|
||||
-d, --desc prints the conditions text as well
|
||||
-z, --zipcode returns the weather for ZIPCODE
|
||||
-s, --search returns the weather for QUERY
|
||||
-u, --units sets the temperature units
|
||||
-i, --icon_test tests the icons used
|
||||
-r, --radar displays the radar for a given location
|
||||
-f, --forecast displays the forecast a given location
|
||||
-h, --help show this message
|
||||
|
||||
ZIPCODE sets the location to the given zipcode
|
||||
to narrow results, include the ISO country code
|
||||
ex) -z "02139,US"
|
||||
|
||||
QUERY sets the location to the search
|
||||
QUERY format is "CITY,REGION,COUNTRY" where
|
||||
EOF
|
||||
exit $1
|
||||
}
|
||||
|
||||
WORKING_DIR="$HOME/.local/share/weather"
|
||||
WEATHER_FILE=".env"
|
||||
|
||||
TIMEOUT=60 # timeout for info
|
||||
TIME=$(date +%s)
|
||||
|
||||
|
||||
if [[ ! -d "$WORKING_DIR" ]] ; then
|
||||
mkdir -p "$WORKING_DIR"
|
||||
fi
|
||||
|
||||
# going to weather directory
|
||||
cd "$WORKING_DIR"
|
||||
|
||||
print_weather() {
|
||||
# prints the weather
|
||||
|
||||
# check to see if it is expired
|
||||
check_expiration
|
||||
|
||||
# checking if description enabled
|
||||
if [[ "$DESCRIPTION" = true ]] ; then
|
||||
DESC=$(echo "$CONDITIONS" | awk '{for(i=1;i<=NF;i++){ $i=toupper(substr($i,1,1)) substr($i,2) }}1')
|
||||
DESC=$(printf '%s ' "$DESC")
|
||||
fi
|
||||
|
||||
if [[ "$PRETTY" = true ]] ; then
|
||||
# print with NF icons
|
||||
icon=$(get_icon "$WEATHER_ICON")
|
||||
humidity_icon=$(echo -e '\ue373')
|
||||
|
||||
# checking units
|
||||
if [[ "$UNIT" == "C" ]] ; then
|
||||
temp_icon=$(echo -e '\ufa03')
|
||||
else
|
||||
temp_icon=$(echo -e '\ufa04')
|
||||
fi
|
||||
else
|
||||
# default printing
|
||||
humidity_icon="% RH"
|
||||
if [[ "$UNIT" == "C" ]] ; then
|
||||
temp_icon="*C"
|
||||
else
|
||||
temp_icon="*F"
|
||||
fi
|
||||
fi
|
||||
|
||||
# printing
|
||||
printf '%s%s %.1f%s %d%s in %s\n' "$DESC" "$icon" $TEMPERATURE "$temp_icon" $HUMIDITY "$humidity_icon" "$CITY"
|
||||
|
||||
# save before exiting
|
||||
save_info
|
||||
}
|
||||
|
||||
print_location() {
|
||||
# prints the location being used for the weather
|
||||
|
||||
# uses reverse geocoding
|
||||
LOCATION_URL="https://api.openweathermap.org/geo/1.0/reverse?lat=$LAT&lon=$LON&appid=$API_KEY"
|
||||
LOCATION=$(curl --silent -fL "$LOCATION_URL" | gojq '.[0] | del(.local_names)')
|
||||
|
||||
name=$(echo "$LOCATION" | gojq '.name' | tr -d '"')
|
||||
state=$(echo "$LOCATION" | gojq '.state' | tr -d '"')
|
||||
country=$(echo "$LOCATION" | gojq '.country' | tr -d '"')
|
||||
|
||||
printf 'Location is set to %s in %s, %s\n' "$name" "$state" "$country"
|
||||
}
|
||||
|
||||
check_expiration() {
|
||||
# checks if the info is expired
|
||||
if [[ $TIME -gt $EXPIRATION ]] ; then
|
||||
# expired
|
||||
get_weather
|
||||
fi
|
||||
}
|
||||
|
||||
get_location() {
|
||||
# searching for user location
|
||||
if [[ -n "$SEARCH" ]] ; then
|
||||
# search
|
||||
LOCATION_URL="https://api.openweathermap.org/geo/1.0/$SEARCH&appid=$API_KEY&limit=5"
|
||||
RESULTS=$(curl --silent -fL "$LOCATION_URL")
|
||||
# check if its an array
|
||||
is_array=$(echo "$RESULTS" | gojq 'if type=="array" then 1 else 0 end')
|
||||
|
||||
if [[ $is_array -eq 1 ]] ; then
|
||||
# returned result is an array
|
||||
|
||||
NUM_RESULTS=$(echo "$RESULTS" | gojq '. | length')
|
||||
if [[ $NUM_RESULTS -gt 1 ]] ; then
|
||||
# provide menu for multiple results
|
||||
for ((i=0; i < $NUM_RESULTS; i++)); do
|
||||
# adding options
|
||||
res=$(echo "$RESULTS" | gojq ".[$i]")
|
||||
name=$(echo "$res" | gojq '.name' | tr -d '"')
|
||||
state=$(echo "$res" | gojq '.state' | tr -d '"')
|
||||
country=$(echo "$res" | gojq '.country' | tr -d '"')
|
||||
opt=$(printf '%s %s, %s' "$name" "$state" "$country")
|
||||
OPTIONS+=($(($i+1)) "$opt")
|
||||
done
|
||||
|
||||
# executing dialog menu
|
||||
exec 3>&1
|
||||
SELECTION=$(dialog \
|
||||
--title 'Multiple Locations Found!' \
|
||||
--clear \
|
||||
--cancel-label 'Exit' \
|
||||
--ok-label 'Select' \
|
||||
--menu 'Please select the desired location. If none of the options look correct, try making refining your search by including a country code and/or region' 0 0 4 \
|
||||
"${OPTIONS[@]}" \
|
||||
2>&1 1>&3)
|
||||
exit_status=$?
|
||||
exec 3>&-
|
||||
clear
|
||||
|
||||
if [[ $exit_status -eq 1 || $exit_satus -eq 255 ]] ; then
|
||||
# canceled or escaped
|
||||
echo "Location not changed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# fix index
|
||||
SELECTION=$(($SELECTION - 1))
|
||||
fi
|
||||
# updating result
|
||||
RESULTS=$(echo "$RESULTS" | gojq ".[$SELECTION]")
|
||||
fi
|
||||
|
||||
# Update info
|
||||
LAT=$(echo "$RESULTS" | gojq ".lat")
|
||||
LON=$(echo "$RESULTS" | gojq ".lon")
|
||||
CITY=$(echo "$RESULTS" | gojq ".name" | tr -d '"')
|
||||
else
|
||||
# no search, default to user IP
|
||||
|
||||
url="http://ip-api.com/csv/?fields=252"
|
||||
res=$(curl --silent -fL "$url")
|
||||
LAT=$(awk -F , '{print $5}' <<<"$res")
|
||||
LON=$(awk -F , '{print $6}' <<<"$res")
|
||||
CITY=$(awk -F , '{print $3}' <<<"$res")
|
||||
fi
|
||||
|
||||
# check
|
||||
if [[ -z "$LAT" || -z "$LON" ]] ; then
|
||||
echo "No location found!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# force refresh
|
||||
EXPIRATION=0
|
||||
}
|
||||
|
||||
get_weather() {
|
||||
# calls api for weather based on $LAT, $LONG
|
||||
|
||||
if [[ -z "$LAT" || -z "$LON" ]] ; then
|
||||
# no lat or lon
|
||||
get_location
|
||||
fi
|
||||
|
||||
if [[ "$UNIT" =~ ^[Cc]$ ]] ; then
|
||||
# units set to metric
|
||||
UNIT="C"
|
||||
UNITS="metric"
|
||||
elif [[ "$UNIT" =~ ^[Ff]$ || -z "$UNIT" ]] ; then
|
||||
# imperial (default)
|
||||
UNIT="F"
|
||||
UNITS="imperial"
|
||||
else
|
||||
printf 'Unit %s unrecognized\n' "$UNIT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
WEATHER_URL="https://api.openweathermap.org/data/2.5/weather?lat=$LAT&lon=$LON&appid=$API_KEY&units=$UNITS"
|
||||
|
||||
WEATHER=$(curl --silent -fL "$WEATHER_URL")
|
||||
|
||||
CONDITIONS=$(echo $WEATHER | gojq -r '.weather[0].description')
|
||||
TEMPERATURE=$(echo $WEATHER | gojq -r '.main.temp')
|
||||
HUMIDITY=$(echo $WEATHER | gojq -r '.main.humidity')
|
||||
WEATHER_ICON=$(echo $WEATHER | gojq -r '.weather[0].icon')
|
||||
EXPIRATION=$(($TIME+$TIMEOUT))
|
||||
}
|
||||
|
||||
update_key() {
|
||||
# updates the API key used
|
||||
|
||||
printf 'Testing API key %s... ' "$API_KEY"
|
||||
WEATHER_URL="https://api.openweathermap.org/data/2.5/weather?lat=42.3736&lon=71.1097&appid=$API_KEY"
|
||||
CODE=$(curl --silent "$WEATHER_URL" | gojq '.cod' | tr -d '"')
|
||||
|
||||
if [[ $CODE -eq 401 ]] ; then
|
||||
# API_KEY error
|
||||
printf 'Error: Invalid API Key "%s"!\n' "$API_KEY" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf 'Success: Key Updated!\n'
|
||||
|
||||
# saving new key
|
||||
save_info
|
||||
}
|
||||
|
||||
save_info() {
|
||||
# saves set env vars to file
|
||||
printf '%s="%s"\n' \
|
||||
"API_KEY" "$API_KEY" \
|
||||
"PARSER" "$PARSER" \
|
||||
"LAT" "$LAT" \
|
||||
"LON" "$LON" \
|
||||
"CITY" "$CITY" \
|
||||
"TEMPERATURE" "$TEMPERATURE" \
|
||||
"HUMIDITY" "$HUMIDITY" \
|
||||
"UNIT" "$UNIT" \
|
||||
"WEATHER_ICON" "$WEATHER_ICON" \
|
||||
"CONDITIONS" "$CONDITIONS" \
|
||||
"EXPIRATION" "$EXPIRATION" > "$WEATHER_FILE"
|
||||
}
|
||||
|
||||
load_info() {
|
||||
# loads env vars
|
||||
if [[ ! -e "$WEATHER_FILE" ]] ; then
|
||||
# generates blank info on fresh installs
|
||||
|
||||
# test for gojq
|
||||
PARSER=gojq
|
||||
if ! command -v $PARSER ; then
|
||||
# test for jq
|
||||
PARSER=jq
|
||||
if ! command -v $PARSER ; then
|
||||
echo "$1 depends on jq or gojq"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
save_info
|
||||
fi
|
||||
source $WEATHER_FILE
|
||||
|
||||
}
|
||||
|
||||
display_forecast() {
|
||||
# two step process, gets weather station from NWS, then mpv to play radar
|
||||
if [[ -z "$LAT" || -z "$LON" ]] ; then
|
||||
echo "Location not found!"
|
||||
exit 1
|
||||
fi
|
||||
echo "Loading weather..."
|
||||
if [[ -n "$PRETTY" ]] ; then
|
||||
# nerdfont
|
||||
curl -fsSL "https://v2d.wttr.in/${LAT},${LON}?F" | less -R
|
||||
else
|
||||
curl -fsSL "https://wttr.in/${LAT},${LON}?F" | less -R
|
||||
fi
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
display_radar() {
|
||||
# two step process, gets weather station from NWS, then mpv to play radar
|
||||
if [[ -z "$LAT" || -z "$LON" ]] ; then
|
||||
echo "Location not found!"
|
||||
exit 1
|
||||
fi
|
||||
# grabbing nws station
|
||||
NWS=$(curl -fsSL "https://api.weather.gov/points/$LAT,$LON")
|
||||
STATION=$(echo "$NWS" | gojq '.properties.radarStation' | tr -d '"')
|
||||
|
||||
# validation
|
||||
if [[ -z "$STATION" ]] ; then
|
||||
echo "NWS Station error!"
|
||||
exit 1
|
||||
fi
|
||||
# mpv to play weather in fullscreen
|
||||
echo "Fetching weather for NWS Station $STATION..."
|
||||
mpv --fs --loop-file "https://radar.weather.gov/ridge/standard/${STATION}_loop.gif" >/dev/null 2>&1
|
||||
|
||||
exit 0
|
||||
}
|
||||
|
||||
get_icon() {
|
||||
# sets NF symbols
|
||||
tod=$(echo "$1" | sed --expression='s/[0-9]//g')
|
||||
conditions=$(echo "$1" | sed --expression='s/[^0-9]//g')
|
||||
|
||||
# getting icon
|
||||
if [[ "$tod" == "d" ]] ; then
|
||||
# day icons
|
||||
case "$conditions" in
|
||||
"01") echo -e '\ue30d' ;; # clear
|
||||
"02") echo -e '\ue30c' ;; # scattered clouds
|
||||
"03") echo -e '\ue302' ;; # broken clouds
|
||||
"04") echo -e '\ue312' ;; # cloudy
|
||||
"09") echo -e '\ue309' ;; # showers
|
||||
"10") echo -e '\ue308' ;; # rain
|
||||
"11") echo -e '\ue30f' ;; # thunderstorm
|
||||
"13") echo -e '\uf2dc' ;; # snow
|
||||
"50") echo -e '\ue303' ;; # mist
|
||||
* ) echo -e '\ue374' ;; # unknown
|
||||
esac
|
||||
elif [[ "$tod" == "n" ]] ; then
|
||||
# night icons
|
||||
case "$conditions" in
|
||||
"01") echo -e '\ue32b' ;; # clear
|
||||
"02") echo -e '\ue379' ;; # scattered clouds
|
||||
"03") echo -e '\ue37e' ;; # broken clouds
|
||||
"04") echo -e '\ue312' ;; # cloudy
|
||||
"09") echo -e '\ue326' ;; # showers
|
||||
"10") echo -e '\ue325' ;; # rain
|
||||
"11") echo -e '\ue32a' ;; # thunderstorm
|
||||
"13") echo -e '\uf2dc' ;; # snow
|
||||
"50") echo -e '\ue346' ;; # mist
|
||||
* ) echo -e '\ue374' ;; # unknown
|
||||
esac
|
||||
else
|
||||
echo "TOD not recognized"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
icon_test() {
|
||||
#tests icons
|
||||
printf 'Testing weather icons\nIf any look broken, check that NerdFont is installed\n'
|
||||
|
||||
printf '\nDay:\n'
|
||||
printf 'Clear: \ue30d\n'
|
||||
printf 'Partly Cloudy: \ue30c\n'
|
||||
printf 'Cloudy: \ue302\n'
|
||||
printf 'Very Cloudy: \ue312\n'
|
||||
printf 'Showers: \ue309\n'
|
||||
printf 'Rain: \ue308\n'
|
||||
printf 'Thunderstorm: \ue30f\n'
|
||||
printf 'Snow: \uf2dc\n'
|
||||
printf 'Fog: \ue303\n'
|
||||
# night icons
|
||||
printf '\nNight:\n'
|
||||
printf 'Clear: \ue32b\n'
|
||||
printf 'Partly Cloudy: \ue379\n'
|
||||
printf 'Cloudy: \ue37e\n'
|
||||
printf 'Very Cloudy: \ue312\n'
|
||||
printf 'Showers: \uf2dc\n'
|
||||
printf 'Rain: \ue325\n'
|
||||
printf 'Thunderstorm: \ue32a\n'
|
||||
printf 'Snow: \uf2dc\n'
|
||||
printf 'Fog: \ue346\n'
|
||||
|
||||
printf '\nAssorted:\n'
|
||||
printf 'Degrees (F) \ufa04\n'
|
||||
printf 'Degrees (C) \ufa03\n'
|
||||
printf '%% Humidity \ue373\n'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# shifting longform
|
||||
for arg in "$@"; do
|
||||
shift
|
||||
case "$arg" in
|
||||
'--zipcode') set -- "$@" "-z" ;;
|
||||
'--location') set -- "$@" "-l" ;;
|
||||
'--apikey') set -- "$@" "-a" ;;
|
||||
'--coords') set -- "$@" "-c" ;;
|
||||
'--search') set -- "$@" "-s" ;;
|
||||
'--units') set -- "$@" "-u" ;;
|
||||
'--pretty') set -- "$@" "-p" ;;
|
||||
'--pretty') set -- "$@" "-p" ;;
|
||||
'--icon_test') set -- "$@" "-i" ;;
|
||||
'--radar') set -- "$@" "-r" ;;
|
||||
'--forecast') set -- "$@" "-f" ;;
|
||||
'--help') set -- "$@" "-h" ;;
|
||||
*) set -- "$@" "$arg" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# loading the info
|
||||
load_info
|
||||
|
||||
while getopts "chpldrfiz:s:u:a:" opt; do
|
||||
case "$opt" in
|
||||
'a' )
|
||||
API_KEY="$OPTARG"
|
||||
update_key
|
||||
;;
|
||||
'u' )
|
||||
UNIT="$OPTARG"
|
||||
;;
|
||||
'l' )
|
||||
print_location
|
||||
exit 0
|
||||
;;
|
||||
'z' )
|
||||
[ -n "$SEARCH" ] && usage 1 || SEARCH="/zip?zip=$OPTARG"
|
||||
;;
|
||||
's' )
|
||||
[ -n "$SEARCH" ] && usage 1 || SEARCH="/direct?q=$OPTARG"
|
||||
;;
|
||||
'p' )
|
||||
PRETTY=true
|
||||
;;
|
||||
'd' )
|
||||
DESCRIPTION=true
|
||||
;;
|
||||
'i' )
|
||||
icon_test
|
||||
;;
|
||||
'r' )
|
||||
display_radar
|
||||
;;
|
||||
'f' )
|
||||
display_forecast
|
||||
;;
|
||||
'h' )
|
||||
usage 0
|
||||
;;
|
||||
'?' )
|
||||
usage 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# test for APIKEY
|
||||
if [[ -z "$API_KEY" ]] ; then
|
||||
echo "No API Key found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "$SEARCH" ]] ; then
|
||||
# perform search
|
||||
get_location
|
||||
fi
|
||||
|
||||
if [[ -z "$QUIET" ]] ; then
|
||||
print_weather
|
||||
fi
|
||||
Loading…
x
Reference in New Issue
Block a user