import os
import requests
from datetime import datetime
from zoneinfo import ZoneInfo
from dotenv import load_dotenv
def get_upcoming_appointments(api_key):
"""Fetches upcoming appointments from the Cal.com API."""
print("Retrieving upcoming appointments...")
url = f"https://api.cal.com/v1/bookings?apiKey={api_key}&status=upcoming"
try:
response = requests.get(url)
response.raise_for_status()
bookings = response.json().get("bookings", [])
# Ensure we only process appointments that are not cancelled and sort them.
accepted_bookings = sorted(
[b for b in bookings if b.get("status") == "ACCEPTED"],
key=lambda b: b['startTime']
)
return accepted_bookings
except requests.exceptions.RequestException as e:
print(f"Error fetching appointments: {e}")
return []
def get_lat_lon_for_location(location, api_key):
"""
Gets latitude and longitude for a location using OpenWeatherMap Geocoding API.
It first tries the location as is, then tries again appending ",US" if the first attempt fails.
"""
base_url = "http://api.openweathermap.org/geo/1.0/direct"
# First attempt: query the location as provided
try:
url = f"{base_url}?q={location}&limit=1&appid={api_key}"
response = requests.get(url)
response.raise_for_status()
data = response.json()
if data:
return data[0]['lat'], data[0]['lon']
except requests.exceptions.RequestException as e:
print(f"Error connecting to geocoding service: {e}")
return None, None
except (KeyError, IndexError):
pass # This will be caught if the location is not found, we'll try again.
# Second attempt (fallback): append ",US" and try again
print(f"Could not find '{location}'. Trying again with country code 'US'...")
try:
us_location_query = f"{location},US"
url = f"{base_url}?q={us_location_query}&limit=1&appid={api_key}"
response = requests.get(url)
response.raise_for_status()
data = response.json()
if data:
return data[0]['lat'], data[0]['lon']
except (requests.exceptions.RequestException, KeyError, IndexError):
# If this also fails, we give up.
pass
return None, None
def get_weather_forecast(lat, lon, api_key):
"""Gets the weather forecast for a given lat/lon."""
url = f"https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={api_key}&units=imperial"
try:
response = requests.get(url)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error fetching weather data: {e}")
return None
def display_summary(appointment, weather, location):
"""Displays a summary of the appointment and weather forecast."""
# Convert UTC time string from API to a local datetime object for display
utc_start_time = datetime.fromisoformat(appointment['startTime'].replace('Z', '+00:00'))
local_tz = ZoneInfo(appointment.get("user", {}).get("timeZone") or "America/New_York")
local_start_time = utc_start_time.astimezone(local_tz)
print("\n-----------------------------------------")
print("\n--- Appointment and Weather Summary ---")
print(f"Appointment: {appointment['title']}")
print(f"When: {local_start_time.strftime('%A, %B %d, %Y, at %I:%M %p %Z')}")
print(f"Where: {location}")
print("\nFetching weather forecast...")
if weather:
print("\nWeather Forecast:")
print(f"Conditions: {weather['weather'][0]['description'].title()}")
print(f"Temperature: {weather['main']['temp']}°F")
print(f"Wind: {weather['wind']['speed']} mph")
else:
print("Could not retrieve weather forecast.")
print("-----------------------------------------")
def main():
"""Main function to run the script."""
load_dotenv()
cal_api_key = os.getenv("CALCOM_API_KEY")
weather_api_key = os.getenv("OPENWEATHERMAP_API_KEY")
if not cal_api_key or not weather_api_key:
print("Error: API keys not found. Please check your .env file.")
return
appointments = get_upcoming_appointments(cal_api_key)
if not appointments:
print("No upcoming appointments found.")
return
print("\nPlease select an appointment to get the weather for:")
for i, appt in enumerate(appointments):
# Convert UTC time for display in the menu
utc_start = datetime.fromisoformat(appt['startTime'].replace('Z', '+00:00'))
local_tz = ZoneInfo(appt.get("user", {}).get("timeZone") or "America/New_York")
local_start = utc_start.astimezone(local_tz)
print(f" {i+1}: {appt['title']} on {local_start.strftime('%a, %b %d at %I:%M %p %Z')}")
try:
choice = int(input("Enter the number of the appointment: ")) - 1
if not 0 <= choice < len(appointments):
raise ValueError
except ValueError:
print("Invalid selection.")
return
selected_appointment = appointments[choice]
# --- Revert to the original, working logic for finding location ---
# The location data is nested within the 'responses' object.
location_response = selected_appointment.get('responses', {}).get('location', {})
# The 'value' can be a simple string or another nested object.
location_value = location_response.get('value') if isinstance(location_response, dict) else None
if isinstance(location_value, dict):
location = location_value.get('value')
else:
location = location_value
if not location or "integrations:" in str(location):
print("\n❌ This appointment has no physical location set. Cannot fetch weather.")
return
lat, lon = get_lat_lon_for_location(location, weather_api_key)
if not lat or not lon:
print(f"\n❌ Could not find coordinates for location: '{location}'.")
print(" Please ensure the location in Cal.com is a recognizable city or address.")
return
weather = get_weather_forecast(lat, lon, weather_api_key)
display_summary(selected_appointment, weather, location)
if __name__ == "__main__":
main()