@@ -33,37 +33,42 @@ def is_location_query(text: str) -> bool:
3333def get_gps_location () -> dict | None :
3434 """
3535 Retrieve current GPS coordinates using Apple CoreLocation via pyobjc.
36- Returns dict with lat, lon, address, maps_link — or None if unavailable.
37- Falls back to a simulated location when running without location permissions .
36+ Falls back to IP-based geolocation if CoreLocation is denied or unavailable.
37+ Returns dict with lat, lon, address, maps_link — or None if all methods fail .
3838 """
3939 try :
4040 import CoreLocation
4141 import time
4242
4343 manager = CoreLocation .CLLocationManager .alloc ().init ()
4444
45- # Request authorization (needed on macOS 10.15+)
4645 auth_status = CoreLocation .CLLocationManager .authorizationStatus ()
47- if auth_status == CoreLocation .kCLAuthorizationStatusNotDetermined :
46+ # kCLAuthorizationStatusDenied = 2, Restricted = 1, NotDetermined = 0
47+ if auth_status in (1 , 2 ):
48+ # Permission denied — skip straight to IP fallback
49+ return _fallback_location ()
50+ if auth_status == 0 :
4851 manager .requestWhenInUseAuthorization ()
49- time .sleep (1 )
52+ time .sleep (1.5 )
5053
5154 location = manager .location ()
5255 if location is None :
5356 return _fallback_location ()
5457
5558 coord = location .coordinate ()
5659 lat , lon = coord .latitude , coord .longitude
57- address = _reverse_geocode (lat , lon )
60+ if lat == 0.0 and lon == 0.0 :
61+ return _fallback_location ()
5862
63+ address = _reverse_geocode (lat , lon )
5964 return {
6065 "lat" : lat ,
6166 "lon" : lon ,
6267 "address" : address ,
6368 "maps_link" : f"https://maps.google.com/?q={ lat :.6f} ,{ lon :.6f} " ,
64- "source" : "CoreLocation (on-device)" ,
69+ "source" : "CoreLocation (on-device GPS )" ,
6570 }
66- except Exception as e :
71+ except Exception :
6772 return _fallback_location ()
6873
6974
@@ -100,16 +105,31 @@ def completion(placemarks, error):
100105
101106
102107def _fallback_location () -> dict :
103- """Return a plausible simulated location for demo/dev purposes."""
104- # San Francisco (Civic Center) — good default for the hackathon
105- lat , lon = 37.7793 , - 122.4193
106- return {
107- "lat" : lat ,
108- "lon" : lon ,
109- "address" : "Civic Center, San Francisco, CA" ,
110- "maps_link" : f"https://maps.google.com/?q={ lat } ,{ lon } " ,
111- "source" : "simulated (no GPS permission)" ,
112- }
108+ """
109+ Fallback when CoreLocation is unavailable or denied.
110+ Uses IP-based geolocation (ipinfo.io, free, no key needed) for real location.
111+ """
112+ import requests as _req
113+ try :
114+ resp = _req .get ("https://ipinfo.io/json" , timeout = 4 ).json ()
115+ loc_str = resp .get ("loc" , "" ) # "37.7749,-122.4194"
116+ city = resp .get ("city" , "" )
117+ region = resp .get ("region" , "" )
118+ country = resp .get ("country" , "" )
119+ if loc_str and "," in loc_str :
120+ lat , lon = map (float , loc_str .split ("," ))
121+ address = ", " .join (p for p in [city , region , country ] if p )
122+ return {
123+ "lat" : lat ,
124+ "lon" : lon ,
125+ "address" : address or f"{ lat :.4f} , { lon :.4f} " ,
126+ "maps_link" : f"https://maps.google.com/?q={ lat :.6f} ,{ lon :.6f} " ,
127+ "source" : "IP geolocation (ipinfo.io)" ,
128+ }
129+ except Exception :
130+ pass
131+ # Last resort: return None so callers know it truly failed
132+ return None
113133
114134
115135def inject_location_into_command (text : str , location : dict ) -> str :
0 commit comments