33# github.com/FlyingFathead/TelegramBot-OpenAI-API/
44# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
55#
6- # >>> weather fetcher module version: v0.728
7- # >>> (Updated July 13 2024)
6+ # >>> weather fetcher module version: v0.75053
7+ # >>> (Updated Oct 8 2024)
88#
99# This API functionality requires both OpenWeatherMap and MapTiler API keys.
1010# You can get both from the corresponding service providers.
1414
1515# Import the NWS data fetching function
1616from api_get_nws_weather import get_nws_forecast , get_nws_alerts
17- from config_paths import NWS_USER_AGENT , NWS_RETRIES , NWS_RETRY_DELAY , FETCH_NWS_FORECAST , FETCH_NWS_ALERTS
17+ from config_paths import NWS_USER_AGENT , NWS_RETRIES , NWS_RETRY_DELAY , FETCH_NWS_FORECAST , FETCH_NWS_ALERTS , NWS_ELIGIBLE_COUNTRIES , NWS_ONLY_ELIGIBLE_COUNTRIES
18+
19+ # linter annotation (use only if you use the v2 method of `get_coordinates`)
20+ # from typing import Optional, Tuple
1821
1922# date & time utils
2023import datetime as dt
@@ -82,26 +85,40 @@ async def get_weather(city_name, country, exclude='', units='metric', lang='fi')
8285 # combined_data = await combine_weather_data(city_name, resolved_country, lat, lon, current_weather_data, forecast_data, moon_phase_data, daily_forecast_data, current_weather_data_from_weatherapi, astronomy_data, additional_data)
8386 # return combined_data
8487
85- # Fetch NWS data
86- logging .info ("Fetching NWS data." )
87- nws_data = await get_nws_forecast (lat , lon )
88- if nws_data :
89- logging .info ("NWS data fetched successfully." )
90- nws_forecast = nws_data .get ('nws_forecast' )
91- nws_forecast_hourly = nws_data .get ('nws_forecast_hourly' )
92- else :
93- logging .warning ("Failed to fetch NWS data." )
88+ # Check if NWS should only be used for eligible countries
89+ if NWS_ONLY_ELIGIBLE_COUNTRIES and resolved_country .upper () not in NWS_ELIGIBLE_COUNTRIES :
90+ logging .info (f"NOTE: NWS data will not be fetched as { resolved_country .upper ()} is not in the eligible country list." )
9491 nws_forecast = None
9592 nws_forecast_hourly = None
96-
97- # Fetch NWS alerts data
98- logging .info ("Fetching NWS alerts data." )
99- nws_alerts = await get_nws_alerts (lat , lon )
100- if nws_alerts :
101- logging .info (f"Fetched { len (nws_alerts )} active NWS alerts." )
93+ nws_alerts = None
10294 else :
103- logging .info ("No active NWS alerts found." )
104-
95+ # Ensure that NWS API requests are made only if eligible
96+ try :
97+ logging .info ("Fetching NWS data." )
98+ nws_data = await get_nws_forecast (lat , lon )
99+ if nws_data :
100+ logging .info ("NWS data fetched successfully." )
101+ nws_forecast = nws_data .get ('nws_forecast' )
102+ nws_forecast_hourly = nws_data .get ('nws_forecast_hourly' )
103+ else :
104+ logging .warning ("Failed to fetch NWS data." )
105+ nws_forecast = None
106+ nws_forecast_hourly = None
107+
108+ # Fetch NWS alerts data only if the forecast is successful
109+ logging .info ("Fetching NWS alerts data." )
110+ nws_alerts = await get_nws_alerts (lat , lon )
111+ if nws_alerts :
112+ logging .info (f"Fetched { len (nws_alerts )} active NWS alerts." )
113+ else :
114+ logging .info ("No active NWS alerts found." )
115+ except Exception as e :
116+ logging .error (f"Error fetching NWS data: { e } " )
117+ nws_forecast = None
118+ nws_forecast_hourly = None
119+ nws_alerts = None
120+
121+ # combine the weather data
105122 combined_data = await combine_weather_data (
106123 city_name , resolved_country , lat , lon ,
107124 current_weather_data , forecast_data , moon_phase_data ,
@@ -114,7 +131,49 @@ async def get_weather(city_name, country, exclude='', units='metric', lang='fi')
114131 logging .error (f"Failed to fetch weather data: { current_weather_response .text } / { forecast_response .text } " )
115132 return "[Inform the user that data fetching the weather data failed, current information could not be fetched. Reply in the user's language.]"
116133
117- # get coordinates
134+ # # get coordinates (method 2; might introduce complexity; needs `Typing`)
135+ # async def get_coordinates(city_name: str, country: Optional[str] = None) -> Tuple[Optional[float], Optional[float], Optional[str]]:
136+ # lat: Optional[float] = None
137+ # lon: Optional[float] = None
138+ # resolved_country: Optional[str] = None
139+
140+ # logging.info(f"Coordinates for {city_name}, {country}: Latitude: {lat}, Longitude: {lon}")
141+ # api_key = os.getenv('MAPTILER_API_KEY')
142+
143+ # if not api_key:
144+ # logging.info("[WARNING] MapTiler API key not set. You need to set the 'MAPTILER_API_KEY' environment variable to use coordinate lookups!")
145+ # return None, None, None
146+
147+ # query = f"{city_name}"
148+ # if country:
149+ # query += f", {country}"
150+
151+ # geocode_url = f"https://api.maptiler.com/geocoding/{query}.json?key={api_key}"
152+ # logging.info(f"Making API request to URL: {geocode_url}")
153+
154+ # async with httpx.AsyncClient() as client:
155+ # response = await client.get(geocode_url)
156+ # logging.info(f"Received response with status code: {response.status_code}")
157+
158+ # if response.status_code == 200:
159+ # data = response.json()
160+ # logging.info(f"Response data: {data}")
161+
162+ # if data.get('features'):
163+ # feature = data['features'][0]
164+ # lat = feature['geometry']['coordinates'][1]
165+ # lon = feature['geometry']['coordinates'][0]
166+ # resolved_country = feature['properties'].get('country_code', 'Unknown')
167+ # logging.info(f"Coordinates for {city_name}, {resolved_country}: Latitude: {lat}, Longitude: {lon}")
168+ # return lat, lon, resolved_country
169+ # else:
170+ # logging.error("No features found in the geocoding response.")
171+ # return None, None, None
172+ # else:
173+ # logging.error(f"Failed to fetch coordinates: {response.text}")
174+ # return None, None, None
175+
176+ # # // (old method for country lookup)
118177async def get_coordinates (city_name , country = None ):
119178 lat = lon = None
120179 resolved_country = None
@@ -208,7 +267,7 @@ def convert_to_24_hour(time_str, timezone_str):
208267# combined weather data
209268# async def combine_weather_data(city_name, country, lat, lon, current_weather_data, forecast_data, moon_phase_data, daily_forecast_data, current_weather_data_from_weatherapi, astronomy_data, additional_data):
210269# Define the combine_weather_data function with NWS integration
211- async def combine_weather_data (city_name , country , lat , lon , current_weather_data , forecast_data , moon_phase_data , daily_forecast_data , current_weather_data_from_weatherapi , astronomy_data , additional_data , nws_forecast , nws_forecast_hourly ):
270+ async def combine_weather_data (city_name , resolved_country , lat , lon , current_weather_data , forecast_data , moon_phase_data , daily_forecast_data , current_weather_data_from_weatherapi , astronomy_data , additional_data , nws_forecast , nws_forecast_hourly ):
212271 tf = TimezoneFinder ()
213272 timezone_str = tf .timezone_at (lat = lat , lng = lon )
214273 local_timezone = pytz .timezone (timezone_str )
@@ -360,7 +419,6 @@ async def combine_weather_data(city_name, country, lat, lon, current_weather_dat
360419
361420 combined_info = f"{ detailed_weather_info } \n \n { final_forecast } "
362421
363-
364422 # Append NWS data (Forecasts)
365423 if nws_forecast :
366424 nws_forecast_info = ""
@@ -398,73 +456,96 @@ async def combine_weather_data(city_name, country, lat, lon, current_weather_dat
398456
399457 combined_info += f"\n { nws_forecast_info } \n { nws_hourly_forecast_info } "
400458
401- # Fetch and append NWS Alerts
402- try :
403- # Round coordinates to 4 decimal places to comply with NWS API
404- lat_rounded = round (lat , 4 )
405- lon_rounded = round (lon , 4 )
406- alerts_url = f"https://api.weather.gov/alerts/active?point={ lat_rounded } ,{ lon_rounded } "
407- async with httpx .AsyncClient (follow_redirects = True ) as client :
408- alerts_response = await client .get (alerts_url , headers = {'User-Agent' : NWS_USER_AGENT })
409- alerts_response .raise_for_status ()
410- alerts_data = alerts_response .json ()
411- except httpx .HTTPStatusError as e :
412- logging .error (f"NWS Alerts HTTP error: { e .response .status_code } - { e .response .text } " )
413- alerts_data = None
414- except Exception as e :
415- logging .error (f"Error fetching NWS alerts: { e } " )
416- alerts_data = None
417-
418- alerts_info = ""
419- if alerts_data and 'features' in alerts_data and alerts_data ['features' ]:
420- alerts_info += "[HUOM! HUOMIOI NÄMÄ! TAKE THESE INTO ACCOUNT!!! MENTION THESE TO THE USER IF THERE ARE WEATHER ALERTS -- INCLUDE ALL THE DETAILS. WHAT, WHEN, WHERE, WHAT SEVERITY, ETC.]\n 🚨 <b>ONGOING ALERTS FROM THE U.S. NWS (weather.gov):</b>\n "
421- for idx , alert in enumerate (alerts_data ['features' ], start = 1 ):
422- properties = alert .get ('properties' , {})
423-
424- event = properties .get ('event' , 'EVENT' ).upper ()
425- headline = properties .get ('headline' , 'HEADLINE' )
426- description = properties .get ('description' , 'No further details available' ) # Fetching the detailed description
427- instruction = properties .get ('instruction' , 'INSTRUCTION' )
428- severity = properties .get ('severity' , 'Unknown' ).capitalize ()
429- certainty = properties .get ('certainty' , 'Unknown' ).capitalize ()
430- urgency = properties .get ('urgency' , 'Unknown' ).capitalize ()
431- area_desc = properties .get ('areaDesc' , 'N/A' )
432- effective = properties .get ('effective' , 'N/A' )
433- expires = properties .get ('expires' , 'N/A' )
434-
435- alerts_info += (
436- f"{ idx } . ⚠️ <b>{ event } </b>\n "
437- f"<b>Vaara:</b> { headline } \n "
438- f"<b>Kuvaus:</b> { description } \n " # Adding the detailed description
439- f"<b>Ohjeet:</b> { instruction } \n "
440- f"<b>Alue:</b> { area_desc } \n "
441- f"<b>Vakavuus:</b> { severity } \n "
442- f"<b>Varmuus:</b> { certainty } \n "
443- f"<b>Kiireellisyys:</b> { urgency } \n "
444- f"<b>Voimassa alkaen:</b> { effective } \n "
445- f"<b>Päättyy:</b> { expires } \n \n "
446- )
447- else :
448- alerts_info += "\n 🚨 Ei aktiivisia varoituksia U.S. NWS:n (weather.gov) mukaan.\n "
449-
450- # if alerts_data and 'features' in alerts_data and alerts_data['features']:
451- # alerts_info += "\n🚨 <b>NWS ALERTS:</b>\n"
452- # for alert in alerts_data['features']:
453- # event = alert.get('properties', {}).get('event', 'EVENT').upper()
454- # severity = alert.get('properties', {}).get('severity', 'SEVERITY').upper()
455- # headline = alert.get('properties', {}).get('headline', 'HEADLINE')
456- # instruction = alert.get('properties', {}).get('instruction', 'INSTRUCTION')
457-
458- # # Highlight severe alerts
459- # if 'HURRICANE' in event or severity in ['WATCH', 'WARNING', 'EMERGENCY']:
460- # alerts_info += f"🔥 <b>{event}</b>\n<b>Vaara:</b> {headline}\n<b>Ohjeet:</b> {instruction}\n\n"
461- # else:
462- # # Include less severe alerts if needed
463- # alerts_info += f"<b>{event}</b>\n{headline}\n{instruction}\n\n"
464- # else:
465- # alerts_info += "\n🚨 <b>NWS ALERTS:</b> Ei aktiivisia varoituksia.\n"
466-
467- combined_info += alerts_info
459+ # Append NWS data (Forecasts) only for eligible countries
460+ if not NWS_ONLY_ELIGIBLE_COUNTRIES or resolved_country .upper () in NWS_ELIGIBLE_COUNTRIES :
461+
462+ # Append NWS Forecasts
463+ if nws_forecast :
464+ nws_forecast_info = ""
465+ nws_periods = nws_forecast .get ('properties' , {}).get ('periods' , [])
466+ if nws_periods :
467+ nws_forecast_info += "🌦️ <b>NWS Forecast (weather.gov):</b>\n "
468+ for period in nws_periods [:3 ]: # Limit to next 3 periods
469+ name = period .get ('name' , 'N/A' )
470+ temperature = period .get ('temperature' , 'N/A' )
471+ temperature_unit = period .get ('temperatureUnit' , 'N/A' )
472+ wind_speed = period .get ('windSpeed' , 'N/A' )
473+ wind_direction = period .get ('windDirection' , 'N/A' )
474+ short_forecast = period .get ('shortForecast' , 'N/A' )
475+ nws_forecast_info += f"{ name } : { short_forecast } , { temperature } °{ temperature_unit } , Wind: { wind_speed } { wind_direction } \n "
476+ else :
477+ nws_forecast_info += "🌦️ <b>NWS Forecast (weather.gov):</b> Ei saatavilla.\n "
478+
479+ if nws_forecast_hourly :
480+ nws_hourly_forecast_info = ""
481+ nws_hourly_periods = nws_forecast_hourly .get ('properties' , {}).get ('periods' , [])
482+ if nws_hourly_periods :
483+ nws_hourly_forecast_info += "⏰ <b>NWS Hourly Forecast:</b>\n "
484+ for period in nws_hourly_periods [:3 ]: # Limit to next 3 hourly forecasts
485+ start_time = period .get ('startTime' , 'N/A' )
486+ temperature = period .get ('temperature' , 'N/A' )
487+ temperature_unit = period .get ('temperatureUnit' , 'N/A' )
488+ wind_speed = period .get ('windSpeed' , 'N/A' )
489+ wind_direction = period .get ('windDirection' , 'N/A' )
490+ short_forecast = period .get ('shortForecast' , 'N/A' )
491+ nws_hourly_forecast_info += f"{ start_time } : { short_forecast } , { temperature } °{ temperature_unit } , Wind: { wind_speed } { wind_direction } \n "
492+ else :
493+ nws_hourly_forecast_info += "⏰ <b>NWS Hourly Forecast:</b> Ei saatavilla.\n "
494+ else :
495+ nws_hourly_forecast_info = "⏰ <b>NWS Hourly Forecast:</b> Ei saatavilla.\n "
496+
497+ combined_info += f"\n { nws_forecast_info } \n { nws_hourly_forecast_info } "
498+
499+ # Fetch and append NWS Alerts
500+ try :
501+ # Round coordinates to 4 decimal places to comply with NWS API
502+ lat_rounded = round (lat , 4 )
503+ lon_rounded = round (lon , 4 )
504+ alerts_url = f"https://api.weather.gov/alerts/active?point={ lat_rounded } ,{ lon_rounded } "
505+ async with httpx .AsyncClient (follow_redirects = True ) as client :
506+ alerts_response = await client .get (alerts_url , headers = {'User-Agent' : NWS_USER_AGENT })
507+ alerts_response .raise_for_status ()
508+ alerts_data = alerts_response .json ()
509+ except httpx .HTTPStatusError as e :
510+ logging .error (f"NWS Alerts HTTP error: { e .response .status_code } - { e .response .text } " )
511+ alerts_data = None
512+ except Exception as e :
513+ logging .error (f"Error fetching NWS alerts: { e } " )
514+ alerts_data = None
515+
516+ alerts_info = ""
517+ if alerts_data and 'features' in alerts_data and alerts_data ['features' ]:
518+ alerts_info += "[HUOM! HUOMIOI NÄMÄ! TAKE THESE INTO ACCOUNT!!! MENTION THESE TO THE USER IF THERE ARE WEATHER ALERTS -- INCLUDE ALL THE DETAILS. WHAT, WHEN, WHERE, WHAT SEVERITY, ETC.]\n 🚨 <b>ONGOING ALERTS FROM THE U.S. NWS (weather.gov):</b>\n "
519+ for idx , alert in enumerate (alerts_data ['features' ], start = 1 ):
520+ properties = alert .get ('properties' , {})
521+
522+ event = properties .get ('event' , 'EVENT' ).upper ()
523+ headline = properties .get ('headline' , 'HEADLINE' )
524+ description = properties .get ('description' , 'No further details available' ) # Fetching the detailed description
525+ instruction = properties .get ('instruction' , 'INSTRUCTION' )
526+ severity = properties .get ('severity' , 'Unknown' ).capitalize ()
527+ certainty = properties .get ('certainty' , 'Unknown' ).capitalize ()
528+ urgency = properties .get ('urgency' , 'Unknown' ).capitalize ()
529+ area_desc = properties .get ('areaDesc' , 'N/A' )
530+ effective = properties .get ('effective' , 'N/A' )
531+ expires = properties .get ('expires' , 'N/A' )
532+
533+ alerts_info += (
534+ f"{ idx } . ⚠️ <b>{ event } </b>\n "
535+ f"<b>Vaara:</b> { headline } \n "
536+ f"<b>Kuvaus:</b> { description } \n " # Adding the detailed description
537+ f"<b>Ohjeet:</b> { instruction } \n "
538+ f"<b>Alue:</b> { area_desc } \n "
539+ f"<b>Vakavuus:</b> { severity } \n "
540+ f"<b>Varmuus:</b> { certainty } \n "
541+ f"<b>Kiireellisyys:</b> { urgency } \n "
542+ f"<b>Voimassa alkaen:</b> { effective } \n "
543+ f"<b>Päättyy:</b> { expires } \n \n "
544+ )
545+ else :
546+ alerts_info += "\n 🚨 Ei aktiivisia varoituksia U.S. NWS:n (weather.gov) mukaan.\n "
547+
548+ combined_info += alerts_info
468549
469550 # Combine all information
470551 combined_info += f"\n { detailed_weather_info } \n \n { final_forecast } "
0 commit comments