#!/usr/bin/env python3 from argparse import ArgumentParser import requests import fscache base_url = 'https://api.weather.gov' headers = { 'user-agent': 'meteor.py/0.2 (us@starfall.systems)', 'accept': 'application/ld+json' } # SGR decorations / ANSI escape colors class term: RESET = '\033[0m' BOLD = '\033[1m' def handle_args(): parser = ArgumentParser() parser.add_argument('lat', help='latitude') parser.add_argument('lon', help='longitude') return parser.parse_args() def print_alerts(county, zone): # in some areas, alerts are not published for the zone but only for the county # you can request both like this rather than making two requestsfork alerts = requests.get(base_url + f'/alerts/active?zone={county},{zone}', headers=headers).json() print(f'{term.BOLD}{alerts.get("title")}') print(f'===================={term.RESET}') for alert in alerts.get('@graph'): print(f'{alert.get("headline")}') print(f' {alert.get("severity")} - {alert.get("certainty")} - {alert.get("urgency")}') print(f' Until: {alert.get("expires")}') print(f'{alert.get("description")}') print('--------------------') # many more cool fields available. # verious datetimes: sent, effective, onset, expires, ends # severity: Minor, Moderate, Severe, Extreme, Unknown # certainty: Unlikely, Possible, Likely, Observed, Unknown # urgency: Past, Future, Expected, Immediate, Unknown # messageType: Alert, Update # can construct the headline ourseves as f'{event} issued {sent} [until {end}] by {senderName}' print() def print_forecast(point): cache_key = f'{point.get("gridId")}-{point.get("gridX")},{point.get("gridY")}' try: print(cache.read(cache_key)) except fscache.CacheMiss: response = requests.get(point.get('forecast'), headers=headers) # ugly cache_control parsing honestly, break out later ttl = int(response.headers['cache-control'].split('max-age=')[-1].split(',')[0]) forecast = response.json() buffer = f'{term.BOLD}forecast for {point.get("relativeLocation").get("city")}, {point.get("relativeLocation").get("state")} ({cache_key})' buffer += '\n' + f'===================={term.RESET}' periods = forecast.get('periods') # print today and tonight in long format for period in periods[:2]: buffer += '\n' + f'{term.BOLD}{period.get("name")}{term.RESET}:' buffer += '\n' + f' {period.get("temperature")}°{period.get("temperatureUnit")}, wind {period.get("windSpeed")} {period.get("windDirection")}' buffer += '\n' + f' {period.get("detailedForecast")}' # print further forecasted periods in short format for period in periods[2:]: buffer += '\n' + f'{term.BOLD}{period.get("name").ljust(15)}{term.RESET}: {period.get("shortForecast")} - {period.get("temperature")}°{period.get("temperatureUnit")}, {period.get("windSpeed")} {period.get("windDirection")}' cache.write(cache_key, buffer, ttl) print(buffer) def main(): args = handle_args() global cache cache = fscache.Cache('meteor.py') point = requests.get(base_url + f'/points/{args.lat},{args.lon}', headers=headers).json() county = point.get('county').split('/')[-1] zone = point.get('forecastZone').split('/')[-1] print_alerts(county, zone) # TODO can hazardous weather outlook be gotten programatically? print_forecast(point) if __name__ == '__main__': import sys sys.exit(main())